@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
@@ -0,0 +1,365 @@
1
+ import assert from 'node:assert/strict'
2
+ import { test } from 'node:test'
3
+
4
+ import {
5
+ ensureRunContext,
6
+ dedup,
7
+ pruneRunContext,
8
+ foldReflectionIntoRunContext,
9
+ buildRunContextSection,
10
+ extractFactsFromMessages,
11
+ } from './run-context'
12
+ import type { RunContext, RunReflection, Message } from '@/types'
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // ensureRunContext
16
+ // ---------------------------------------------------------------------------
17
+
18
+ test('ensureRunContext returns fresh context for null input', () => {
19
+ const ctx = ensureRunContext(null)
20
+ assert.equal(ctx.version, 0)
21
+ assert.equal(ctx.objective, null)
22
+ assert.deepEqual(ctx.keyFacts, [])
23
+ assert.deepEqual(ctx.discoveries, [])
24
+ assert.deepEqual(ctx.failedApproaches, [])
25
+ assert.deepEqual(ctx.constraints, [])
26
+ assert.deepEqual(ctx.currentPlan, [])
27
+ assert.deepEqual(ctx.completedSteps, [])
28
+ assert.deepEqual(ctx.blockers, [])
29
+ assert.equal(ctx.parentContext, null)
30
+ })
31
+
32
+ test('ensureRunContext returns fresh context for undefined input', () => {
33
+ const ctx = ensureRunContext(undefined)
34
+ assert.equal(ctx.version, 0)
35
+ assert.deepEqual(ctx.keyFacts, [])
36
+ })
37
+
38
+ test('ensureRunContext passes through a valid RunContext', () => {
39
+ const existing: RunContext = {
40
+ objective: 'Ship it',
41
+ constraints: ['No breaking changes'],
42
+ keyFacts: ['Fact A'],
43
+ discoveries: [],
44
+ failedApproaches: [],
45
+ currentPlan: ['Step 1'],
46
+ completedSteps: [],
47
+ blockers: [],
48
+ parentContext: null,
49
+ updatedAt: 1000,
50
+ version: 5,
51
+ }
52
+
53
+ const result = ensureRunContext(existing)
54
+ assert.equal(result, existing) // same reference
55
+ assert.equal(result.version, 5)
56
+ assert.deepEqual(result.keyFacts, ['Fact A'])
57
+ })
58
+
59
+ test('ensureRunContext backfills missing arrays on malformed object with version', () => {
60
+ // Simulates persisted data where some array fields were stripped/corrupted
61
+ const malformed = { version: 3, objective: 'Fix it', updatedAt: 1 } as unknown as RunContext
62
+
63
+ const result = ensureRunContext(malformed)
64
+ assert.equal(result.version, 3) // preserves version
65
+ assert.equal(result.objective, 'Fix it')
66
+ assert.deepEqual(result.constraints, [])
67
+ assert.deepEqual(result.keyFacts, [])
68
+ assert.deepEqual(result.discoveries, [])
69
+ assert.deepEqual(result.failedApproaches, [])
70
+ assert.deepEqual(result.currentPlan, [])
71
+ assert.deepEqual(result.completedSteps, [])
72
+ assert.deepEqual(result.blockers, [])
73
+ })
74
+
75
+ test('ensureRunContext returns fresh context for object without version field', () => {
76
+ const noVersion = { objective: 'Something', keyFacts: ['A'] } as unknown as RunContext
77
+ const result = ensureRunContext(noVersion)
78
+ assert.equal(result.version, 0)
79
+ assert.deepEqual(result.keyFacts, [])
80
+ assert.notEqual(result, noVersion) // new object
81
+ })
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // dedup
85
+ // ---------------------------------------------------------------------------
86
+
87
+ test('dedup removes case-insensitive duplicates', () => {
88
+ assert.deepEqual(dedup(['Hello World', 'hello world', 'HELLO WORLD']), ['Hello World'])
89
+ })
90
+
91
+ test('dedup normalizes whitespace', () => {
92
+ assert.deepEqual(dedup(['too many spaces', 'too many spaces']), ['too many spaces'])
93
+ })
94
+
95
+ test('dedup filters out empty and blank strings', () => {
96
+ assert.deepEqual(dedup(['valid', '', ' ', '\t\n', 'also valid']), ['valid', 'also valid'])
97
+ })
98
+
99
+ test('dedup preserves order of first occurrence', () => {
100
+ assert.deepEqual(dedup(['B', 'A', 'b', 'C', 'a']), ['B', 'A', 'C'])
101
+ })
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // pruneRunContext
105
+ // ---------------------------------------------------------------------------
106
+
107
+ test('pruneRunContext enforces array caps keeping most recent entries', () => {
108
+ const ctx = ensureRunContext(null)
109
+ // keyFacts cap is 20 — fill with 25
110
+ ctx.keyFacts = Array.from({ length: 25 }, (_, i) => `fact-${i}`)
111
+ // blockers cap is 8 — fill with 12
112
+ ctx.blockers = Array.from({ length: 12 }, (_, i) => `blocker-${i}`)
113
+
114
+ const pruned = pruneRunContext(ctx)
115
+ assert.equal(pruned.keyFacts.length, 20)
116
+ assert.equal(pruned.keyFacts[0], 'fact-5') // sliced from end
117
+ assert.equal(pruned.keyFacts[19], 'fact-24')
118
+ assert.equal(pruned.blockers.length, 8)
119
+ assert.equal(pruned.blockers[0], 'blocker-4')
120
+ })
121
+
122
+ test('pruneRunContext leaves arrays under cap unchanged', () => {
123
+ const ctx = ensureRunContext(null)
124
+ ctx.keyFacts = ['a', 'b', 'c']
125
+ const pruned = pruneRunContext(ctx)
126
+ assert.deepEqual(pruned.keyFacts, ['a', 'b', 'c'])
127
+ })
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // foldReflectionIntoRunContext
131
+ // ---------------------------------------------------------------------------
132
+
133
+ test('foldReflectionIntoRunContext creates context from null and maps reflection fields', () => {
134
+ const reflection = {
135
+ id: 'r1',
136
+ runId: 'run1',
137
+ sessionId: 's1',
138
+ source: 'test',
139
+ status: 'completed' as const,
140
+ summary: 'Test reflection',
141
+ invariantNotes: ['Invariant A'],
142
+ lessonNotes: ['Lesson B'],
143
+ derivedNotes: ['Derived C'],
144
+ significantEventNotes: ['Event D'],
145
+ failureNotes: ['Failure E'],
146
+ openLoopNotes: ['Open loop F'],
147
+ boundaryNotes: ['Boundary G'],
148
+ createdAt: 1,
149
+ updatedAt: 1,
150
+ } satisfies Partial<RunReflection> as RunReflection
151
+
152
+ const ctx = foldReflectionIntoRunContext(null, reflection)
153
+ assert.equal(ctx.version, 1)
154
+ assert.ok(ctx.keyFacts.includes('Invariant A'))
155
+ assert.ok(ctx.keyFacts.includes('Lesson B'))
156
+ assert.ok(ctx.discoveries.includes('Derived C'))
157
+ assert.ok(ctx.discoveries.includes('Event D'))
158
+ assert.ok(ctx.failedApproaches.includes('Failure E'))
159
+ assert.ok(ctx.blockers.includes('Open loop F'))
160
+ assert.ok(ctx.constraints.includes('Boundary G'))
161
+ })
162
+
163
+ test('foldReflectionIntoRunContext deduplicates when folding', () => {
164
+ const existing: RunContext = {
165
+ objective: null,
166
+ constraints: [],
167
+ keyFacts: ['Already known fact'],
168
+ discoveries: [],
169
+ failedApproaches: [],
170
+ currentPlan: [],
171
+ completedSteps: [],
172
+ blockers: [],
173
+ parentContext: null,
174
+ updatedAt: 1,
175
+ version: 2,
176
+ }
177
+
178
+ const reflection = {
179
+ id: 'r2',
180
+ runId: 'run2',
181
+ sessionId: 's2',
182
+ source: 'test',
183
+ status: 'completed' as const,
184
+ summary: 'Dup test',
185
+ invariantNotes: ['already known fact'], // same content, different case
186
+ derivedNotes: [],
187
+ failureNotes: [],
188
+ lessonNotes: [],
189
+ createdAt: 1,
190
+ updatedAt: 1,
191
+ } satisfies Partial<RunReflection> as RunReflection
192
+
193
+ const ctx = foldReflectionIntoRunContext(existing, reflection)
194
+ assert.equal(ctx.keyFacts.length, 1)
195
+ assert.equal(ctx.keyFacts[0], 'Already known fact') // keeps original casing
196
+ })
197
+
198
+ test('foldReflectionIntoRunContext increments version', () => {
199
+ const existing = ensureRunContext(null)
200
+ existing.version = 4
201
+
202
+ const reflection = {
203
+ id: 'r3',
204
+ runId: 'run3',
205
+ sessionId: 's3',
206
+ source: 'test',
207
+ status: 'completed' as const,
208
+ summary: 'Version test',
209
+ invariantNotes: [],
210
+ derivedNotes: [],
211
+ failureNotes: [],
212
+ lessonNotes: [],
213
+ createdAt: 1,
214
+ updatedAt: 1,
215
+ } satisfies Partial<RunReflection> as RunReflection
216
+
217
+ const ctx = foldReflectionIntoRunContext(existing, reflection)
218
+ assert.equal(ctx.version, 5)
219
+ })
220
+
221
+ // ---------------------------------------------------------------------------
222
+ // buildRunContextSection
223
+ // ---------------------------------------------------------------------------
224
+
225
+ test('buildRunContextSection returns null for null context', () => {
226
+ assert.equal(buildRunContextSection(null, false), null)
227
+ })
228
+
229
+ test('buildRunContextSection returns null for minimal prompt', () => {
230
+ const ctx = ensureRunContext(null)
231
+ ctx.keyFacts = ['Something important']
232
+ assert.equal(buildRunContextSection(ctx, true), null)
233
+ })
234
+
235
+ test('buildRunContextSection returns null for empty context', () => {
236
+ const ctx = ensureRunContext(null)
237
+ assert.equal(buildRunContextSection(ctx, false), null)
238
+ })
239
+
240
+ test('buildRunContextSection renders all non-empty fields', () => {
241
+ const ctx: RunContext = {
242
+ objective: 'Fix the pipeline',
243
+ constraints: ['No downtime'],
244
+ keyFacts: ['Build passes locally'],
245
+ discoveries: ['Staging uses different auth'],
246
+ failedApproaches: ['Restart did not help'],
247
+ currentPlan: ['Investigate auth', 'Deploy fix'],
248
+ completedSteps: ['Investigate auth'],
249
+ blockers: ['Waiting on credentials'],
250
+ parentContext: 'Coordinator wants a contained fix',
251
+ updatedAt: 1,
252
+ version: 3,
253
+ }
254
+
255
+ const section = buildRunContextSection(ctx, false)
256
+ assert.ok(section)
257
+ assert.match(section, /Working Memory \(RunContext\)/)
258
+ assert.match(section, /Coordinator Context/)
259
+ assert.match(section, /Coordinator wants a contained fix/)
260
+ assert.match(section, /Current Objective/)
261
+ assert.match(section, /Fix the pipeline/)
262
+ assert.match(section, /Constraints/)
263
+ assert.match(section, /No downtime/)
264
+ assert.match(section, /Key Facts/)
265
+ assert.match(section, /Build passes locally/)
266
+ assert.match(section, /Already Tried \(Failed\)/)
267
+ assert.match(section, /Restart did not help/)
268
+ assert.match(section, /Current Plan/)
269
+ assert.match(section, /Blockers/)
270
+ assert.match(section, /Waiting on credentials/)
271
+ assert.match(section, /Discoveries/)
272
+ assert.match(section, /Staging uses different auth/)
273
+ })
274
+
275
+ test('buildRunContextSection renders plan with checkboxes for completed steps', () => {
276
+ const ctx: RunContext = {
277
+ objective: null,
278
+ constraints: [],
279
+ keyFacts: [],
280
+ discoveries: [],
281
+ failedApproaches: [],
282
+ currentPlan: ['Step A', 'Step B', 'Step C'],
283
+ completedSteps: ['step a', 'Step C'], // case-insensitive match
284
+ blockers: [],
285
+ parentContext: null,
286
+ updatedAt: 1,
287
+ version: 1,
288
+ }
289
+
290
+ const section = buildRunContextSection(ctx, false)
291
+ assert.ok(section)
292
+ assert.match(section, /\[x\] Step A/)
293
+ assert.match(section, /\[ \] Step B/)
294
+ assert.match(section, /\[x\] Step C/)
295
+ })
296
+
297
+ test('buildRunContextSection respects budget cap', () => {
298
+ const ctx: RunContext = {
299
+ objective: 'A'.repeat(2000),
300
+ constraints: ['B'.repeat(2000)],
301
+ keyFacts: ['C'.repeat(2000)],
302
+ discoveries: [],
303
+ failedApproaches: [],
304
+ currentPlan: [],
305
+ completedSteps: [],
306
+ blockers: [],
307
+ parentContext: null,
308
+ updatedAt: 1,
309
+ version: 1,
310
+ }
311
+
312
+ const section = buildRunContextSection(ctx, false)
313
+ assert.ok(section)
314
+ // The section should exist but be bounded. At 3000 char budget,
315
+ // not all fields can fit with 2000-char values.
316
+ assert.ok(section.length < 4000) // header + budget
317
+ })
318
+
319
+ // ---------------------------------------------------------------------------
320
+ // extractFactsFromMessages
321
+ // ---------------------------------------------------------------------------
322
+
323
+ test('extractFactsFromMessages extracts facts matching keyword patterns', () => {
324
+ const messages = [
325
+ { text: 'I discovered that the API key must always be rotated monthly for compliance reasons.' },
326
+ { text: 'Note: the staging environment uses a separate auth service from production.' },
327
+ ] as Message[]
328
+
329
+ const result = extractFactsFromMessages(messages)
330
+ assert.ok(result.keyFacts.length > 0)
331
+ })
332
+
333
+ test('extractFactsFromMessages categorizes error patterns as failedApproaches', () => {
334
+ const messages = [
335
+ { text: "The migration failed: the schema was incompatible with the target version due to column ordering." },
336
+ ] as Message[]
337
+
338
+ const result = extractFactsFromMessages(messages)
339
+ assert.ok(result.failedApproaches.length > 0)
340
+ assert.ok(result.failedApproaches.some((f) => /schema/i.test(f)))
341
+ })
342
+
343
+ test('extractFactsFromMessages deduplicates results', () => {
344
+ const messages = [
345
+ { text: 'Important: always validate the input before processing the request payload.' },
346
+ { text: 'important: always validate the input before processing the request payload.' },
347
+ ] as Message[]
348
+
349
+ const result = extractFactsFromMessages(messages)
350
+ // Same fact stated twice (different case) should be deduped
351
+ const matching = result.keyFacts.filter((f) => /validate the input/i.test(f))
352
+ assert.ok(matching.length <= 1)
353
+ })
354
+
355
+ test('extractFactsFromMessages ignores short messages', () => {
356
+ const messages = [
357
+ { text: 'OK' },
358
+ { text: 'Sure, noted.' },
359
+ { text: '' },
360
+ ] as Message[]
361
+
362
+ const result = extractFactsFromMessages(messages)
363
+ assert.equal(result.keyFacts.length, 0)
364
+ assert.equal(result.failedApproaches.length, 0)
365
+ })