@swarmclawai/swarmclaw 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/README.md +14 -0
  2. package/bin/daemon-cmd.js +169 -0
  3. package/bin/server-cmd.js +3 -0
  4. package/bin/swarmclaw.js +11 -0
  5. package/package.json +17 -16
  6. package/src/app/api/agents/[id]/clone/route.ts +3 -32
  7. package/src/app/api/agents/[id]/route.ts +6 -158
  8. package/src/app/api/agents/[id]/status/route.ts +2 -3
  9. package/src/app/api/agents/[id]/thread/route.ts +4 -17
  10. package/src/app/api/agents/bulk/route.ts +5 -47
  11. package/src/app/api/agents/route.ts +5 -119
  12. package/src/app/api/agents/trash/route.ts +13 -24
  13. package/src/app/api/auth/route.ts +3 -9
  14. package/src/app/api/autonomy/estop/route.ts +5 -5
  15. package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
  16. package/src/app/api/chatrooms/[id]/route.ts +23 -2
  17. package/src/app/api/chatrooms/route.ts +13 -2
  18. package/src/app/api/chats/[id]/clear/route.ts +2 -13
  19. package/src/app/api/chats/[id]/deploy/route.ts +2 -3
  20. package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
  21. package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
  22. package/src/app/api/chats/[id]/queue/route.ts +17 -64
  23. package/src/app/api/chats/[id]/retry/route.ts +4 -22
  24. package/src/app/api/chats/[id]/route.ts +10 -138
  25. package/src/app/api/chats/heartbeat/route.ts +2 -1
  26. package/src/app/api/chats/migrate-messages/route.ts +7 -0
  27. package/src/app/api/chats/route.ts +13 -134
  28. package/src/app/api/connectors/[id]/access/route.ts +12 -229
  29. package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
  30. package/src/app/api/connectors/[id]/health/route.ts +12 -39
  31. package/src/app/api/connectors/[id]/route.ts +14 -122
  32. package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
  33. package/src/app/api/connectors/doctor/route.ts +1 -1
  34. package/src/app/api/connectors/route.ts +12 -70
  35. package/src/app/api/credentials/[id]/route.ts +2 -4
  36. package/src/app/api/credentials/route.ts +10 -19
  37. package/src/app/api/daemon/health-check/route.ts +3 -4
  38. package/src/app/api/daemon/route.ts +10 -8
  39. package/src/app/api/documents/route.ts +11 -10
  40. package/src/app/api/external-agents/route.ts +3 -3
  41. package/src/app/api/gateways/[id]/health/route.ts +2 -3
  42. package/src/app/api/gateways/[id]/route.ts +7 -122
  43. package/src/app/api/gateways/route.ts +3 -103
  44. package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
  45. package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
  46. package/src/app/api/openclaw/directory/route.ts +2 -2
  47. package/src/app/api/openclaw/history/route.ts +3 -5
  48. package/src/app/api/providers/[id]/route.test.ts +49 -0
  49. package/src/app/api/providers/ollama/route.ts +6 -5
  50. package/src/app/api/schedules/[id]/route.ts +14 -108
  51. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  52. package/src/app/api/schedules/route.ts +9 -51
  53. package/src/app/api/settings/route.ts +4 -3
  54. package/src/app/api/setup/check-provider/route.ts +23 -1
  55. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  56. package/src/app/api/system/status/route.ts +2 -2
  57. package/src/app/api/tasks/[id]/route.ts +16 -202
  58. package/src/app/api/tasks/bulk/route.ts +5 -86
  59. package/src/app/api/tasks/metrics/route.ts +2 -1
  60. package/src/app/api/tasks/route.ts +11 -171
  61. package/src/app/api/upload/route.ts +1 -1
  62. package/src/app/api/uploads/[filename]/route.ts +1 -1
  63. package/src/app/api/uploads/route.ts +1 -1
  64. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  65. package/src/app/layout.tsx +9 -6
  66. package/src/app/protocols/page.tsx +71 -89
  67. package/src/app/tasks/page.tsx +32 -32
  68. package/src/cli/index.js +1 -0
  69. package/src/cli/spec.js +1 -0
  70. package/src/components/agents/agent-sheet.tsx +5 -5
  71. package/src/components/auth/setup-wizard/index.tsx +4 -4
  72. package/src/components/auth/setup-wizard/step-agents.tsx +1 -1
  73. package/src/components/auth/setup-wizard/step-connect.tsx +1 -1
  74. package/src/components/auth/setup-wizard/utils.ts +1 -1
  75. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  76. package/src/components/connectors/connector-list.tsx +26 -40
  77. package/src/components/connectors/connector-sheet.tsx +95 -149
  78. package/src/components/gateways/gateway-sheet.tsx +61 -110
  79. package/src/components/layout/live-query-sync.tsx +121 -0
  80. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  81. package/src/components/providers/app-query-provider.tsx +17 -0
  82. package/src/components/providers/provider-list.tsx +60 -61
  83. package/src/components/providers/provider-sheet.tsx +74 -56
  84. package/src/components/skills/skill-list.tsx +5 -18
  85. package/src/components/skills/skill-sheet.tsx +21 -20
  86. package/src/components/skills/skills-workspace.tsx +48 -87
  87. package/src/components/tasks/task-card.tsx +20 -13
  88. package/src/components/tasks/task-column.tsx +22 -7
  89. package/src/components/tasks/task-list.tsx +8 -11
  90. package/src/components/tasks/task-sheet.tsx +111 -103
  91. package/src/features/agents/queries.ts +20 -0
  92. package/src/features/chatrooms/queries.ts +20 -0
  93. package/src/features/chats/queries.ts +27 -0
  94. package/src/features/connectors/queries.ts +145 -0
  95. package/src/features/credentials/queries.ts +37 -0
  96. package/src/features/extensions/queries.ts +26 -0
  97. package/src/features/external-agents/queries.ts +36 -0
  98. package/src/features/gateways/queries.ts +274 -0
  99. package/src/features/missions/queries.ts +23 -0
  100. package/src/features/projects/queries.ts +20 -0
  101. package/src/features/protocols/queries.ts +149 -0
  102. package/src/features/providers/queries.ts +142 -0
  103. package/src/features/settings/queries.ts +20 -0
  104. package/src/features/skills/queries.ts +182 -0
  105. package/src/features/tasks/queries.ts +189 -0
  106. package/src/hooks/use-ws.ts +3 -2
  107. package/src/lib/app/api-client.ts +2 -2
  108. package/src/lib/providers/index.test.ts +108 -0
  109. package/src/lib/providers/index.ts +38 -15
  110. package/src/lib/query/client.ts +17 -0
  111. package/src/lib/server/agents/agent-runtime-config.ts +1 -1
  112. package/src/lib/server/agents/agent-service.ts +429 -0
  113. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  114. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  115. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  116. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  117. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  118. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  119. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  120. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  121. package/src/lib/server/build-llm.ts +7 -15
  122. package/src/lib/server/capability-router.test.ts +70 -1
  123. package/src/lib/server/capability-router.ts +24 -99
  124. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  125. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  126. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  127. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  128. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  129. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  130. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  131. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  132. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  133. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  134. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  135. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  136. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  137. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  138. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  139. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  140. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  141. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  142. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  143. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  144. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  145. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  146. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  147. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  148. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  149. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  150. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  151. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  152. package/src/lib/server/chats/chat-session-service.ts +410 -0
  153. package/src/lib/server/connectors/access.ts +1 -1
  154. package/src/lib/server/connectors/commands.ts +7 -6
  155. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  156. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  157. package/src/lib/server/connectors/connector-service.ts +453 -0
  158. package/src/lib/server/connectors/delivery.ts +17 -12
  159. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  160. package/src/lib/server/connectors/media.ts +1 -1
  161. package/src/lib/server/connectors/response-media.ts +1 -1
  162. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  163. package/src/lib/server/connectors/session.ts +9 -7
  164. package/src/lib/server/connectors/voice-note.ts +2 -1
  165. package/src/lib/server/context-manager.ts +20 -1
  166. package/src/lib/server/cost.ts +2 -3
  167. package/src/lib/server/credentials/credential-repository.ts +43 -4
  168. package/src/lib/server/credentials/credential-service.ts +112 -0
  169. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  170. package/src/lib/server/daemon/controller.ts +577 -0
  171. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  172. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  173. package/src/lib/server/daemon/types.ts +101 -0
  174. package/src/lib/server/embeddings.ts +3 -9
  175. package/src/lib/server/eval/agent-regression.ts +3 -2
  176. package/src/lib/server/eval/runner.ts +2 -2
  177. package/src/lib/server/execution-brief.test.ts +167 -0
  178. package/src/lib/server/execution-brief.ts +295 -0
  179. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  180. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  181. package/src/lib/server/execution-engine/index.ts +35 -0
  182. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  183. package/src/lib/server/execution-engine/types.ts +33 -0
  184. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  185. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  186. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  187. package/src/lib/server/messages/message-repository.ts +330 -0
  188. package/src/lib/server/missions/mission-service/core.ts +8 -6
  189. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  190. package/src/lib/server/openclaw/doctor.ts +1 -1
  191. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  192. package/src/lib/server/openclaw/gateway.ts +5 -14
  193. package/src/lib/server/openclaw/health.ts +3 -11
  194. package/src/lib/server/openclaw/sync.ts +8 -6
  195. package/src/lib/server/persistence/storage-context.ts +3 -0
  196. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  197. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  198. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  199. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  200. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  201. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  202. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  203. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  204. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  205. package/src/lib/server/protocols/protocol-types.ts +10 -7
  206. package/src/lib/server/provider-endpoint.ts +7 -12
  207. package/src/lib/server/provider-model-discovery.ts +2 -11
  208. package/src/lib/server/query-expansion.ts +5 -6
  209. package/src/lib/server/run-context.test.ts +365 -0
  210. package/src/lib/server/run-context.ts +367 -0
  211. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  212. package/src/lib/server/runtime/queue/core.ts +61 -190
  213. package/src/lib/server/runtime/run-ledger.ts +8 -0
  214. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  215. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  216. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  217. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  218. package/src/lib/server/service-result.ts +16 -0
  219. package/src/lib/server/session-note.ts +2 -3
  220. package/src/lib/server/session-reset-policy.ts +4 -3
  221. package/src/lib/server/session-tools/connector.ts +9 -6
  222. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  223. package/src/lib/server/session-tools/crud.ts +162 -10
  224. package/src/lib/server/session-tools/delegate.ts +1 -1
  225. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  226. package/src/lib/server/session-tools/memory.ts +6 -4
  227. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  228. package/src/lib/server/session-tools/session-info.ts +119 -12
  229. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  230. package/src/lib/server/session-tools/skills.ts +15 -15
  231. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  232. package/src/lib/server/session-tools/subagent.ts +125 -7
  233. package/src/lib/server/session-tools/team-context.ts +4 -3
  234. package/src/lib/server/session-tools/wallet.ts +0 -58
  235. package/src/lib/server/sessions/session-lineage.ts +55 -0
  236. package/src/lib/server/sessions/session-repository.ts +2 -2
  237. package/src/lib/server/skills/learned-skills.ts +24 -23
  238. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  239. package/src/lib/server/skills/skill-repository.ts +136 -13
  240. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  241. package/src/lib/server/storage-normalization.test.ts +44 -267
  242. package/src/lib/server/storage-normalization.ts +75 -0
  243. package/src/lib/server/storage.ts +19 -0
  244. package/src/lib/server/structured-extract.ts +3 -14
  245. package/src/lib/server/tasks/task-followups.ts +16 -11
  246. package/src/lib/server/tasks/task-result.test.ts +25 -29
  247. package/src/lib/server/tasks/task-result.ts +5 -9
  248. package/src/lib/server/tasks/task-route-service.ts +449 -0
  249. package/src/lib/server/text-normalization.ts +41 -0
  250. package/src/lib/server/tool-planning.ts +6 -42
  251. package/src/lib/server/upload-path.ts +5 -0
  252. package/src/lib/server/working-state/extraction.ts +614 -0
  253. package/src/lib/server/working-state/normalization.ts +866 -0
  254. package/src/lib/server/working-state/prompt.ts +60 -0
  255. package/src/lib/server/working-state/repository.ts +38 -0
  256. package/src/lib/server/working-state/service.test.ts +253 -0
  257. package/src/lib/server/working-state/service.ts +293 -0
  258. package/src/lib/validation/schemas.ts +1 -0
  259. package/src/lib/ws-client.ts +3 -3
  260. package/src/stores/slices/task-slice.ts +1 -4
  261. package/src/stores/use-chatroom-store.ts +2 -2
  262. package/src/types/index.ts +277 -12
@@ -0,0 +1,367 @@
1
+ /**
2
+ * RunContext — Persistent structured working memory for agents.
3
+ *
4
+ * Survives compaction, flows through delegation, and accumulates learnings
5
+ * from reflections. The session message history is the primary "memory" but
6
+ * it's fragile (compaction drops old messages) and isolated (workers don't
7
+ * see coordinator context). RunContext fixes both.
8
+ */
9
+
10
+ import type { Message, RunContext, RunReflection, Session } from '@/types'
11
+ import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
12
+ import { log } from '@/lib/server/logger'
13
+
14
+ const TAG = 'run-context'
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Array caps — enforced by pruneRunContext
18
+ // ---------------------------------------------------------------------------
19
+
20
+ const CAPS: Record<string, number> = {
21
+ keyFacts: 20,
22
+ discoveries: 16,
23
+ failedApproaches: 16,
24
+ constraints: 12,
25
+ currentPlan: 12,
26
+ completedSteps: 12,
27
+ blockers: 8,
28
+ }
29
+
30
+ const PARENT_CONTEXT_BUDGET = 600
31
+ const RUN_CONTEXT_SECTION_BUDGET = 3000
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Core helpers
35
+ // ---------------------------------------------------------------------------
36
+
37
+ /** Return the existing RunContext or create a fresh empty one. */
38
+ export function ensureRunContext(existing: RunContext | null | undefined): RunContext {
39
+ if (existing && typeof existing === 'object' && typeof existing.version === 'number') {
40
+ // Backfill any missing array fields from malformed persisted data
41
+ if (!Array.isArray(existing.constraints)) existing.constraints = []
42
+ if (!Array.isArray(existing.keyFacts)) existing.keyFacts = []
43
+ if (!Array.isArray(existing.discoveries)) existing.discoveries = []
44
+ if (!Array.isArray(existing.failedApproaches)) existing.failedApproaches = []
45
+ if (!Array.isArray(existing.currentPlan)) existing.currentPlan = []
46
+ if (!Array.isArray(existing.completedSteps)) existing.completedSteps = []
47
+ if (!Array.isArray(existing.blockers)) existing.blockers = []
48
+ return existing
49
+ }
50
+ return {
51
+ objective: null,
52
+ constraints: [],
53
+ keyFacts: [],
54
+ discoveries: [],
55
+ failedApproaches: [],
56
+ currentPlan: [],
57
+ completedSteps: [],
58
+ blockers: [],
59
+ parentContext: null,
60
+ updatedAt: Date.now(),
61
+ version: 0,
62
+ }
63
+ }
64
+
65
+ /** Normalize whitespace, trim, and case-insensitive dedup an array of strings. */
66
+ export function dedup(arr: string[]): string[] {
67
+ const seen = new Set<string>()
68
+ const result: string[] = []
69
+ for (const raw of arr) {
70
+ const normalized = raw.replace(/\s+/g, ' ').trim()
71
+ if (!normalized) continue
72
+ const key = normalized.toLowerCase()
73
+ if (seen.has(key)) continue
74
+ seen.add(key)
75
+ result.push(normalized)
76
+ }
77
+ return result
78
+ }
79
+
80
+ /** Enforce array caps on a RunContext, keeping the most recent entries. */
81
+ export function pruneRunContext(ctx: RunContext): RunContext {
82
+ const record = ctx as unknown as Record<string, unknown>
83
+ for (const [field, cap] of Object.entries(CAPS)) {
84
+ const arr = record[field]
85
+ if (Array.isArray(arr) && arr.length > cap) {
86
+ record[field] = arr.slice(-cap)
87
+ }
88
+ }
89
+ return ctx
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Reflection folding
94
+ // ---------------------------------------------------------------------------
95
+
96
+ /**
97
+ * Map reflection note fields into RunContext fields.
98
+ *
99
+ * | RunReflection field | RunContext target |
100
+ * |---------------------------|---------------------|
101
+ * | invariantNotes | keyFacts |
102
+ * | lessonNotes | keyFacts |
103
+ * | derivedNotes | discoveries |
104
+ * | significantEventNotes | discoveries |
105
+ * | failureNotes | failedApproaches |
106
+ * | openLoopNotes | blockers |
107
+ * | boundaryNotes | constraints |
108
+ */
109
+ export function foldReflectionIntoRunContext(
110
+ current: RunContext | null | undefined,
111
+ reflection: RunReflection,
112
+ ): RunContext {
113
+ const ctx = ensureRunContext(current)
114
+
115
+ // keyFacts <- invariantNotes + lessonNotes
116
+ ctx.keyFacts = dedup([
117
+ ...ctx.keyFacts,
118
+ ...(reflection.invariantNotes || []),
119
+ ...(reflection.lessonNotes || []),
120
+ ])
121
+
122
+ // discoveries <- derivedNotes + significantEventNotes
123
+ ctx.discoveries = dedup([
124
+ ...ctx.discoveries,
125
+ ...(reflection.derivedNotes || []),
126
+ ...(reflection.significantEventNotes || []),
127
+ ])
128
+
129
+ // failedApproaches <- failureNotes
130
+ ctx.failedApproaches = dedup([
131
+ ...ctx.failedApproaches,
132
+ ...(reflection.failureNotes || []),
133
+ ])
134
+
135
+ // blockers <- openLoopNotes
136
+ ctx.blockers = dedup([
137
+ ...ctx.blockers,
138
+ ...(reflection.openLoopNotes || []),
139
+ ])
140
+
141
+ // constraints <- boundaryNotes
142
+ ctx.constraints = dedup([
143
+ ...ctx.constraints,
144
+ ...(reflection.boundaryNotes || []),
145
+ ])
146
+
147
+ ctx.version++
148
+ ctx.updatedAt = Date.now()
149
+ return pruneRunContext(ctx)
150
+ }
151
+
152
+ // ---------------------------------------------------------------------------
153
+ // Delegation serialization
154
+ // ---------------------------------------------------------------------------
155
+
156
+ /** Serialize a RunContext into a budget-capped summary string for delegation handoff. */
157
+ export function serializeParentContext(ctx: RunContext | null | undefined): string | null {
158
+ if (!ctx) return null
159
+
160
+ const parts: string[] = []
161
+ let budget = PARENT_CONTEXT_BUDGET
162
+
163
+ const append = (line: string): boolean => {
164
+ if (budget - line.length - 1 < 0) return false
165
+ parts.push(line)
166
+ budget -= line.length + 1
167
+ return true
168
+ }
169
+
170
+ if (ctx.objective) append(`Objective: ${ctx.objective}`)
171
+ if (ctx.constraints.length > 0) append(`Constraints: ${ctx.constraints.join('; ')}`)
172
+ if (ctx.keyFacts.length > 0) append(`Key facts: ${ctx.keyFacts.slice(-6).join('; ')}`)
173
+ if (ctx.failedApproaches.length > 0) append(`Already tried (failed): ${ctx.failedApproaches.slice(-4).join('; ')}`)
174
+ if (ctx.blockers.length > 0) append(`Blockers: ${ctx.blockers.join('; ')}`)
175
+ if (ctx.discoveries.length > 0) append(`Discoveries: ${ctx.discoveries.slice(-4).join('; ')}`)
176
+
177
+ return parts.length > 0 ? parts.join('\n') : null
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // Pre-compaction fact extraction (regex-based, no LLM call)
182
+ // ---------------------------------------------------------------------------
183
+
184
+ const FACT_PATTERNS: RegExp[] = [
185
+ /\b(?:important|critical|key|note|remember|must|always|never|constraint|requirement|blocker|discovered|found that|turns out)\b[:\s]+(.{10,200})/gi,
186
+ /\b(?:error|failed|doesn't work|won't work|can't|cannot|broken)\b[:\s]+(.{10,200})/gi,
187
+ ]
188
+
189
+ /** Extract lightweight facts from messages about to be compacted. */
190
+ export function extractFactsFromMessages(messages: Message[]): { keyFacts: string[]; failedApproaches: string[] } {
191
+ const keyFacts: string[] = []
192
+ const failedApproaches: string[] = []
193
+
194
+ for (const msg of messages) {
195
+ const text = msg.text || ''
196
+ if (!text || text.length < 20) continue
197
+
198
+ for (const pattern of FACT_PATTERNS) {
199
+ pattern.lastIndex = 0
200
+ let match: RegExpExecArray | null = pattern.exec(text)
201
+ while (match !== null) {
202
+ const fact = match[1]?.trim()
203
+ if (!fact || fact.length < 10) {
204
+ match = pattern.exec(text)
205
+ continue
206
+ }
207
+
208
+ if (/\b(?:error|failed|doesn't work|won't work|can't|cannot|broken)\b/i.test(match[0])) {
209
+ failedApproaches.push(fact.slice(0, 200))
210
+ } else {
211
+ keyFacts.push(fact.slice(0, 200))
212
+ }
213
+ match = pattern.exec(text)
214
+ }
215
+ }
216
+ }
217
+
218
+ return {
219
+ keyFacts: dedup(keyFacts).slice(-10),
220
+ failedApproaches: dedup(failedApproaches).slice(-8),
221
+ }
222
+ }
223
+
224
+ // ---------------------------------------------------------------------------
225
+ // Session-level update helper
226
+ // ---------------------------------------------------------------------------
227
+
228
+ /** Load-modify-save a session's RunContext with a version bump. */
229
+ export function updateSessionRunContext(
230
+ sessionId: string,
231
+ updater: (ctx: RunContext) => RunContext,
232
+ ): void {
233
+ try {
234
+ const session = getSession(sessionId) as Session | undefined
235
+ if (!session) return
236
+
237
+ const ctx = ensureRunContext(session.runContext)
238
+ const updated = updater(ctx)
239
+ updated.version++
240
+ updated.updatedAt = Date.now()
241
+ session.runContext = pruneRunContext(updated)
242
+ saveSession(sessionId, session)
243
+ } catch (err: unknown) {
244
+ log.warn(TAG, `Failed to update RunContext for session ${sessionId}:`, err instanceof Error ? err.message : String(err))
245
+ }
246
+ }
247
+
248
+ // ---------------------------------------------------------------------------
249
+ // MainLoopState -> RunContext projection
250
+ // ---------------------------------------------------------------------------
251
+
252
+ /** Project orchestrator MainLoopState into the session's RunContext. */
253
+ export function syncMainLoopToRunContext(
254
+ sessionId: string,
255
+ mainLoopState: {
256
+ goal?: string | null
257
+ planSteps?: string[]
258
+ completedPlanSteps?: string[]
259
+ workingMemoryNotes?: string[]
260
+ },
261
+ ): void {
262
+ try {
263
+ const session = getSession(sessionId) as Session | undefined
264
+ if (!session) return
265
+
266
+ const ctx = ensureRunContext(session.runContext)
267
+
268
+ if (mainLoopState.goal) ctx.objective = mainLoopState.goal
269
+ if (Array.isArray(mainLoopState.planSteps)) ctx.currentPlan = mainLoopState.planSteps
270
+ if (Array.isArray(mainLoopState.completedPlanSteps)) ctx.completedSteps = mainLoopState.completedPlanSteps
271
+ if (Array.isArray(mainLoopState.workingMemoryNotes) && mainLoopState.workingMemoryNotes.length > 0) {
272
+ ctx.keyFacts = dedup([...ctx.keyFacts, ...mainLoopState.workingMemoryNotes])
273
+ }
274
+
275
+ ctx.version++
276
+ ctx.updatedAt = Date.now()
277
+ session.runContext = pruneRunContext(ctx)
278
+ saveSession(sessionId, session)
279
+ } catch (err: unknown) {
280
+ log.warn(TAG, `Failed to sync MainLoopState to RunContext for ${sessionId}:`, err instanceof Error ? err.message : String(err))
281
+ }
282
+ }
283
+
284
+ // ---------------------------------------------------------------------------
285
+ // Prompt section rendering
286
+ // ---------------------------------------------------------------------------
287
+
288
+ /** Build the RunContext prompt section. Returns null if nothing to render or minimal prompt. */
289
+ export function buildRunContextSection(
290
+ runContext: RunContext | null | undefined,
291
+ isMinimalPrompt: boolean,
292
+ ): string | null {
293
+ if (isMinimalPrompt || !runContext) return null
294
+
295
+ const lines: string[] = []
296
+ let budget = RUN_CONTEXT_SECTION_BUDGET
297
+
298
+ const append = (line: string): boolean => {
299
+ if (budget - line.length - 1 < 0) return false
300
+ lines.push(line)
301
+ budget -= line.length + 1
302
+ return true
303
+ }
304
+
305
+ // Parent coordinator context
306
+ if (runContext.parentContext) {
307
+ append('## Coordinator Context')
308
+ append(runContext.parentContext)
309
+ append('')
310
+ }
311
+
312
+ // Objective
313
+ if (runContext.objective) {
314
+ append('## Current Objective')
315
+ append(runContext.objective)
316
+ append('')
317
+ }
318
+
319
+ // Constraints
320
+ if (runContext.constraints.length > 0) {
321
+ append('## Constraints')
322
+ for (const c of runContext.constraints) append(`- ${c}`)
323
+ append('')
324
+ }
325
+
326
+ // Key facts — survive compaction
327
+ if (runContext.keyFacts.length > 0) {
328
+ append('## Key Facts')
329
+ for (const f of runContext.keyFacts) append(`- ${f}`)
330
+ append('')
331
+ }
332
+
333
+ // Failed approaches — don't repeat these
334
+ if (runContext.failedApproaches.length > 0) {
335
+ append('## Already Tried (Failed)')
336
+ for (const f of runContext.failedApproaches) append(`- ${f}`)
337
+ append('')
338
+ }
339
+
340
+ // Current plan
341
+ if (runContext.currentPlan.length > 0) {
342
+ append('## Current Plan')
343
+ const completedSet = new Set(runContext.completedSteps.map((s) => s.toLowerCase()))
344
+ for (const step of runContext.currentPlan) {
345
+ const done = completedSet.has(step.toLowerCase())
346
+ append(`- [${done ? 'x' : ' '}] ${step}`)
347
+ }
348
+ append('')
349
+ }
350
+
351
+ // Blockers
352
+ if (runContext.blockers.length > 0) {
353
+ append('## Blockers')
354
+ for (const b of runContext.blockers) append(`- ${b}`)
355
+ append('')
356
+ }
357
+
358
+ // Discoveries
359
+ if (runContext.discoveries.length > 0) {
360
+ append('## Discoveries')
361
+ for (const d of runContext.discoveries) append(`- ${d}`)
362
+ append('')
363
+ }
364
+
365
+ if (lines.length === 0) return null
366
+ return ['## Working Memory (RunContext)', '', ...lines].join('\n')
367
+ }
@@ -21,6 +21,7 @@ import { log } from '@/lib/server/logger'
21
21
  import { WORKSPACE_DIR } from '@/lib/server/data-dir'
22
22
  import { drainSystemEvents, drainOrchestratorEvents } from '@/lib/server/runtime/system-events'
23
23
  import { buildMissionContextBlock } from '@/lib/server/missions/mission-service'
24
+ import { getMessages, getRecentMessages, clearMessages } from '@/lib/server/messages/message-repository'
24
25
  import type { Agent, AppSettings, ApprovalRequest, Chatroom, Message, Session } from '@/types'
25
26
  import { isOrchestratorEligible } from '@/lib/orchestrator-config'
26
27
  import { buildIdentityContinuityContext } from '@/lib/server/identity-continuity'
@@ -384,7 +385,7 @@ export function buildAgentHeartbeatPrompt(
384
385
  const effectiveFileContent = isHeartbeatContentEffectivelyEmpty(strippedContent) ? '' : strippedContent
385
386
  if (effectiveFileContent) sections.push(`\nHEARTBEAT.md contents:\n${effectiveFileContent.slice(0, 2000)}`)
386
387
 
387
- const recentMessages = (Array.isArray(session.messages) ? session.messages : []).slice(-5) as HeartbeatPromptMessage[]
388
+ const recentMessages = (session.id ? getRecentMessages(session.id, 5) : []) as HeartbeatPromptMessage[]
388
389
  const recentContext = recentMessages
389
390
  .map((m) => {
390
391
  const text = (m.text || '').slice(0, 200)
@@ -520,9 +521,10 @@ export function heartbeatConfigForSession(
520
521
  }
521
522
 
522
523
  function lastUserMessageAt(session: HeartbeatPromptSession): number {
523
- if (!Array.isArray(session?.messages)) return 0
524
- for (let i = session.messages.length - 1; i >= 0; i--) {
525
- const msg = session.messages[i]
524
+ if (!session?.id) return 0
525
+ const messages = getMessages(session.id)
526
+ for (let i = messages.length - 1; i >= 0; i--) {
527
+ const msg = messages[i]
526
528
  if (msg?.role === 'user' && typeof msg.time === 'number' && msg.time > 0) {
527
529
  return msg.time
528
530
  }
@@ -742,9 +744,9 @@ export async function tickHeartbeats() {
742
744
  // Isolated mode: clear message history before each heartbeat for a fresh context
743
745
  const resetMode = session.sessionResetMode ?? agent?.sessionResetMode
744
746
  if (resetMode === 'isolated') {
747
+ clearMessages(session.id)
745
748
  patchSession(session.id, (s) => {
746
749
  if (!s) return s
747
- s.messages = []
748
750
  s.updatedAt = Date.now()
749
751
  return s
750
752
  })