@swarmclawai/swarmclaw 1.2.3 → 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 (273) hide show
  1. package/README.md +20 -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]/models/route.test.ts +60 -0
  49. package/src/app/api/providers/[id]/models/route.ts +33 -1
  50. package/src/app/api/providers/[id]/route.test.ts +49 -0
  51. package/src/app/api/providers/[id]/route.ts +30 -1
  52. package/src/app/api/providers/ollama/route.ts +6 -5
  53. package/src/app/api/schedules/[id]/route.ts +14 -108
  54. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  55. package/src/app/api/schedules/route.ts +9 -51
  56. package/src/app/api/settings/route.ts +4 -3
  57. package/src/app/api/setup/check-provider/route.ts +15 -1
  58. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  59. package/src/app/api/system/status/route.ts +2 -2
  60. package/src/app/api/tasks/[id]/route.ts +16 -202
  61. package/src/app/api/tasks/bulk/route.ts +5 -86
  62. package/src/app/api/tasks/metrics/route.ts +2 -1
  63. package/src/app/api/tasks/route.ts +11 -171
  64. package/src/app/api/upload/route.ts +1 -1
  65. package/src/app/api/uploads/[filename]/route.ts +1 -1
  66. package/src/app/api/uploads/route.ts +1 -1
  67. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  68. package/src/app/layout.tsx +9 -6
  69. package/src/app/protocols/page.tsx +71 -89
  70. package/src/app/tasks/page.tsx +32 -32
  71. package/src/cli/index.js +1 -0
  72. package/src/cli/spec.js +1 -0
  73. package/src/components/agents/agent-sheet.tsx +51 -25
  74. package/src/components/agents/inspector-panel.tsx +15 -4
  75. package/src/components/auth/setup-wizard/index.tsx +27 -18
  76. package/src/components/auth/setup-wizard/shared.tsx +2 -2
  77. package/src/components/auth/setup-wizard/step-agents.tsx +51 -38
  78. package/src/components/auth/setup-wizard/step-connect.tsx +48 -17
  79. package/src/components/auth/setup-wizard/types.ts +6 -4
  80. package/src/components/auth/setup-wizard/utils.test.ts +38 -8
  81. package/src/components/auth/setup-wizard/utils.ts +14 -8
  82. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  83. package/src/components/connectors/connector-list.tsx +26 -40
  84. package/src/components/connectors/connector-sheet.tsx +95 -149
  85. package/src/components/gateways/gateway-sheet.tsx +61 -110
  86. package/src/components/layout/live-query-sync.tsx +121 -0
  87. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  88. package/src/components/providers/app-query-provider.tsx +17 -0
  89. package/src/components/providers/provider-list.tsx +150 -77
  90. package/src/components/providers/provider-sheet.tsx +102 -77
  91. package/src/components/shared/model-combobox.tsx +5 -4
  92. package/src/components/skills/skill-list.tsx +5 -18
  93. package/src/components/skills/skill-sheet.tsx +21 -20
  94. package/src/components/skills/skills-workspace.tsx +48 -87
  95. package/src/components/tasks/task-card.tsx +20 -13
  96. package/src/components/tasks/task-column.tsx +22 -7
  97. package/src/components/tasks/task-list.tsx +8 -11
  98. package/src/components/tasks/task-sheet.tsx +111 -103
  99. package/src/features/agents/queries.ts +20 -0
  100. package/src/features/chatrooms/queries.ts +20 -0
  101. package/src/features/chats/queries.ts +27 -0
  102. package/src/features/connectors/queries.ts +145 -0
  103. package/src/features/credentials/queries.ts +37 -0
  104. package/src/features/extensions/queries.ts +26 -0
  105. package/src/features/external-agents/queries.ts +36 -0
  106. package/src/features/gateways/queries.ts +274 -0
  107. package/src/features/missions/queries.ts +23 -0
  108. package/src/features/projects/queries.ts +20 -0
  109. package/src/features/protocols/queries.ts +149 -0
  110. package/src/features/providers/queries.ts +142 -0
  111. package/src/features/settings/queries.ts +20 -0
  112. package/src/features/skills/queries.ts +182 -0
  113. package/src/features/tasks/queries.ts +189 -0
  114. package/src/hooks/use-ws.ts +3 -2
  115. package/src/lib/agent-provider-options.test.ts +152 -0
  116. package/src/lib/agent-provider-options.ts +84 -0
  117. package/src/lib/app/api-client.ts +2 -2
  118. package/src/lib/providers/index.test.ts +78 -0
  119. package/src/lib/providers/index.ts +13 -10
  120. package/src/lib/query/client.ts +17 -0
  121. package/src/lib/server/agents/agent-runtime-config.ts +6 -6
  122. package/src/lib/server/agents/agent-service.ts +429 -0
  123. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  124. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  125. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  126. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  127. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  128. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  129. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  130. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  131. package/src/lib/server/build-llm.ts +7 -15
  132. package/src/lib/server/capability-router.test.ts +70 -1
  133. package/src/lib/server/capability-router.ts +24 -99
  134. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  135. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  136. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  137. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  138. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  139. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  140. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  141. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  142. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  143. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  144. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  145. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  146. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  147. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  148. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  149. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  150. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  151. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  152. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  153. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  154. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  155. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  156. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  157. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  158. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  159. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  160. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  161. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  162. package/src/lib/server/chats/chat-session-service.ts +410 -0
  163. package/src/lib/server/connectors/access.ts +1 -1
  164. package/src/lib/server/connectors/commands.ts +7 -6
  165. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  166. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  167. package/src/lib/server/connectors/connector-service.ts +453 -0
  168. package/src/lib/server/connectors/delivery.ts +17 -12
  169. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  170. package/src/lib/server/connectors/media.ts +1 -1
  171. package/src/lib/server/connectors/response-media.ts +1 -1
  172. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  173. package/src/lib/server/connectors/session.ts +9 -7
  174. package/src/lib/server/connectors/voice-note.ts +2 -1
  175. package/src/lib/server/context-manager.ts +20 -1
  176. package/src/lib/server/cost.ts +2 -3
  177. package/src/lib/server/credentials/credential-repository.ts +43 -4
  178. package/src/lib/server/credentials/credential-service.ts +112 -0
  179. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  180. package/src/lib/server/daemon/controller.ts +577 -0
  181. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  182. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  183. package/src/lib/server/daemon/types.ts +101 -0
  184. package/src/lib/server/embeddings.ts +3 -9
  185. package/src/lib/server/eval/agent-regression.ts +3 -2
  186. package/src/lib/server/eval/runner.ts +2 -2
  187. package/src/lib/server/execution-brief.test.ts +167 -0
  188. package/src/lib/server/execution-brief.ts +295 -0
  189. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  190. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  191. package/src/lib/server/execution-engine/index.ts +35 -0
  192. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  193. package/src/lib/server/execution-engine/types.ts +33 -0
  194. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  195. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  196. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  197. package/src/lib/server/messages/message-repository.ts +330 -0
  198. package/src/lib/server/missions/mission-service/core.ts +8 -6
  199. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  200. package/src/lib/server/openclaw/doctor.ts +1 -1
  201. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  202. package/src/lib/server/openclaw/gateway.ts +5 -14
  203. package/src/lib/server/openclaw/health.ts +3 -11
  204. package/src/lib/server/openclaw/sync.ts +8 -6
  205. package/src/lib/server/persistence/storage-context.ts +3 -0
  206. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  207. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  208. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  209. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  210. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  211. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  212. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  213. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  214. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  215. package/src/lib/server/protocols/protocol-types.ts +10 -7
  216. package/src/lib/server/provider-endpoint.ts +7 -12
  217. package/src/lib/server/provider-model-discovery.ts +2 -11
  218. package/src/lib/server/query-expansion.ts +5 -6
  219. package/src/lib/server/run-context.test.ts +365 -0
  220. package/src/lib/server/run-context.ts +367 -0
  221. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  222. package/src/lib/server/runtime/queue/core.ts +61 -190
  223. package/src/lib/server/runtime/run-ledger.ts +8 -0
  224. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  225. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  226. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  227. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  228. package/src/lib/server/service-result.ts +16 -0
  229. package/src/lib/server/session-note.ts +2 -3
  230. package/src/lib/server/session-reset-policy.ts +4 -3
  231. package/src/lib/server/session-tools/connector.ts +9 -6
  232. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  233. package/src/lib/server/session-tools/crud.ts +162 -10
  234. package/src/lib/server/session-tools/delegate.ts +1 -1
  235. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  236. package/src/lib/server/session-tools/memory.ts +6 -4
  237. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  238. package/src/lib/server/session-tools/session-info.ts +119 -12
  239. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  240. package/src/lib/server/session-tools/skills.ts +15 -15
  241. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  242. package/src/lib/server/session-tools/subagent.ts +125 -7
  243. package/src/lib/server/session-tools/team-context.ts +4 -3
  244. package/src/lib/server/session-tools/wallet.ts +0 -58
  245. package/src/lib/server/sessions/session-lineage.ts +55 -0
  246. package/src/lib/server/sessions/session-repository.ts +2 -2
  247. package/src/lib/server/skills/learned-skills.ts +24 -23
  248. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  249. package/src/lib/server/skills/skill-repository.ts +136 -13
  250. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  251. package/src/lib/server/storage-normalization.test.ts +42 -215
  252. package/src/lib/server/storage-normalization.ts +98 -0
  253. package/src/lib/server/storage.ts +19 -0
  254. package/src/lib/server/structured-extract.ts +3 -14
  255. package/src/lib/server/tasks/task-followups.ts +16 -11
  256. package/src/lib/server/tasks/task-result.test.ts +25 -29
  257. package/src/lib/server/tasks/task-result.ts +5 -9
  258. package/src/lib/server/tasks/task-route-service.ts +449 -0
  259. package/src/lib/server/text-normalization.ts +41 -0
  260. package/src/lib/server/tool-planning.ts +6 -42
  261. package/src/lib/server/upload-path.ts +5 -0
  262. package/src/lib/server/working-state/extraction.ts +614 -0
  263. package/src/lib/server/working-state/normalization.ts +866 -0
  264. package/src/lib/server/working-state/prompt.ts +60 -0
  265. package/src/lib/server/working-state/repository.ts +38 -0
  266. package/src/lib/server/working-state/service.test.ts +253 -0
  267. package/src/lib/server/working-state/service.ts +293 -0
  268. package/src/lib/validation/schemas.ts +1 -0
  269. package/src/lib/ws-client.ts +3 -3
  270. package/src/stores/slices/task-slice.ts +1 -4
  271. package/src/stores/use-chatroom-store.ts +2 -2
  272. package/src/types/index.ts +288 -22
  273. package/src/views/settings/section-providers.tsx +2 -2
@@ -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()
@@ -34,8 +34,16 @@ import { isHeartbeatSource } from '@/lib/server/runtime/heartbeat-source'
34
34
  import { perf } from '@/lib/server/runtime/perf'
35
35
  import { getAgent } from '@/lib/server/agents/agent-repository'
36
36
  import { isDirectConnectorSession } from '@/lib/server/connectors/session-kind'
37
- import { getSession, getSessionMessages, saveSession } from '@/lib/server/sessions/session-repository'
37
+ import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
38
+ import {
39
+ getMessages,
40
+ getMessageCount,
41
+ appendMessage,
42
+ replaceMessageAt,
43
+ replaceAllMessages,
44
+ } from '@/lib/server/messages/message-repository'
38
45
  import { appendUsage } from '@/lib/server/usage/usage-repository'
46
+ import { synchronizeWorkingStateForTurn } from '@/lib/server/working-state/service'
39
47
  import { notify } from '@/lib/server/ws-hub'
40
48
 
41
49
  import type { ExecuteChatTurnInput, ExecuteChatTurnResult } from './chat-execution-types'
@@ -193,7 +201,7 @@ export async function finalizeChatTurn(params: {
193
201
  const totalTokens = inputTokens + outputTokens
194
202
  if (totalTokens > 0) {
195
203
  const cost = estimateCost(sessionForRun.model, inputTokens, outputTokens)
196
- const history = getSessionMessages(sessionId)
204
+ const history = getMessages(sessionId)
197
205
  const usageRecord: UsageRecord = {
198
206
  sessionId,
199
207
  messageIndex: history.length,
@@ -354,15 +362,17 @@ export async function finalizeChatTurn(params: {
354
362
  const current = getSession(sessionId)
355
363
  let assistantPersisted = false
356
364
  if (current) {
357
- current.messages = Array.isArray(current.messages) ? current.messages : []
365
+ // Load messages from relational table (lazy-migrates from blob on first access)
366
+ const messages = getMessages(sessionId)
367
+ let messagesPruned = false
358
368
  if (!isDirectConnectorSession(current) && current.connectorContext) {
359
369
  current.connectorContext = undefined
360
370
  }
361
371
  const currentAgent = current.agentId ? getAgent(current.agentId) : null
362
- pruneStreamingAssistantArtifacts(current.messages, {
372
+ if (pruneStreamingAssistantArtifacts(messages, {
363
373
  minIndex: runMessageStartIndex,
364
374
  minTime: runStartedAt,
365
- })
375
+ })) messagesPruned = true
366
376
  const persistField = (key: string, value: unknown) => {
367
377
  const normalized = normalizeResumeId(value)
368
378
  if ((current as unknown as Record<string, unknown>)[key] !== normalized) {
@@ -392,6 +402,8 @@ export async function finalizeChatTurn(params: {
392
402
  }
393
403
  }
394
404
 
405
+ let persistedAssistantMsg: Message | null = null
406
+ let replacedLast = false
395
407
  if (shouldPersistAssistant) {
396
408
  const persistedKind = isHeartbeatRun ? 'heartbeat' : 'chat'
397
409
  const nowTs = Date.now()
@@ -410,7 +422,7 @@ export async function finalizeChatTurn(params: {
410
422
  runId: lifecycleRunId,
411
423
  })
412
424
  if (nextAssistantMessage) {
413
- const previous = current.messages.at(-1)
425
+ const previous = messages.at(-1)
414
426
  const nextToolEvents = nextAssistantMessage.toolEvents || []
415
427
  const nextKind = nextAssistantMessage.kind || persistedKind
416
428
  if (shouldSuppressRedundantConnectorDeliveryFollowup({
@@ -433,11 +445,14 @@ export async function finalizeChatTurn(params: {
433
445
  nextKind,
434
446
  now: nowTs,
435
447
  })) {
436
- current.messages[current.messages.length - 1] = nextAssistantMessage
448
+ messages[messages.length - 1] = nextAssistantMessage
437
449
  assistantPersisted = true
450
+ replacedLast = true
451
+ persistedAssistantMsg = nextAssistantMessage
438
452
  } else {
439
- current.messages.push(nextAssistantMessage)
453
+ messages.push(nextAssistantMessage)
440
454
  assistantPersisted = true
455
+ persistedAssistantMsg = nextAssistantMessage
441
456
  }
442
457
  persistedResponseForHooks = nextAssistantMessage.text
443
458
  if (assistantPersisted) {
@@ -534,16 +549,28 @@ export async function finalizeChatTurn(params: {
534
549
  }
535
550
  }
536
551
  if (isHeartbeatRun && heartbeatClassification === 'suppress') {
537
- pruneSuppressedHeartbeatStreamMessage(current.messages)
552
+ if (pruneSuppressedHeartbeatStreamMessage(messages)) messagesPruned = true
538
553
  }
539
554
 
540
555
  if (isHeartbeatRun) {
541
- const pruned = pruneOldHeartbeatMessages(current.messages)
556
+ const pruned = pruneOldHeartbeatMessages(messages)
542
557
  if (pruned > 0) {
558
+ messagesPruned = true
543
559
  log.info('heartbeat', `Pruned ${pruned} old heartbeat message(s) from session ${sessionId}`)
544
560
  }
545
561
  }
546
562
 
563
+ // Persist messages: use O(1) append/replace when no pruning, O(n) replaceAll when pruned
564
+ if (messagesPruned) {
565
+ replaceAllMessages(sessionId, messages)
566
+ } else if (assistantPersisted && persistedAssistantMsg) {
567
+ if (replacedLast) {
568
+ replaceMessageAt(sessionId, getMessageCount(sessionId) - 1, persistedAssistantMsg)
569
+ } else {
570
+ appendMessage(sessionId, persistedAssistantMsg)
571
+ }
572
+ }
573
+
547
574
  try {
548
575
  await runCapabilityHook('afterChatTurn', {
549
576
  session: current,
@@ -563,8 +590,9 @@ export async function finalizeChatTurn(params: {
563
590
 
564
591
  refreshSessionIdentityState(current, currentAgent)
565
592
  let resolvedMissionId = mission?.id || current.missionId || null
593
+ let updatedMission = mission || null
566
594
  if (resolvedMissionId) {
567
- const updatedMission = await applyMissionOutcomeForTurn({
595
+ updatedMission = await applyMissionOutcomeForTurn({
568
596
  session: current,
569
597
  missionId: resolvedMissionId,
570
598
  source,
@@ -579,6 +607,43 @@ export async function finalizeChatTurn(params: {
579
607
  current.missionId = updatedMission.id
580
608
  }
581
609
  }
610
+ const missionStateChanged = Boolean(
611
+ updatedMission
612
+ && (
613
+ updatedMission.id !== mission?.id
614
+ || updatedMission.updatedAt !== mission?.updatedAt
615
+ || updatedMission.status !== mission?.status
616
+ || updatedMission.phase !== mission?.phase
617
+ || updatedMission.currentStep !== mission?.currentStep
618
+ || updatedMission.waitState?.reason !== mission?.waitState?.reason
619
+ )
620
+ )
621
+ const shouldSyncWorkingState = (
622
+ (!isHeartbeatRun && (assistantPersisted || persistedToolEvents.length > 0 || Boolean(errorMessage)))
623
+ || (isHeartbeatRun && (persistedToolEvents.length > 0 || Boolean(errorMessage) || missionStateChanged))
624
+ )
625
+ if (shouldSyncWorkingState) {
626
+ try {
627
+ await synchronizeWorkingStateForTurn({
628
+ sessionId,
629
+ agentId: current.agentId || null,
630
+ mission: updatedMission,
631
+ message,
632
+ assistantText: hiddenControlOnly ? '' : textForPersistence,
633
+ error: errorMessage || null,
634
+ toolEvents: persistedToolEvents,
635
+ runId: lifecycleRunId,
636
+ source,
637
+ })
638
+ } catch (workingStateError: unknown) {
639
+ log.warn('chat-run', `Working-state sync failed for session ${sessionId}`, {
640
+ runId: lifecycleRunId,
641
+ error: typeof workingStateError === 'object' && workingStateError !== null && 'message' in workingStateError
642
+ ? (workingStateError as Error).message
643
+ : String(workingStateError),
644
+ })
645
+ }
646
+ }
582
647
  try {
583
648
  syncSessionArchiveMemory(current, { agent: currentAgent })
584
649
  } catch {
@@ -591,7 +656,7 @@ export async function finalizeChatTurn(params: {
591
656
  isHeartbeatRun,
592
657
  agentAutoDraftSetting: currentAgent?.autoDraftSkillSuggestions === true,
593
658
  toolEventCount: persistedToolEvents.length,
594
- messageCount: current.messages.length,
659
+ messageCount: messages.length,
595
660
  })) {
596
661
  try {
597
662
  const { createSkillSuggestionFromSession } = await import('@/lib/server/skills/skill-suggestions')
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getSession,
3
- saveSession,
4
3
  } from '@/lib/server/sessions/session-repository'
4
+ import { getMessages, replaceAllMessages } from '@/lib/server/messages/message-repository'
5
5
  import { notify } from '@/lib/server/ws-hub'
6
6
  import type { MessageToolEvent, SSEEvent } from '@/types'
7
7
  import { upsertStreamingAssistantArtifact } from '@/lib/chat/chat-streaming-state'
@@ -72,7 +72,7 @@ export function createPartialAssistantPersistence(input: {
72
72
  try {
73
73
  const current = getSession(prepared.sessionId)
74
74
  if (!current) return
75
- current.messages = Array.isArray(current.messages) ? current.messages : []
75
+ const currentMessages = getMessages(prepared.sessionId)
76
76
  const partialMsg = await applyMessageLifecycleHooks({
77
77
  session: current,
78
78
  message: {
@@ -98,11 +98,11 @@ export function createPartialAssistantPersistence(input: {
98
98
  if (snapshotKey === lastPartialSnapshotKey) return
99
99
  lastPartialSnapshotKey = snapshotKey
100
100
  lastPartialSaveAt = Date.now()
101
- upsertStreamingAssistantArtifact(current.messages, partialMsg, {
101
+ upsertStreamingAssistantArtifact(currentMessages, partialMsg, {
102
102
  minIndex: prepared.runMessageStartIndex,
103
103
  minTime: prepared.runStartedAt,
104
104
  })
105
- saveSession(prepared.sessionId, current)
105
+ replaceAllMessages(prepared.sessionId, currentMessages)
106
106
  notify(`messages:${prepared.sessionId}`)
107
107
  } catch {
108
108
  // Partial persistence is best-effort.
@@ -1,5 +1,6 @@
1
1
  import { notify } from '@/lib/server/ws-hub'
2
2
  import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
3
+ import { appendMessage } from '@/lib/server/messages/message-repository'
3
4
  import type { MessageToolEvent, SSEEvent } from '@/types'
4
5
  import type { ExecuteChatTurnResult } from './chat-execution-types'
5
6
  import {
@@ -47,8 +48,7 @@ async function completeSyntheticAssistantTurn(params: {
47
48
  isSynthetic: true,
48
49
  })
49
50
  if (nextAssistantMessage) {
50
- session.messages = Array.isArray(session.messages) ? session.messages : []
51
- session.messages.push(nextAssistantMessage)
51
+ appendMessage(params.sessionId, nextAssistantMessage)
52
52
  session.lastActiveAt = Date.now()
53
53
  saveSession(params.sessionId, session)
54
54
  if (params.notifyMessages) notify(`messages:${params.sessionId}`)
@@ -2,13 +2,14 @@ import fs from 'fs'
2
2
  import os from 'os'
3
3
 
4
4
  import { getProvider } from '@/lib/providers'
5
- import type { Message, Session } from '@/types'
5
+ import type { ExecutionBrief, Message, Session } from '@/types'
6
6
  import {
7
7
  decryptKey,
8
8
  loadCredentials,
9
9
  } from '@/lib/server/credentials/credential-repository'
10
10
  import { getAgent } from '@/lib/server/agents/agent-repository'
11
11
  import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
12
+ import { getMessages, getMessageCount, appendMessage } from '@/lib/server/messages/message-repository'
12
13
  import { loadSettings } from '@/lib/server/settings/settings-repository'
13
14
  import { loadSkills } from '@/lib/server/skills/skill-repository'
14
15
  import { resolveImagePath } from '@/lib/server/resolve-image'
@@ -45,7 +46,6 @@ import {
45
46
  import { normalizeProviderEndpoint, isLocalOpenClawEndpoint } from '@/lib/openclaw/openclaw-endpoint'
46
47
  import { NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
47
48
  import {
48
- buildMissionContextBlock,
49
49
  resolveMissionForTurn,
50
50
  } from '@/lib/server/missions/mission-service'
51
51
  import {
@@ -69,7 +69,15 @@ import {
69
69
  resetSessionRuntime,
70
70
  resolveSessionResetPolicy,
71
71
  } from '@/lib/server/session-reset-policy'
72
+ import {
73
+ buildExecutionBrief,
74
+ buildExecutionBriefContextBlock,
75
+ } from '@/lib/server/execution-brief'
72
76
  import { checkAgentBudgetLimits } from '@/lib/server/cost'
77
+ import {
78
+ classifyMessage,
79
+ toMessageSemanticsSummary,
80
+ } from '@/lib/server/chat-execution/message-classifier'
73
81
  import {
74
82
  filterRuntimeCapabilityIds,
75
83
  getTodaySpendUsd,
@@ -408,13 +416,13 @@ function resolveApiKeyForSession(session: SessionWithCredentials, provider: Prov
408
416
  if (!session.credentialId) throw new Error('No API key configured for this session')
409
417
  const creds = loadCredentials()
410
418
  const cred = creds[session.credentialId]
411
- if (!cred) throw new Error('API key not found. Please add one in Settings.')
419
+ if (!cred?.encryptedKey) throw new Error('API key not found. Please add one in Settings.')
412
420
  return decryptKey(cred.encryptedKey)
413
421
  }
414
422
  if (provider.optionalApiKey && session.credentialId) {
415
423
  const creds = loadCredentials()
416
424
  const cred = creds[session.credentialId]
417
- if (cred) {
425
+ if (cred?.encryptedKey) {
418
426
  try { return decryptKey(cred.encryptedKey) } catch { return null }
419
427
  }
420
428
  }
@@ -445,7 +453,8 @@ export interface PreparedExecutableChatTurn {
445
453
  lifecycleRunId: string
446
454
  agentForSession: ReturnType<typeof getAgent>
447
455
  mission: Awaited<ReturnType<typeof resolveMissionForTurn>>
448
- missionContextBlock?: string
456
+ executionBrief: ExecutionBrief
457
+ executionBriefContextBlock?: string
449
458
  extensionsForRun: string[]
450
459
  effectiveMessage: string
451
460
  providerType: string
@@ -492,9 +501,8 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
492
501
 
493
502
  const session = getSession(sessionId)
494
503
  if (!session) throw new Error(`Session not found: ${sessionId}`)
495
- session.messages = Array.isArray(session.messages) ? session.messages : []
496
504
  const runStartedAt = Date.now()
497
- const runMessageStartIndex = session.messages.length
505
+ const runMessageStartIndex = getMessageCount(sessionId)
498
506
 
499
507
  const appSettings = loadSettings()
500
508
  const lifecycleRunId = runId || `${sessionId}:${runStartedAt}`
@@ -542,7 +550,7 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
542
550
  {
543
551
  sessionId: session.id,
544
552
  session,
545
- messageCount: Array.isArray(session.messages) ? session.messages.length : 0,
553
+ messageCount: getMessageCount(sessionId),
546
554
  durationMs: Date.now() - (session.createdAt || runStartedAt),
547
555
  reason: freshness.reason || 'session_reset',
548
556
  },
@@ -615,12 +623,16 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
615
623
  if (isHeartbeatRun && input.modelOverride) {
616
624
  sessionForRun = { ...sessionForRun, model: input.modelOverride }
617
625
  }
618
- const missionContextBlock = buildMissionContextBlock(mission)
626
+ const executionBrief = buildExecutionBrief({
627
+ session: sessionForRun,
628
+ mission,
629
+ })
630
+ const executionBriefContextBlock = buildExecutionBriefContextBlock(executionBrief)
619
631
 
620
632
  if (extensionsForRun.length > 0) {
621
633
  const modelResolvePrompt = heartbeatLightContext
622
- ? (joinSystemPromptBlocks(buildLightHeartbeatSystemPrompt(sessionForRun), missionContextBlock) || '')
623
- : (joinSystemPromptBlocks(buildAgentSystemPrompt(sessionForRun), missionContextBlock) || '')
634
+ ? (joinSystemPromptBlocks(buildLightHeartbeatSystemPrompt(sessionForRun), executionBriefContextBlock) || '')
635
+ : (joinSystemPromptBlocks(buildAgentSystemPrompt(sessionForRun), executionBriefContextBlock) || '')
624
636
  const modelResolve = await runCapabilityBeforeModelResolve(
625
637
  {
626
638
  session: sessionForRun,
@@ -710,7 +722,17 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
710
722
 
711
723
  const shouldPersistUserMessage = shouldPersistInboundUserMessage(internal, source)
712
724
  if (shouldPersistUserMessage) {
713
- const linkAnalysis = !internal ? await runLinkUnderstanding(message) : []
725
+ const [linkAnalysis, semantics] = await Promise.all([
726
+ !internal ? runLinkUnderstanding(message) : Promise.resolve([]),
727
+ classifyMessage({
728
+ sessionId,
729
+ agentId: session.agentId || null,
730
+ message,
731
+ history: getMessages(sessionId),
732
+ })
733
+ .then((classification) => toMessageSemanticsSummary(classification))
734
+ .catch(() => undefined),
735
+ ])
714
736
  const guardedUserText = guardUntrustedText({
715
737
  text: message,
716
738
  source,
@@ -727,13 +749,14 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
727
749
  imageUrl: imageUrl || undefined,
728
750
  attachedFiles: attachedFiles?.length ? attachedFiles : undefined,
729
751
  replyToId: input.replyToId || undefined,
752
+ ...(semantics ? { semantics } : {}),
730
753
  },
731
754
  enabledIds: extensionsForRun,
732
755
  phase: 'user',
733
756
  runId: lifecycleRunId,
734
757
  })
735
758
  if (nextUserMessage) {
736
- session.messages.push(nextUserMessage)
759
+ appendMessage(sessionId, nextUserMessage)
737
760
  if (linkAnalysis.length > 0) {
738
761
  const linkAnalysisMessage = await applyMessageLifecycleHooks({
739
762
  session,
@@ -749,7 +772,7 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
749
772
  isSynthetic: true,
750
773
  })
751
774
  if (linkAnalysisMessage) {
752
- session.messages.push(linkAnalysisMessage)
775
+ appendMessage(sessionId, linkAnalysisMessage)
753
776
  }
754
777
  }
755
778
  session.lastActiveAt = Date.now()
@@ -781,8 +804,8 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
781
804
  && !useLocalOpenClawNativeRuntime
782
805
 
783
806
  const systemPrompt = heartbeatLightContext
784
- ? joinSystemPromptBlocks(buildLightHeartbeatSystemPrompt(sessionForRun), missionContextBlock)
785
- : (hasExtensions ? undefined : joinSystemPromptBlocks(buildAgentSystemPrompt(sessionForRun), missionContextBlock))
807
+ ? joinSystemPromptBlocks(buildLightHeartbeatSystemPrompt(sessionForRun), executionBriefContextBlock)
808
+ : (hasExtensions ? undefined : joinSystemPromptBlocks(buildAgentSystemPrompt(sessionForRun), executionBriefContextBlock))
786
809
 
787
810
  return {
788
811
  kind: 'ready',
@@ -797,7 +820,8 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
797
820
  lifecycleRunId,
798
821
  agentForSession,
799
822
  mission,
800
- missionContextBlock: missionContextBlock || undefined,
823
+ executionBrief,
824
+ executionBriefContextBlock: executionBriefContextBlock || undefined,
801
825
  extensionsForRun,
802
826
  effectiveMessage,
803
827
  providerType,
@@ -74,7 +74,8 @@ export async function executePreparedChatTurn(params: {
74
74
  resolvedImagePath,
75
75
  heartbeatLightContext,
76
76
  isAutoRunNoHistory,
77
- missionContextBlock,
77
+ executionBrief,
78
+ executionBriefContextBlock,
78
79
  } = prepared
79
80
 
80
81
  const emit = partialPersistence.emit
@@ -144,7 +145,8 @@ export async function executePreparedChatTurn(params: {
144
145
  attachedFiles,
145
146
  apiKey,
146
147
  systemPrompt,
147
- extraSystemContext: missionContextBlock ? [missionContextBlock] : undefined,
148
+ executionBrief,
149
+ extraSystemContext: [executionBriefContextBlock].filter((value): value is string => typeof value === 'string' && value.trim().length > 0),
148
150
  write: (raw) => parseAndEmit(raw),
149
151
  history: heartbeatHistory ?? applyContextClearBoundary(getSessionMessages(sessionId)),
150
152
  signal: abortController.signal,