@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
@@ -0,0 +1,167 @@
1
+ import assert from 'node:assert/strict'
2
+ import { test } from 'node:test'
3
+
4
+ import { buildExecutionBrief, buildExecutionBriefContextBlock, serializeExecutionBriefForDelegation } from './execution-brief'
5
+ import type { Mission, Session, SessionWorkingState } from '@/types'
6
+
7
+ test('buildExecutionBrief prefers working state and folds in mission and run-context fallback data', () => {
8
+ const session = {
9
+ id: 's1',
10
+ name: 'Main Session',
11
+ cwd: '/tmp/project',
12
+ user: 'tester',
13
+ provider: 'openai',
14
+ model: 'gpt-test',
15
+ claudeSessionId: null,
16
+ messages: [],
17
+ createdAt: 1,
18
+ lastActiveAt: 1,
19
+ runContext: {
20
+ objective: 'Fallback objective',
21
+ constraints: ['Do not change the API'],
22
+ keyFacts: ['The build already passes locally.'],
23
+ discoveries: ['The failing path is only used in staging.'],
24
+ failedApproaches: ['Restarting the worker did not help.'],
25
+ currentPlan: ['Fallback step'],
26
+ completedSteps: [],
27
+ blockers: ['Waiting on staging credentials.'],
28
+ parentContext: 'Parent asked for a contained fix.',
29
+ updatedAt: 1,
30
+ version: 1,
31
+ },
32
+ } satisfies Partial<Session> as Session
33
+
34
+ const mission = {
35
+ id: 'm1',
36
+ source: 'chat',
37
+ objective: 'Ship the release fix',
38
+ status: 'active',
39
+ phase: 'executing',
40
+ currentStep: 'Verify staging auth',
41
+ successCriteria: ['Restore staging deploys'],
42
+ waitState: {
43
+ kind: 'approval',
44
+ reason: 'Waiting for deploy approval.',
45
+ },
46
+ createdAt: 1,
47
+ updatedAt: 1,
48
+ } satisfies Partial<Mission> as Mission
49
+
50
+ const workingState = {
51
+ sessionId: 's1',
52
+ missionId: 'm1',
53
+ objective: 'Ship the release fix safely',
54
+ summary: 'Auth mismatch isolated to staging.',
55
+ constraints: ['Do not change the API'],
56
+ successCriteria: ['Restore staging deploys'],
57
+ status: 'progress',
58
+ nextAction: 'Request deploy approval',
59
+ planSteps: [
60
+ { id: 'p1', text: 'Request deploy approval', status: 'active', createdAt: 1, updatedAt: 1 },
61
+ { id: 'p2', text: 'Roll staging credentials', status: 'resolved', createdAt: 1, updatedAt: 1 },
62
+ ],
63
+ confirmedFacts: [
64
+ { id: 'f1', statement: 'Auth mismatch isolated to staging.', source: 'tool', status: 'active', createdAt: 1, updatedAt: 1 },
65
+ ],
66
+ artifacts: [
67
+ { id: 'a1', label: 'deploy.log', kind: 'file', path: '/tmp/project/deploy.log', status: 'active', createdAt: 1, updatedAt: 1 },
68
+ ],
69
+ decisions: [],
70
+ blockers: [
71
+ { id: 'b1', summary: 'Deploy approval is pending.', kind: 'approval', nextAction: 'Request deploy approval', status: 'active', createdAt: 1, updatedAt: 1 },
72
+ ],
73
+ openQuestions: [],
74
+ hypotheses: [],
75
+ evidenceRefs: [
76
+ { id: 'e1', type: 'tool', summary: 'Checked deploy logs', value: '403 from staging auth', sessionId: 's1', createdAt: 1 },
77
+ ],
78
+ createdAt: 1,
79
+ updatedAt: 1,
80
+ } satisfies SessionWorkingState
81
+
82
+ const brief = buildExecutionBrief({ session, mission, workingState })
83
+
84
+ assert.equal(brief.objective, 'Ship the release fix safely')
85
+ assert.equal(brief.summary, 'Auth mismatch isolated to staging.')
86
+ assert.equal(brief.status, 'progress')
87
+ assert.equal(brief.nextAction, 'Request deploy approval')
88
+ assert.equal(brief.plan[0]?.text, 'Request deploy approval')
89
+ assert.equal(brief.blockers[0], 'Deploy approval is pending. | next: Request deploy approval')
90
+ assert.ok(brief.blockers.some((entry) => /waiting for deploy approval/i.test(entry)))
91
+ assert.ok(brief.facts.some((entry) => /auth mismatch isolated to staging/i.test(entry)))
92
+ assert.ok(brief.artifacts.some((entry) => /deploy\.log/i.test(entry)))
93
+ assert.equal(brief.parentContext, 'Parent asked for a contained fix.')
94
+ assert.equal(brief.missionPhase, 'executing')
95
+ })
96
+
97
+ test('buildExecutionBriefContextBlock renders a single canonical state block', () => {
98
+ const brief = buildExecutionBrief({
99
+ session: {
100
+ id: 's2',
101
+ name: 'Session',
102
+ cwd: '/tmp',
103
+ user: 'tester',
104
+ provider: 'openai',
105
+ model: 'gpt-test',
106
+ claudeSessionId: null,
107
+ messages: [],
108
+ createdAt: 1,
109
+ lastActiveAt: 1,
110
+ } satisfies Partial<Session> as Session,
111
+ workingState: {
112
+ sessionId: 's2',
113
+ objective: 'Finish the rollout',
114
+ summary: 'Everything is ready except final verification.',
115
+ constraints: ['No schema changes'],
116
+ successCriteria: ['Verify production traffic'],
117
+ status: 'progress',
118
+ nextAction: 'Run the final smoke test',
119
+ planSteps: [{ id: 'p1', text: 'Run the final smoke test', status: 'active', createdAt: 1, updatedAt: 1 }],
120
+ confirmedFacts: [{ id: 'f1', statement: 'Staging already passed.', source: 'tool', status: 'active', createdAt: 1, updatedAt: 1 }],
121
+ artifacts: [],
122
+ decisions: [],
123
+ blockers: [],
124
+ openQuestions: [],
125
+ hypotheses: [],
126
+ evidenceRefs: [],
127
+ createdAt: 1,
128
+ updatedAt: 1,
129
+ },
130
+ })
131
+
132
+ const block = buildExecutionBriefContextBlock(brief)
133
+ assert.match(block, /## Execution Brief/)
134
+ assert.match(block, /Objective: Finish the rollout/)
135
+ assert.match(block, /Next action: Run the final smoke test/)
136
+ assert.match(block, /Staging already passed\./)
137
+ })
138
+
139
+ test('serializeExecutionBriefForDelegation creates a bounded handoff summary', () => {
140
+ const text = serializeExecutionBriefForDelegation({
141
+ sessionId: 's3',
142
+ missionId: 'm3',
143
+ objective: 'Repair the deployment pipeline',
144
+ summary: 'The regression is isolated to the release job.',
145
+ status: 'blocked',
146
+ nextAction: 'Fix the release job',
147
+ plan: [
148
+ { text: 'Fix the release job', status: 'active' },
149
+ { text: 'Verify deploy output', status: 'resolved' },
150
+ ],
151
+ blockers: ['Approval required before production deploy.'],
152
+ facts: ['The build already passes locally.'],
153
+ artifacts: ['/tmp/project/release.log'],
154
+ constraints: ['Keep the current release shape.'],
155
+ successCriteria: ['Production deploy completes'],
156
+ missionStatus: 'active',
157
+ missionPhase: 'executing',
158
+ waitState: null,
159
+ evidenceRefs: [],
160
+ parentContext: 'Parent is waiting on a concise status update.',
161
+ })
162
+
163
+ assert.ok(text)
164
+ assert.match(String(text), /Objective: Repair the deployment pipeline/)
165
+ assert.match(String(text), /Blockers: Approval required before production deploy\./)
166
+ assert.match(String(text), /Parent context: Parent is waiting on a concise status update\./)
167
+ })
@@ -0,0 +1,295 @@
1
+ import type {
2
+ EvidenceRef,
3
+ ExecutionBrief,
4
+ ExecutionBriefPlanStep,
5
+ Mission,
6
+ Session,
7
+ SessionWorkingState,
8
+ WorkingPlanStep,
9
+ WorkingStateItemStatus,
10
+ WorkingStateStatus,
11
+ } from '@/types'
12
+ import { getSession } from '@/lib/server/sessions/session-repository'
13
+ import { loadSessionWorkingState } from '@/lib/server/working-state/service'
14
+ import { ensureRunContext } from '@/lib/server/run-context'
15
+ import { cleanText, cleanMultiline } from '@/lib/server/text-normalization'
16
+
17
+ const MAX_PLAN_ITEMS = 8
18
+ const MAX_FACTS = 8
19
+ const MAX_BLOCKERS = 6
20
+ const MAX_ARTIFACTS = 6
21
+ const MAX_EVIDENCE = 6
22
+ const MAX_DELEGATION_PLAN_ITEMS = 4
23
+ const MAX_DELEGATION_FACTS = 4
24
+ const MAX_DELEGATION_BLOCKERS = 4
25
+ const MAX_DELEGATION_ARTIFACTS = 4
26
+ const DELEGATION_BUDGET = 1_200
27
+
28
+ function uniqueStrings(values: Array<unknown>, maxItems: number, maxChars = 240): string[] {
29
+ const out: string[] = []
30
+ const seen = new Set<string>()
31
+ for (const value of values) {
32
+ const normalized = cleanText(value, maxChars)
33
+ if (!normalized) continue
34
+ const key = normalized.toLowerCase()
35
+ if (seen.has(key)) continue
36
+ seen.add(key)
37
+ out.push(normalized)
38
+ if (out.length >= maxItems) break
39
+ }
40
+ return out
41
+ }
42
+
43
+ function summarizeArtifact(value: unknown): string {
44
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return ''
45
+ const artifact = value as Record<string, unknown>
46
+ return cleanText(artifact.path || artifact.url || artifact.label, 220)
47
+ }
48
+
49
+ function summarizeEvidenceRef(ref: EvidenceRef): string {
50
+ const summary = cleanText(ref.summary, 180)
51
+ if (!summary) return ''
52
+ const value = cleanText(ref.value, 140)
53
+ return value ? `[${ref.type}] ${summary}: ${value}` : `[${ref.type}] ${summary}`
54
+ }
55
+
56
+ function planStatus(step: WorkingPlanStep): WorkingStateItemStatus {
57
+ return step.status === 'resolved' || step.status === 'superseded' ? step.status : 'active'
58
+ }
59
+
60
+ function dedupePlan(steps: ExecutionBriefPlanStep[]): ExecutionBriefPlanStep[] {
61
+ const out: ExecutionBriefPlanStep[] = []
62
+ const seen = new Set<string>()
63
+ for (const step of steps) {
64
+ const text = cleanText(step.text, 240)
65
+ if (!text) continue
66
+ const key = text.toLowerCase()
67
+ if (seen.has(key)) continue
68
+ seen.add(key)
69
+ out.push({
70
+ text,
71
+ status: step.status === 'resolved' || step.status === 'superseded' ? step.status : 'active',
72
+ })
73
+ if (out.length >= MAX_PLAN_ITEMS) break
74
+ }
75
+ return out
76
+ }
77
+
78
+ function inferStatus(mission: Mission | null | undefined, workingState: SessionWorkingState | null): WorkingStateStatus {
79
+ if (workingState?.status) return workingState.status
80
+ if (!mission) return 'idle'
81
+ if (mission.status === 'completed') return 'completed'
82
+ if (mission.status === 'waiting') return 'waiting'
83
+ if (mission.status === 'failed' || mission.phase === 'failed') return 'blocked'
84
+ if (mission.waitState) return 'waiting'
85
+ if (mission.phase === 'executing' || mission.phase === 'dispatching' || mission.phase === 'verifying') return 'progress'
86
+ return 'idle'
87
+ }
88
+
89
+ function buildPlan(workingState: SessionWorkingState | null, session: Session | null): ExecutionBriefPlanStep[] {
90
+ if (workingState && Array.isArray(workingState.planSteps) && workingState.planSteps.length > 0) {
91
+ return dedupePlan(
92
+ workingState.planSteps
93
+ .filter((step) => step.status !== 'superseded')
94
+ .map((step) => ({
95
+ text: cleanText(step.text, 240),
96
+ status: planStatus(step),
97
+ })),
98
+ )
99
+ }
100
+ const runContext = session?.runContext ? ensureRunContext(session.runContext) : null
101
+ if (!runContext || !Array.isArray(runContext.currentPlan) || runContext.currentPlan.length === 0) return []
102
+ const completed = new Set((runContext.completedSteps || []).map((value) => cleanText(value, 240).toLowerCase()).filter(Boolean))
103
+ return dedupePlan(
104
+ runContext.currentPlan.map((step) => {
105
+ const text = cleanText(step, 240)
106
+ return {
107
+ text,
108
+ status: completed.has(text.toLowerCase()) ? 'resolved' : 'active',
109
+ }
110
+ }),
111
+ )
112
+ }
113
+
114
+ function buildFacts(workingState: SessionWorkingState | null, session: Session | null): string[] {
115
+ const activeFacts = workingState
116
+ ? workingState.confirmedFacts
117
+ .filter((fact) => fact.status === 'active')
118
+ .map((fact) => fact.statement)
119
+ : []
120
+ const runContextFacts = session?.runContext ? ensureRunContext(session.runContext).keyFacts : []
121
+ return uniqueStrings([...activeFacts, ...runContextFacts], MAX_FACTS, 240)
122
+ }
123
+
124
+ function buildBlockers(workingState: SessionWorkingState | null, mission: Mission | null | undefined, session: Session | null): string[] {
125
+ const activeBlockers = workingState
126
+ ? workingState.blockers
127
+ .filter((blocker) => blocker.status === 'active')
128
+ .map((blocker) => blocker.nextAction ? `${blocker.summary} | next: ${blocker.nextAction}` : blocker.summary)
129
+ : []
130
+ const missionBlockers = mission?.waitState?.reason ? [cleanText(mission.waitState.reason, 280)] : []
131
+ const runContextBlockers = session?.runContext ? ensureRunContext(session.runContext).blockers : []
132
+ return uniqueStrings([...activeBlockers, ...missionBlockers, ...runContextBlockers], MAX_BLOCKERS, 280)
133
+ }
134
+
135
+ function buildArtifacts(workingState: SessionWorkingState | null): string[] {
136
+ const artifacts = workingState
137
+ ? workingState.artifacts
138
+ .filter((artifact) => artifact.status === 'active')
139
+ .map((artifact) => summarizeArtifact(artifact))
140
+ : []
141
+ return uniqueStrings(artifacts, MAX_ARTIFACTS, 220)
142
+ }
143
+
144
+ function buildConstraints(workingState: SessionWorkingState | null, session: Session | null): string[] {
145
+ const workingConstraints = workingState?.constraints || []
146
+ const runContext = session?.runContext ? ensureRunContext(session.runContext) : null
147
+ return uniqueStrings([...(workingConstraints || []), ...(runContext?.constraints || [])], 10, 220)
148
+ }
149
+
150
+ function buildSuccessCriteria(workingState: SessionWorkingState | null, mission: Mission | null | undefined): string[] {
151
+ return uniqueStrings([...(workingState?.successCriteria || []), ...(mission?.successCriteria || [])], 10, 220)
152
+ }
153
+
154
+ function buildEvidenceRefs(workingState: SessionWorkingState | null): EvidenceRef[] {
155
+ if (!workingState || !Array.isArray(workingState.evidenceRefs) || workingState.evidenceRefs.length === 0) return []
156
+ return [...workingState.evidenceRefs]
157
+ .filter((ref) => Boolean(cleanText(ref.summary, 180)))
158
+ .slice(-MAX_EVIDENCE)
159
+ }
160
+
161
+ export function buildExecutionBrief(params: {
162
+ sessionId?: string | null
163
+ session?: Session | null
164
+ mission?: Mission | null
165
+ workingState?: SessionWorkingState | null
166
+ }): ExecutionBrief {
167
+ const session = params.session
168
+ || (params.sessionId ? getSession(params.sessionId) || null : null)
169
+ const mission = params.mission || null
170
+ const workingState = params.workingState
171
+ || (session?.id ? loadSessionWorkingState(session.id, { mission }) : null)
172
+ const runContext = session?.runContext ? ensureRunContext(session.runContext) : null
173
+ const plan = buildPlan(workingState, session)
174
+ const nextAction = cleanText(
175
+ workingState?.nextAction
176
+ || mission?.currentStep
177
+ || plan.find((step) => step.status === 'active')?.text,
178
+ 240,
179
+ ) || null
180
+
181
+ return {
182
+ sessionId: session?.id || params.sessionId || null,
183
+ missionId: mission?.id || workingState?.missionId || null,
184
+ objective: cleanMultiline(
185
+ workingState?.objective
186
+ || mission?.objective
187
+ || runContext?.objective,
188
+ 900,
189
+ ) || null,
190
+ summary: cleanMultiline(
191
+ workingState?.summary
192
+ || mission?.verifierSummary
193
+ || mission?.plannerSummary,
194
+ 700,
195
+ ) || null,
196
+ status: inferStatus(mission, workingState),
197
+ nextAction,
198
+ plan,
199
+ blockers: buildBlockers(workingState, mission, session),
200
+ facts: buildFacts(workingState, session),
201
+ artifacts: buildArtifacts(workingState),
202
+ constraints: buildConstraints(workingState, session),
203
+ successCriteria: buildSuccessCriteria(workingState, mission),
204
+ missionStatus: mission?.status || null,
205
+ missionPhase: mission?.phase || null,
206
+ waitState: mission?.waitState || null,
207
+ evidenceRefs: buildEvidenceRefs(workingState),
208
+ parentContext: cleanMultiline(runContext?.parentContext, 900) || null,
209
+ }
210
+ }
211
+
212
+ function buildListSection(title: string, values: string[]): string | null {
213
+ if (!values.length) return null
214
+ return [title, ...values.map((value) => `- ${value}`)].join('\n')
215
+ }
216
+
217
+ function formatPlan(plan: ExecutionBriefPlanStep[]): string | null {
218
+ if (!plan.length) return null
219
+ return [
220
+ 'Plan',
221
+ ...plan.map((step) => `- [${step.status === 'resolved' ? 'x' : ' '}] ${step.text}`),
222
+ ].join('\n')
223
+ }
224
+
225
+ export function buildExecutionBriefContextBlock(
226
+ brief: ExecutionBrief | null | undefined,
227
+ options?: { title?: string },
228
+ ): string {
229
+ if (!brief) return ''
230
+ const hasContent = Boolean(
231
+ brief.parentContext
232
+ || brief.objective
233
+ || brief.summary
234
+ || brief.nextAction
235
+ || brief.plan.length > 0
236
+ || brief.blockers.length > 0
237
+ || brief.facts.length > 0
238
+ || brief.artifacts.length > 0
239
+ || brief.constraints.length > 0
240
+ || brief.successCriteria.length > 0
241
+ || brief.evidenceRefs.length > 0
242
+ || brief.missionStatus
243
+ || brief.missionPhase
244
+ || brief.waitState?.reason,
245
+ )
246
+ if (!hasContent && brief.status === 'idle') return ''
247
+ const sections = [
248
+ options?.title || '## Execution Brief',
249
+ brief.parentContext ? `Parent context:\n${brief.parentContext}` : '',
250
+ brief.objective ? `Objective: ${brief.objective}` : '',
251
+ brief.summary ? `Summary: ${brief.summary}` : '',
252
+ `Status: ${brief.status}`,
253
+ brief.missionStatus ? `Mission status: ${brief.missionStatus}` : '',
254
+ brief.missionPhase ? `Mission phase: ${brief.missionPhase}` : '',
255
+ brief.waitState?.reason ? `Waiting reason: ${cleanText(brief.waitState.reason, 280)}` : '',
256
+ brief.nextAction ? `Next action: ${brief.nextAction}` : '',
257
+ brief.successCriteria.length > 0 ? `Success criteria: ${brief.successCriteria.join(' | ')}` : '',
258
+ brief.constraints.length > 0 ? `Constraints: ${brief.constraints.join(' | ')}` : '',
259
+ formatPlan(brief.plan),
260
+ buildListSection('Blockers', brief.blockers),
261
+ buildListSection('Facts', brief.facts),
262
+ buildListSection('Artifacts', brief.artifacts),
263
+ buildListSection('Evidence', brief.evidenceRefs.map((ref) => summarizeEvidenceRef(ref)).filter(Boolean)),
264
+ 'Trust this execution brief before reconstructing state from the raw transcript or older assistant text.',
265
+ ].filter(Boolean)
266
+ return sections.join('\n')
267
+ }
268
+
269
+ export function serializeExecutionBriefForDelegation(
270
+ brief: ExecutionBrief | null | undefined,
271
+ ): string | null {
272
+ if (!brief) return null
273
+ const parts: string[] = []
274
+ let budget = DELEGATION_BUDGET
275
+
276
+ const append = (line: string): void => {
277
+ if (!line) return
278
+ if (budget - line.length - 1 < 0) return
279
+ parts.push(line)
280
+ budget -= line.length + 1
281
+ }
282
+
283
+ append(brief.objective ? `Objective: ${brief.objective}` : '')
284
+ append(brief.summary ? `Summary: ${cleanText(brief.summary, 280)}` : '')
285
+ append(`Status: ${brief.status}`)
286
+ append(brief.nextAction ? `Next action: ${brief.nextAction}` : '')
287
+ append(brief.successCriteria.length > 0 ? `Success criteria: ${brief.successCriteria.slice(0, 4).join('; ')}` : '')
288
+ append(brief.constraints.length > 0 ? `Constraints: ${brief.constraints.slice(0, 4).join('; ')}` : '')
289
+ append(brief.plan.length > 0 ? `Plan: ${brief.plan.slice(0, MAX_DELEGATION_PLAN_ITEMS).map((step) => step.text).join('; ')}` : '')
290
+ append(brief.blockers.length > 0 ? `Blockers: ${brief.blockers.slice(0, MAX_DELEGATION_BLOCKERS).join('; ')}` : '')
291
+ append(brief.facts.length > 0 ? `Facts: ${brief.facts.slice(0, MAX_DELEGATION_FACTS).join('; ')}` : '')
292
+ append(brief.artifacts.length > 0 ? `Artifacts: ${brief.artifacts.slice(0, MAX_DELEGATION_ARTIFACTS).join('; ')}` : '')
293
+ append(brief.parentContext ? `Parent context: ${cleanText(brief.parentContext, 280)}` : '')
294
+ return parts.length > 0 ? parts.join('\n') : null
295
+ }
@@ -0,0 +1,9 @@
1
+ import { executeSessionChatTurn } from '@/lib/server/chat-execution/chat-execution'
2
+ import type {
3
+ ExecuteChatTurnInput,
4
+ ExecuteChatTurnResult,
5
+ } from '@/lib/server/chat-execution/chat-execution-types'
6
+
7
+ export function executeExecutionChatTurn(input: ExecuteChatTurnInput): Promise<ExecuteChatTurnResult> {
8
+ return executeSessionChatTurn(input)
9
+ }
@@ -0,0 +1,44 @@
1
+ import assert from 'node:assert/strict'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { describe, it } from 'node:test'
5
+
6
+ const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '../../../..')
7
+ const srcRoot = path.join(repoRoot, 'src')
8
+
9
+ const ALLOWED_IMPORTERS = new Set([
10
+ path.join(srcRoot, 'lib/server/chat-execution/chat-execution.ts'),
11
+ path.join(srcRoot, 'lib/server/execution-engine/chat-turn.ts'),
12
+ ])
13
+
14
+ function walk(dir: string): string[] {
15
+ const results: string[] = []
16
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
17
+ const fullPath = path.join(dir, entry.name)
18
+ if (entry.isDirectory()) {
19
+ results.push(...walk(fullPath))
20
+ continue
21
+ }
22
+ if (!/\.(?:ts|tsx|js|mjs)$/.test(entry.name)) continue
23
+ if (entry.name.includes('.test.')) continue
24
+ results.push(fullPath)
25
+ }
26
+ return results
27
+ }
28
+
29
+ describe('executeSessionChatTurn import boundary', () => {
30
+ it('is only imported through the execution-engine chat-turn wrapper', () => {
31
+ const offenders: string[] = []
32
+ const importPattern = /import\s*\{[^}]*\bexecuteSessionChatTurn\b[^}]*\}\s*from\s*['"][^'"]+['"]/m
33
+
34
+ for (const filePath of walk(srcRoot)) {
35
+ if (ALLOWED_IMPORTERS.has(filePath)) continue
36
+ const contents = fs.readFileSync(filePath, 'utf8')
37
+ if (importPattern.test(contents)) {
38
+ offenders.push(path.relative(repoRoot, filePath))
39
+ }
40
+ }
41
+
42
+ assert.deepEqual(offenders, [])
43
+ })
44
+ })
@@ -0,0 +1,35 @@
1
+ import { enqueueSessionRun } from '@/lib/server/runtime/session-run-manager'
2
+ import type { ExecuteChatTurnResult } from '@/lib/server/chat-execution/chat-execution-types'
3
+ import type {
4
+ EnqueueExecutionRequest,
5
+ ExecutionHandle,
6
+ } from '@/lib/server/execution-engine/types'
7
+ import { enqueueTaskAttemptExecution } from '@/lib/server/execution-engine/task-attempt'
8
+
9
+ export function enqueueExecution(
10
+ input: EnqueueExecutionRequest,
11
+ ): ExecutionHandle<ExecuteChatTurnResult> {
12
+ if (input.kind === 'session_turn') {
13
+ const result = enqueueSessionRun(input.input)
14
+ return {
15
+ executionId: result.runId,
16
+ promise: result.promise,
17
+ abort: result.abort,
18
+ position: result.position,
19
+ deduped: result.deduped,
20
+ coalesced: result.coalesced,
21
+ unsubscribe: result.unsubscribe,
22
+ }
23
+ }
24
+
25
+ return enqueueTaskAttemptExecution(input)
26
+ }
27
+
28
+ export { executeExecutionChatTurn } from '@/lib/server/execution-engine/chat-turn'
29
+ export type {
30
+ EnqueueExecutionRequest,
31
+ EnqueueTaskAttemptExecutionRequest,
32
+ EnqueueSessionTurnExecutionRequest,
33
+ ExecutionHandle,
34
+ ExecutionResult,
35
+ } from '@/lib/server/execution-engine/types'