@swarmclawai/swarmclaw 1.2.3 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/README.md +20 -0
  2. package/bin/daemon-cmd.js +169 -0
  3. package/bin/server-cmd.js +3 -0
  4. package/bin/swarmclaw.js +11 -0
  5. package/package.json +17 -16
  6. package/src/app/api/agents/[id]/clone/route.ts +3 -32
  7. package/src/app/api/agents/[id]/route.ts +6 -158
  8. package/src/app/api/agents/[id]/status/route.ts +2 -3
  9. package/src/app/api/agents/[id]/thread/route.ts +4 -17
  10. package/src/app/api/agents/bulk/route.ts +5 -47
  11. package/src/app/api/agents/route.ts +5 -119
  12. package/src/app/api/agents/trash/route.ts +13 -24
  13. package/src/app/api/auth/route.ts +3 -9
  14. package/src/app/api/autonomy/estop/route.ts +5 -5
  15. package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
  16. package/src/app/api/chatrooms/[id]/route.ts +23 -2
  17. package/src/app/api/chatrooms/route.ts +13 -2
  18. package/src/app/api/chats/[id]/clear/route.ts +2 -13
  19. package/src/app/api/chats/[id]/deploy/route.ts +2 -3
  20. package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
  21. package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
  22. package/src/app/api/chats/[id]/queue/route.ts +17 -64
  23. package/src/app/api/chats/[id]/retry/route.ts +4 -22
  24. package/src/app/api/chats/[id]/route.ts +10 -138
  25. package/src/app/api/chats/heartbeat/route.ts +2 -1
  26. package/src/app/api/chats/migrate-messages/route.ts +7 -0
  27. package/src/app/api/chats/route.ts +13 -134
  28. package/src/app/api/connectors/[id]/access/route.ts +12 -229
  29. package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
  30. package/src/app/api/connectors/[id]/health/route.ts +12 -39
  31. package/src/app/api/connectors/[id]/route.ts +14 -122
  32. package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
  33. package/src/app/api/connectors/doctor/route.ts +1 -1
  34. package/src/app/api/connectors/route.ts +12 -70
  35. package/src/app/api/credentials/[id]/route.ts +2 -4
  36. package/src/app/api/credentials/route.ts +10 -19
  37. package/src/app/api/daemon/health-check/route.ts +3 -4
  38. package/src/app/api/daemon/route.ts +10 -8
  39. package/src/app/api/documents/route.ts +11 -10
  40. package/src/app/api/external-agents/route.ts +3 -3
  41. package/src/app/api/gateways/[id]/health/route.ts +2 -3
  42. package/src/app/api/gateways/[id]/route.ts +7 -122
  43. package/src/app/api/gateways/route.ts +3 -103
  44. package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
  45. package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
  46. package/src/app/api/openclaw/directory/route.ts +2 -2
  47. package/src/app/api/openclaw/history/route.ts +3 -5
  48. package/src/app/api/providers/[id]/models/route.test.ts +60 -0
  49. package/src/app/api/providers/[id]/models/route.ts +33 -1
  50. package/src/app/api/providers/[id]/route.test.ts +49 -0
  51. package/src/app/api/providers/[id]/route.ts +30 -1
  52. package/src/app/api/providers/ollama/route.ts +6 -5
  53. package/src/app/api/schedules/[id]/route.ts +14 -108
  54. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  55. package/src/app/api/schedules/route.ts +9 -51
  56. package/src/app/api/settings/route.ts +4 -3
  57. package/src/app/api/setup/check-provider/route.ts +15 -1
  58. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  59. package/src/app/api/system/status/route.ts +2 -2
  60. package/src/app/api/tasks/[id]/route.ts +16 -202
  61. package/src/app/api/tasks/bulk/route.ts +5 -86
  62. package/src/app/api/tasks/metrics/route.ts +2 -1
  63. package/src/app/api/tasks/route.ts +11 -171
  64. package/src/app/api/upload/route.ts +1 -1
  65. package/src/app/api/uploads/[filename]/route.ts +1 -1
  66. package/src/app/api/uploads/route.ts +1 -1
  67. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  68. package/src/app/layout.tsx +9 -6
  69. package/src/app/protocols/page.tsx +71 -89
  70. package/src/app/tasks/page.tsx +32 -32
  71. package/src/cli/index.js +1 -0
  72. package/src/cli/spec.js +1 -0
  73. package/src/components/agents/agent-sheet.tsx +51 -25
  74. package/src/components/agents/inspector-panel.tsx +15 -4
  75. package/src/components/auth/setup-wizard/index.tsx +27 -18
  76. package/src/components/auth/setup-wizard/shared.tsx +2 -2
  77. package/src/components/auth/setup-wizard/step-agents.tsx +51 -38
  78. package/src/components/auth/setup-wizard/step-connect.tsx +48 -17
  79. package/src/components/auth/setup-wizard/types.ts +6 -4
  80. package/src/components/auth/setup-wizard/utils.test.ts +38 -8
  81. package/src/components/auth/setup-wizard/utils.ts +14 -8
  82. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  83. package/src/components/connectors/connector-list.tsx +26 -40
  84. package/src/components/connectors/connector-sheet.tsx +95 -149
  85. package/src/components/gateways/gateway-sheet.tsx +61 -110
  86. package/src/components/layout/live-query-sync.tsx +121 -0
  87. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  88. package/src/components/providers/app-query-provider.tsx +17 -0
  89. package/src/components/providers/provider-list.tsx +150 -77
  90. package/src/components/providers/provider-sheet.tsx +102 -77
  91. package/src/components/shared/model-combobox.tsx +5 -4
  92. package/src/components/skills/skill-list.tsx +5 -18
  93. package/src/components/skills/skill-sheet.tsx +21 -20
  94. package/src/components/skills/skills-workspace.tsx +48 -87
  95. package/src/components/tasks/task-card.tsx +20 -13
  96. package/src/components/tasks/task-column.tsx +22 -7
  97. package/src/components/tasks/task-list.tsx +8 -11
  98. package/src/components/tasks/task-sheet.tsx +111 -103
  99. package/src/features/agents/queries.ts +20 -0
  100. package/src/features/chatrooms/queries.ts +20 -0
  101. package/src/features/chats/queries.ts +27 -0
  102. package/src/features/connectors/queries.ts +145 -0
  103. package/src/features/credentials/queries.ts +37 -0
  104. package/src/features/extensions/queries.ts +26 -0
  105. package/src/features/external-agents/queries.ts +36 -0
  106. package/src/features/gateways/queries.ts +274 -0
  107. package/src/features/missions/queries.ts +23 -0
  108. package/src/features/projects/queries.ts +20 -0
  109. package/src/features/protocols/queries.ts +149 -0
  110. package/src/features/providers/queries.ts +142 -0
  111. package/src/features/settings/queries.ts +20 -0
  112. package/src/features/skills/queries.ts +182 -0
  113. package/src/features/tasks/queries.ts +189 -0
  114. package/src/hooks/use-ws.ts +3 -2
  115. package/src/lib/agent-provider-options.test.ts +152 -0
  116. package/src/lib/agent-provider-options.ts +84 -0
  117. package/src/lib/app/api-client.ts +2 -2
  118. package/src/lib/providers/index.test.ts +78 -0
  119. package/src/lib/providers/index.ts +13 -10
  120. package/src/lib/query/client.ts +17 -0
  121. package/src/lib/server/agents/agent-runtime-config.ts +6 -6
  122. package/src/lib/server/agents/agent-service.ts +429 -0
  123. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  124. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  125. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  126. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  127. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  128. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  129. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  130. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  131. package/src/lib/server/build-llm.ts +7 -15
  132. package/src/lib/server/capability-router.test.ts +70 -1
  133. package/src/lib/server/capability-router.ts +24 -99
  134. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  135. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  136. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  137. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  138. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  139. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  140. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  141. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  142. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  143. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  144. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  145. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  146. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  147. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  148. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  149. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  150. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  151. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  152. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  153. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  154. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  155. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  156. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  157. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  158. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  159. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  160. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  161. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  162. package/src/lib/server/chats/chat-session-service.ts +410 -0
  163. package/src/lib/server/connectors/access.ts +1 -1
  164. package/src/lib/server/connectors/commands.ts +7 -6
  165. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  166. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  167. package/src/lib/server/connectors/connector-service.ts +453 -0
  168. package/src/lib/server/connectors/delivery.ts +17 -12
  169. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  170. package/src/lib/server/connectors/media.ts +1 -1
  171. package/src/lib/server/connectors/response-media.ts +1 -1
  172. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  173. package/src/lib/server/connectors/session.ts +9 -7
  174. package/src/lib/server/connectors/voice-note.ts +2 -1
  175. package/src/lib/server/context-manager.ts +20 -1
  176. package/src/lib/server/cost.ts +2 -3
  177. package/src/lib/server/credentials/credential-repository.ts +43 -4
  178. package/src/lib/server/credentials/credential-service.ts +112 -0
  179. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  180. package/src/lib/server/daemon/controller.ts +577 -0
  181. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  182. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  183. package/src/lib/server/daemon/types.ts +101 -0
  184. package/src/lib/server/embeddings.ts +3 -9
  185. package/src/lib/server/eval/agent-regression.ts +3 -2
  186. package/src/lib/server/eval/runner.ts +2 -2
  187. package/src/lib/server/execution-brief.test.ts +167 -0
  188. package/src/lib/server/execution-brief.ts +295 -0
  189. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  190. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  191. package/src/lib/server/execution-engine/index.ts +35 -0
  192. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  193. package/src/lib/server/execution-engine/types.ts +33 -0
  194. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  195. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  196. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  197. package/src/lib/server/messages/message-repository.ts +330 -0
  198. package/src/lib/server/missions/mission-service/core.ts +8 -6
  199. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  200. package/src/lib/server/openclaw/doctor.ts +1 -1
  201. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  202. package/src/lib/server/openclaw/gateway.ts +5 -14
  203. package/src/lib/server/openclaw/health.ts +3 -11
  204. package/src/lib/server/openclaw/sync.ts +8 -6
  205. package/src/lib/server/persistence/storage-context.ts +3 -0
  206. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  207. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  208. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  209. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  210. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  211. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  212. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  213. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  214. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  215. package/src/lib/server/protocols/protocol-types.ts +10 -7
  216. package/src/lib/server/provider-endpoint.ts +7 -12
  217. package/src/lib/server/provider-model-discovery.ts +2 -11
  218. package/src/lib/server/query-expansion.ts +5 -6
  219. package/src/lib/server/run-context.test.ts +365 -0
  220. package/src/lib/server/run-context.ts +367 -0
  221. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  222. package/src/lib/server/runtime/queue/core.ts +61 -190
  223. package/src/lib/server/runtime/run-ledger.ts +8 -0
  224. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  225. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  226. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  227. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  228. package/src/lib/server/service-result.ts +16 -0
  229. package/src/lib/server/session-note.ts +2 -3
  230. package/src/lib/server/session-reset-policy.ts +4 -3
  231. package/src/lib/server/session-tools/connector.ts +9 -6
  232. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  233. package/src/lib/server/session-tools/crud.ts +162 -10
  234. package/src/lib/server/session-tools/delegate.ts +1 -1
  235. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  236. package/src/lib/server/session-tools/memory.ts +6 -4
  237. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  238. package/src/lib/server/session-tools/session-info.ts +119 -12
  239. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  240. package/src/lib/server/session-tools/skills.ts +15 -15
  241. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  242. package/src/lib/server/session-tools/subagent.ts +125 -7
  243. package/src/lib/server/session-tools/team-context.ts +4 -3
  244. package/src/lib/server/session-tools/wallet.ts +0 -58
  245. package/src/lib/server/sessions/session-lineage.ts +55 -0
  246. package/src/lib/server/sessions/session-repository.ts +2 -2
  247. package/src/lib/server/skills/learned-skills.ts +24 -23
  248. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  249. package/src/lib/server/skills/skill-repository.ts +136 -13
  250. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  251. package/src/lib/server/storage-normalization.test.ts +42 -215
  252. package/src/lib/server/storage-normalization.ts +98 -0
  253. package/src/lib/server/storage.ts +19 -0
  254. package/src/lib/server/structured-extract.ts +3 -14
  255. package/src/lib/server/tasks/task-followups.ts +16 -11
  256. package/src/lib/server/tasks/task-result.test.ts +25 -29
  257. package/src/lib/server/tasks/task-result.ts +5 -9
  258. package/src/lib/server/tasks/task-route-service.ts +449 -0
  259. package/src/lib/server/text-normalization.ts +41 -0
  260. package/src/lib/server/tool-planning.ts +6 -42
  261. package/src/lib/server/upload-path.ts +5 -0
  262. package/src/lib/server/working-state/extraction.ts +614 -0
  263. package/src/lib/server/working-state/normalization.ts +866 -0
  264. package/src/lib/server/working-state/prompt.ts +60 -0
  265. package/src/lib/server/working-state/repository.ts +38 -0
  266. package/src/lib/server/working-state/service.test.ts +253 -0
  267. package/src/lib/server/working-state/service.ts +293 -0
  268. package/src/lib/validation/schemas.ts +1 -0
  269. package/src/lib/ws-client.ts +3 -3
  270. package/src/stores/slices/task-slice.ts +1 -4
  271. package/src/stores/use-chatroom-store.ts +2 -2
  272. package/src/types/index.ts +288 -22
  273. package/src/views/settings/section-providers.tsx +2 -2
@@ -1,5 +1,6 @@
1
1
  import type { Message, MessageToolEvent } from '@/types'
2
2
  import { loadSessions, saveSessions } from './storage'
3
+ import { appendMessage } from '@/lib/server/messages/message-repository'
3
4
  import { notify } from './ws-hub'
4
5
 
5
6
  export interface SessionNoteInput {
@@ -27,16 +28,14 @@ export function appendSessionNote(input: SessionNoteInput): Message | null {
27
28
  const sessions = loadSessions()
28
29
  const session = sessions[input.sessionId]
29
30
  if (!session) return null
30
- if (!Array.isArray(session.messages)) session.messages = []
31
31
 
32
32
  const next = buildSessionNoteMessage(input)
33
33
  if (!next) return null
34
34
 
35
- session.messages.push(next)
35
+ appendMessage(input.sessionId, next)
36
36
  session.lastActiveAt = next.time
37
37
  sessions[input.sessionId] = session
38
38
  saveSessions(sessions)
39
39
  notify('sessions')
40
- notify(`messages:${input.sessionId}`)
41
40
  return next
42
41
  }
@@ -1,5 +1,6 @@
1
1
  import type { Agent, AppSettings, Session, SessionResetMode, SessionResetType } from '@/types'
2
2
  import { isDirectConnectorSession } from '@/lib/server/connectors/session-kind'
3
+ import { clearMessages, getMessageCount } from '@/lib/server/messages/message-repository'
3
4
 
4
5
  export interface ResolvedSessionResetPolicy {
5
6
  type: SessionResetType
@@ -190,7 +191,7 @@ export function evaluateSessionFreshness(params: {
190
191
  const now = typeof params.now === 'number' ? params.now : Date.now()
191
192
  const session = params.session
192
193
  const policy = params.policy
193
- const messageCount = Array.isArray(session?.messages) ? session.messages.length : 0
194
+ const messageCount = typeof session?.id === 'string' ? getMessageCount(session.id) : 0
194
195
  const createdAt = typeof session?.createdAt === 'number' ? session.createdAt : now
195
196
  const lastActiveAt = typeof session?.lastActiveAt === 'number' ? session.lastActiveAt : createdAt
196
197
  const idleExpiresAt = typeof policy.idleTimeoutSec === 'number' && policy.idleTimeoutSec > 0
@@ -275,9 +276,9 @@ export function resetSessionRuntime(
275
276
  opts?: { now?: number },
276
277
  ): number {
277
278
  const now = typeof opts?.now === 'number' ? opts.now : Date.now()
278
- const cleared = Array.isArray(session.messages) ? session.messages.length : 0
279
+ const cleared = getMessageCount(session.id)
279
280
 
280
- session.messages = []
281
+ clearMessages(session.id)
281
282
  session.claudeSessionId = null
282
283
  session.codexThreadId = null
283
284
  session.opencodeSessionId = null
@@ -10,6 +10,7 @@ import { normalizeToolInputArgs } from './normalize-tool-args'
10
10
  import { safeJsonParseObject } from '../json-utils'
11
11
  import { tryResolvePathWithinBaseDir } from '../path-utils'
12
12
  import { dedup, errorMessage } from '@/lib/shared-utils'
13
+ import { getMessages } from '@/lib/server/messages/message-repository'
13
14
  import { isDirectConnectorSession } from '../connectors/session-kind'
14
15
  import {
15
16
  prepareConnectorVoiceNotePayload,
@@ -135,9 +136,10 @@ function pruneOldConnectorToolState(now: number): void {
135
136
  }
136
137
 
137
138
  function parseLatestUserTurn(
138
- session: { messages?: Array<Record<string, unknown>> } | null | undefined,
139
+ sessionId: string | null | undefined,
139
140
  ): { text: string; time: number } {
140
- const msgs = Array.isArray(session?.messages) ? session.messages : []
141
+ if (!sessionId) return { text: '', time: 0 }
142
+ const msgs = getMessages(sessionId)
141
143
  for (let i = msgs.length - 1; i >= 0; i -= 1) {
142
144
  const msg = msgs[i]
143
145
  if (String(msg?.role || '') !== 'user') continue
@@ -435,10 +437,10 @@ function trimToString(value: unknown): string {
435
437
 
436
438
  function resolveSessionConnectorTargets(
437
439
  session: {
440
+ id?: string
438
441
  user?: string
439
442
  name?: string
440
443
  connectorContext?: Record<string, unknown>
441
- messages?: Array<Record<string, unknown>>
442
444
  } | null | undefined,
443
445
  connectorId: string,
444
446
  ): Array<{ channelId: string; senderId?: string; senderName?: string }> {
@@ -463,7 +465,8 @@ function resolveSessionConnectorTargets(
463
465
  : null)
464
466
  }
465
467
 
466
- const messages = Array.isArray(session?.messages) ? session.messages : []
468
+ const sessionId = typeof session?.id === 'string' ? session.id : ''
469
+ const messages = sessionId ? getMessages(sessionId) : []
467
470
  for (let i = messages.length - 1; i >= 0; i -= 1) {
468
471
  if (messages[i]?.historyExcluded === true) continue
469
472
  const source = messages[i]?.source as Record<string, unknown> | undefined
@@ -485,10 +488,10 @@ function pickChannelTarget(params: {
485
488
  connectorId: string
486
489
  to?: string
487
490
  currentSession?: {
491
+ id?: string
488
492
  user?: string
489
493
  name?: string
490
494
  connectorContext?: Record<string, unknown>
491
- messages?: Array<Record<string, unknown>>
492
495
  } | null
493
496
  }): { channelId: string; error?: string } {
494
497
  let channelId = params.to?.trim() || ''
@@ -694,7 +697,7 @@ async function executeConnectorAction(input: ConnectorActionInput, bctx: Connect
694
697
  const currentSession = bctx.resolveCurrentSession?.()
695
698
  const sessionId = bctx.ctx?.sessionId || currentSession?.id || undefined
696
699
  const connectorScopedSessionId = isDirectConnectorSession(currentSession) ? sessionId : undefined
697
- const latestUserTurn = parseLatestUserTurn(currentSession)
700
+ const latestUserTurn = parseLatestUserTurn(sessionId)
698
701
  const turnKey = buildConnectorActionKey([sessionId, latestUserTurn.time || 'no-user-turn'])
699
702
 
700
703
  if (actionName === 'list_running' || actionName === 'list_targets') {
@@ -1,13 +1,14 @@
1
1
  import { z } from 'zod'
2
2
  import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
3
  import { HumanMessage } from '@langchain/core/messages'
4
- import { loadSessions, saveSessions } from '../storage'
5
4
  import { buildChatModel } from '../build-llm'
6
5
  import type { ToolBuildContext } from './context'
7
6
  import type { Extension, ExtensionHooks, Session } from '@/types'
8
7
  import { registerNativeCapability } from '../native-capabilities'
9
8
  import { normalizeToolInputArgs } from './normalize-tool-args'
10
9
  import { errorMessage } from '@/lib/shared-utils'
10
+ import { updateSessionRunContext } from '@/lib/server/run-context'
11
+ import { getMessages, replaceAllMessages } from '@/lib/server/messages/message-repository'
11
12
 
12
13
  interface ContextToolContext {
13
14
  ctx?: { agentId?: string | null; sessionId?: string | null }
@@ -22,7 +23,7 @@ async function executeContextStatus(bctx: ContextToolContext) {
22
23
  const { getContextStatus } = await import('../context-manager')
23
24
  const session = bctx.resolveCurrentSession?.()
24
25
  if (!session) return 'Error: no current session context.'
25
- const status = getContextStatus(session.messages || [], 2000, session.provider as string, session.model as string, {
26
+ const status = getContextStatus(getMessages(session.id), 2000, session.provider as string, session.model as string, {
26
27
  includeToolEvents: false,
27
28
  })
28
29
  return JSON.stringify(status)
@@ -36,7 +37,7 @@ async function executeContextSummarize(args: { keepLastN?: number }, bctx: Conte
36
37
  const session = bctx.resolveCurrentSession?.()
37
38
  if (!session || !bctx.ctx?.sessionId) return 'Error: no session context.'
38
39
 
39
- const messages = session.messages || []
40
+ const messages = getMessages(session.id)
40
41
  const keepLastN = normalized.keepLastN as number | undefined
41
42
  const keep = Math.max(2, Math.min(keepLastN || 10, messages.length))
42
43
  if (messages.length <= keep) return JSON.stringify({ status: 'no_action' })
@@ -58,15 +59,39 @@ async function executeContextSummarize(args: { keepLastN?: number }, bctx: Conte
58
59
  provider: session.provider, model: session.model, generateSummary
59
60
  })
60
61
 
61
- const sessions = loadSessions()
62
- if (sessions[bctx.ctx.sessionId]) {
63
- sessions[bctx.ctx.sessionId].messages = result.messages
64
- saveSessions(sessions)
65
- }
62
+ replaceAllMessages(bctx.ctx.sessionId ?? '', result.messages)
66
63
  return JSON.stringify({ status: 'compacted', remaining: result.messages.length })
67
64
  } catch (err: unknown) { return `Error: ${errorMessage(err)}` }
68
65
  }
69
66
 
67
+ const PIN_CONTEXT_TYPE_MAP: Record<string, 'keyFacts' | 'failedApproaches' | 'blockers' | 'discoveries'> = {
68
+ fact: 'keyFacts',
69
+ failed_approach: 'failedApproaches',
70
+ blocker: 'blockers',
71
+ discovery: 'discoveries',
72
+ }
73
+
74
+ async function executePinContext(
75
+ args: { type?: string; content?: string },
76
+ bctx: ContextToolContext,
77
+ ) {
78
+ try {
79
+ const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
80
+ const type = String(normalized.type || '').toLowerCase()
81
+ const content = String(normalized.content || '').slice(0, 300).trim()
82
+ if (!content) return 'Error: content is required.'
83
+ const field = PIN_CONTEXT_TYPE_MAP[type]
84
+ if (!field) return `Error: type must be one of: ${Object.keys(PIN_CONTEXT_TYPE_MAP).join(', ')}`
85
+ const sessionId = bctx.ctx?.sessionId
86
+ if (!sessionId) return 'Error: no session context.'
87
+ updateSessionRunContext(sessionId, (ctx) => {
88
+ ctx[field] = [...ctx[field], content]
89
+ return ctx
90
+ })
91
+ return JSON.stringify({ status: 'pinned', type, field })
92
+ } catch (err: unknown) { return `Error: ${errorMessage(err)}` }
93
+ }
94
+
70
95
  /**
71
96
  * Register as a Built-in Extension
72
97
  */
@@ -89,6 +114,19 @@ const ContextExtension: Extension = {
89
114
  properties: { keepLastN: { type: 'number' } }
90
115
  },
91
116
  execute: async (args, context) => executeContextSummarize(args as { keepLastN?: number }, { ctx: { sessionId: context.session.id, agentId: context.session.agentId ?? null }, resolveCurrentSession: () => context.session as unknown as Session })
117
+ },
118
+ {
119
+ name: 'pin_context',
120
+ description: 'Pin a fact, failed approach, blocker, or discovery to working memory so it survives context compaction and flows to subagents.',
121
+ parameters: {
122
+ type: 'object',
123
+ properties: {
124
+ type: { type: 'string', enum: ['fact', 'failed_approach', 'blocker', 'discovery'], description: 'What kind of context to pin.' },
125
+ content: { type: 'string', description: 'The content to pin (max 300 chars).' },
126
+ },
127
+ required: ['type', 'content'],
128
+ },
129
+ execute: async (args, context) => executePinContext(args as { type?: string; content?: string }, { ctx: { sessionId: context.session.id, agentId: context.session.agentId ?? null }, resolveCurrentSession: () => context.session as unknown as Session })
92
130
  }
93
131
  ]
94
132
  }
@@ -107,6 +145,17 @@ export function buildContextTools(bctx: ToolBuildContext): StructuredToolInterfa
107
145
  tool(
108
146
  async (args) => executeContextSummarize(args as { keepLastN?: number }, bctx),
109
147
  { name: 'context_summarize', description: ContextExtension.tools![1].description, schema: z.object({}).passthrough() }
110
- )
148
+ ),
149
+ tool(
150
+ async (args) => executePinContext(args as { type?: string; content?: string }, bctx),
151
+ {
152
+ name: 'pin_context',
153
+ description: ContextExtension.tools![2].description,
154
+ schema: z.object({
155
+ type: z.enum(['fact', 'failed_approach', 'blocker', 'discovery']).describe('What kind of context to pin.'),
156
+ content: z.string().describe('The content to pin (max 300 chars).'),
157
+ }),
158
+ }
159
+ ),
111
160
  ]
112
161
  }
@@ -15,6 +15,7 @@ import {
15
15
  encryptKey,
16
16
  decryptKey,
17
17
  } from '../storage'
18
+ import { getMessages } from '@/lib/server/messages/message-repository'
18
19
  import { resolveScheduleName } from '@/lib/schedules/schedule-name'
19
20
  import type { ScheduleLike } from '@/lib/schedules/schedule-dedupe'
20
21
  import {
@@ -38,6 +39,12 @@ import {
38
39
  prepareTaskCreation,
39
40
  } from '@/lib/server/tasks/task-service'
40
41
  import { ensureMissionForTask, enrichTaskWithMissionSummary } from '@/lib/server/missions/mission-service'
42
+ import { classifyMessage } from '@/lib/server/chat-execution/message-classifier'
43
+ import {
44
+ buildDelegationTaskProfile,
45
+ formatDelegationRationale,
46
+ resolveDelegationAdvisory,
47
+ } from '@/lib/server/agents/delegation-advisory'
41
48
  import type { ToolBuildContext } from './context'
42
49
  import { normalizeToolInputArgs } from './normalize-tool-args'
43
50
  import type { BoardTask } from '@/types'
@@ -76,6 +83,99 @@ function findDuplicateManagedAgent(
76
83
  return null
77
84
  }
78
85
 
86
+ function normalizeStringList(value: unknown): string[] {
87
+ if (!Array.isArray(value)) return []
88
+ const seen = new Set<string>()
89
+ const out: string[] = []
90
+ for (const entry of value) {
91
+ const trimmed = typeof entry === 'string' ? entry.trim() : ''
92
+ const key = trimmed.toLowerCase()
93
+ if (!trimmed || seen.has(key)) continue
94
+ seen.add(key)
95
+ out.push(trimmed)
96
+ }
97
+ return out
98
+ }
99
+
100
+ function buildTaskDelegationText(parsed: Record<string, unknown>): string {
101
+ const title = typeof parsed.title === 'string' ? parsed.title.trim() : ''
102
+ const description = typeof parsed.description === 'string' ? parsed.description.trim() : ''
103
+ return [title, description].filter(Boolean).join('\n\n').trim()
104
+ }
105
+
106
+ async function resolveManagedTaskDelegation(params: {
107
+ parsed: Record<string, unknown>
108
+ agents: ReturnType<typeof loadAgents>
109
+ ctx?: ToolBuildContext['ctx']
110
+ assignedAgentId: string | null
111
+ explicitAssignment: boolean
112
+ allowAutoAssignment: boolean
113
+ }): Promise<{
114
+ assignedAgentId: string | null
115
+ advisory: Record<string, unknown> | null
116
+ }> {
117
+ const currentAgentId = typeof params.ctx?.agentId === 'string' ? params.ctx.agentId.trim() : ''
118
+ if (!currentAgentId || params.ctx?.delegationEnabled !== true) {
119
+ return { assignedAgentId: params.assignedAgentId, advisory: null }
120
+ }
121
+ const currentAgent = params.agents[currentAgentId]
122
+ if (!currentAgent) {
123
+ return { assignedAgentId: params.assignedAgentId, advisory: null }
124
+ }
125
+
126
+ const explicitCapabilities = normalizeStringList(params.parsed.requiredCapabilities)
127
+ const classificationText = buildTaskDelegationText(params.parsed)
128
+ const classification = (!explicitCapabilities.length && classificationText && params.ctx?.sessionId)
129
+ ? await classifyMessage({
130
+ sessionId: params.ctx.sessionId,
131
+ agentId: currentAgentId,
132
+ message: classificationText,
133
+ }).catch(() => null)
134
+ : null
135
+
136
+ const profile = buildDelegationTaskProfile({
137
+ classification,
138
+ requiredCapabilities: explicitCapabilities,
139
+ })
140
+ if (!profile.substantial) {
141
+ return { assignedAgentId: params.assignedAgentId, advisory: null }
142
+ }
143
+
144
+ const delegationAdvisory = resolveDelegationAdvisory({
145
+ currentAgent,
146
+ agents: params.agents,
147
+ profile,
148
+ delegationTargetMode: params.ctx?.delegationTargetMode === 'selected' ? 'selected' : 'all',
149
+ delegationTargetAgentIds: params.ctx?.delegationTargetAgentIds || [],
150
+ })
151
+ const recommended = delegationAdvisory.recommended
152
+ if (!delegationAdvisory.shouldDelegate || !recommended) {
153
+ return { assignedAgentId: params.assignedAgentId, advisory: null }
154
+ }
155
+ if (params.explicitAssignment && params.assignedAgentId === recommended.agentId) {
156
+ return { assignedAgentId: params.assignedAgentId, advisory: null }
157
+ }
158
+
159
+ let assignedAgentId = params.assignedAgentId
160
+ let autoAssigned = false
161
+ if (!params.explicitAssignment && params.allowAutoAssignment) {
162
+ assignedAgentId = recommended.agentId
163
+ autoAssigned = true
164
+ }
165
+
166
+ return {
167
+ assignedAgentId,
168
+ advisory: {
169
+ recommendedAgentId: recommended.agentId,
170
+ recommendedAgentName: recommended.agentName,
171
+ rationale: formatDelegationRationale(recommended),
172
+ workType: profile.workType,
173
+ requiredCapabilities: profile.requiredCapabilities,
174
+ autoAssigned,
175
+ },
176
+ }
177
+ }
178
+
79
179
  const VALID_CONNECTOR_PLATFORMS = new Set([
80
180
  'discord',
81
181
  'telegram',
@@ -208,7 +308,7 @@ function deriveScheduleFollowupTarget(sessionId: string | null | undefined): {
208
308
 
209
309
  if (isMainSession(session)) return {}
210
310
 
211
- const messages = Array.isArray(session.messages) ? session.messages : []
311
+ const messages = getMessages(normalizedSessionId)
212
312
  for (let i = messages.length - 1; i >= 0; i -= 1) {
213
313
  const message = messages[i]
214
314
  if ((typeof message?.role === 'string' ? message.role : '') !== 'user') continue
@@ -365,6 +465,9 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
365
465
  description += `\n\nYou may create tasks for yourself, leave them unassigned, or delegate them to other agents. Your agent ID is "${ctx?.agentId || 'unknown'}". When delegating, set a target agent using "agentId", "assignee", "agent", "assignedAgentId", or "assigned_agent_id". Use the target agent's exact ID when possible. Valid manual statuses: backlog, queued, completed, failed, archived. "running" is runtime-only and set automatically when execution starts.` + agentSummary
366
466
  }
367
467
  description += '\n\nCreate/update calls accept either `data` as a JSON string or direct top-level fields like `title`, `description`, `status`, `agentId`, and `projectId`.'
468
+ if (canAssignOtherAgents) {
469
+ description += '\n\nWhen you omit an assignee, the runtime may auto-assign the task to a materially better-fit teammate based on `requiredCapabilities` or the classified work type. If you set an explicit assignee, it is respected in v1, but the response may include `delegationAdvisory` when another teammate is a better fit.'
470
+ }
368
471
  description += '\n\nFor follow-up work, set `continueFromTaskId` (or `followUpToTaskId`) to a prior task ID. The new task will inherit the predecessor\'s project/agent/session context, block on that task by default, and reuse its execution session when possible.'
369
472
  if (ctx?.projectId) {
370
473
  description += `\n\nCurrent project context: "${ctx.projectName || ctx.projectId}" (projectId "${ctx.projectId}"). Omit "projectId" to use this active project by default.`
@@ -492,6 +595,7 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
492
595
  if ((toolKey === 'manage_tasks' || toolKey === 'manage_schedules' || toolKey === 'manage_secrets') && !Object.prototype.hasOwnProperty.call(parsed, 'projectId') && ctx?.projectId) {
493
596
  parsed.projectId = ctx.projectId
494
597
  }
598
+ let taskDelegationAdvisory: Record<string, unknown> | null = null
495
599
  if (toolKey === 'manage_tasks' || toolKey === 'manage_schedules') {
496
600
  const agents = loadAgents()
497
601
  const resolution = resolveManagedAgentAssignment(
@@ -502,12 +606,12 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
502
606
  : null,
503
607
  { allowDescription: toolKey === 'manage_tasks' },
504
608
  )
505
- const assignmentError = validateManagedAgentAssignment({
506
- resourceLabel: res.label,
507
- agents,
508
- assignScope: canAssignOtherAgents ? 'all' : 'self',
509
- currentAgentId: ctx?.agentId || null,
510
- targetAgentId: resolution.agentId,
609
+ const assignmentError = validateManagedAgentAssignment({
610
+ resourceLabel: res.label,
611
+ agents,
612
+ assignScope: canAssignOtherAgents ? 'all' : 'self',
613
+ currentAgentId: ctx?.agentId || null,
614
+ targetAgentId: resolution.agentId,
511
615
  unresolvedReference: resolution.unresolvedReference,
512
616
  isDelegation: toolKey === 'manage_tasks' ? isDelegationTaskPayload(parsed as Record<string, unknown>) : false,
513
617
  delegatorAgentId: toolKey === 'manage_tasks'
@@ -516,6 +620,18 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
516
620
  })
517
621
  if (assignmentError) return assignmentError
518
622
  parsed.agentId = resolution.agentId
623
+ if (toolKey === 'manage_tasks') {
624
+ const delegated = await resolveManagedTaskDelegation({
625
+ parsed: parsed as Record<string, unknown>,
626
+ agents,
627
+ ctx,
628
+ assignedAgentId: resolution.agentId,
629
+ explicitAssignment: resolution.source === 'explicit',
630
+ allowAutoAssignment: true,
631
+ })
632
+ parsed.agentId = delegated.assignedAgentId
633
+ taskDelegationAdvisory = delegated.advisory
634
+ }
519
635
  }
520
636
  let preparedManagedTask: BoardTask | null = null
521
637
  let preparedManagedSchedule: any = null
@@ -639,6 +755,12 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
639
755
  missionId: mission.id,
640
756
  })
641
757
  }
758
+ if (taskDelegationAdvisory && responseEntry && typeof responseEntry === 'object') {
759
+ responseEntry = {
760
+ ...(responseEntry as Record<string, unknown>),
761
+ delegationAdvisory: taskDelegationAdvisory,
762
+ }
763
+ }
642
764
  }
643
765
  if (toolKey === 'manage_tasks' && entry.status === 'queued') {
644
766
  const { enqueueTask } = await import('@/lib/server/runtime/queue')
@@ -675,6 +797,7 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
675
797
  if (continuationError) return continuationError
676
798
  }
677
799
  const prevStatus = all[effectiveId]?.status
800
+ let taskDelegationAdvisory: Record<string, unknown> | null = null
678
801
  const managedAgents = toolKey === 'manage_tasks' || toolKey === 'manage_schedules'
679
802
  ? loadAgents()
680
803
  : null
@@ -682,6 +805,10 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
682
805
  const requestedClear = Object.prototype.hasOwnProperty.call(parsedRecord, 'agentId') && parsedRecord.agentId == null
683
806
  const shouldResolveAssignment = requestedClear
684
807
  || hasManagedAgentAssignmentInput(parsedRecord)
808
+ let resolvedAgentId: string | null = requestedClear
809
+ ? null
810
+ : (typeof all[effectiveId]?.agentId === 'string' ? all[effectiveId].agentId : null)
811
+ let explicitAssignment = false
685
812
  if (shouldResolveAssignment) {
686
813
  const resolution = resolveManagedAgentAssignment(
687
814
  parsedRecord,
@@ -689,18 +816,20 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
689
816
  null,
690
817
  { allowDescription: false },
691
818
  )
819
+ resolvedAgentId = requestedClear ? null : resolution.agentId
820
+ explicitAssignment = resolution.hadExplicitInput
692
821
  const assignmentError = validateManagedAgentAssignment({
693
822
  resourceLabel: res.label,
694
823
  agents: managedAgents,
695
824
  assignScope: canAssignOtherAgents ? 'all' : 'self',
696
825
  currentAgentId: ctx?.agentId || null,
697
- targetAgentId: requestedClear ? null : resolution.agentId,
826
+ targetAgentId: resolvedAgentId,
698
827
  unresolvedReference: requestedClear ? null : resolution.unresolvedReference,
699
828
  isDelegation: toolKey === 'manage_tasks'
700
829
  ? isDelegationTaskPayload({
701
830
  ...all[effectiveId],
702
831
  ...parsedRecord,
703
- agentId: requestedClear ? null : resolution.agentId,
832
+ agentId: resolvedAgentId,
704
833
  } as Record<string, unknown>)
705
834
  : false,
706
835
  delegatorAgentId: toolKey === 'manage_tasks'
@@ -711,7 +840,24 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
711
840
  : null,
712
841
  })
713
842
  if (assignmentError) return assignmentError
714
- if (!requestedClear) parsedRecord.agentId = resolution.agentId
843
+ if (!requestedClear) parsedRecord.agentId = resolvedAgentId
844
+ }
845
+ if (toolKey === 'manage_tasks') {
846
+ const delegated = await resolveManagedTaskDelegation({
847
+ parsed: {
848
+ ...all[effectiveId],
849
+ ...parsedRecord,
850
+ },
851
+ agents: managedAgents,
852
+ ctx,
853
+ assignedAgentId: resolvedAgentId,
854
+ explicitAssignment,
855
+ allowAutoAssignment: !resolvedAgentId || resolvedAgentId === ctx?.agentId,
856
+ })
857
+ if (delegated.assignedAgentId !== resolvedAgentId) {
858
+ parsedRecord.agentId = delegated.assignedAgentId
859
+ }
860
+ taskDelegationAdvisory = delegated.advisory
715
861
  }
716
862
  }
717
863
  if (toolKey === 'manage_schedules') {
@@ -797,6 +943,12 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
797
943
  if (toolKey === 'manage_projects') {
798
944
  return JSON.stringify(buildProjectSnapshot(all[effectiveId]))
799
945
  }
946
+ if (toolKey === 'manage_tasks' && taskDelegationAdvisory) {
947
+ return JSON.stringify({
948
+ ...(all[effectiveId] as Record<string, unknown>),
949
+ delegationAdvisory: taskDelegationAdvisory,
950
+ })
951
+ }
800
952
  if (toolKey === 'manage_schedules' && affectedScheduleIds?.length) {
801
953
  return JSON.stringify({
802
954
  ...all[effectiveId],
@@ -21,7 +21,7 @@ import {
21
21
  registerDelegationRuntime,
22
22
  startDelegationJob,
23
23
  } from '@/lib/server/agents/delegation-jobs'
24
- import { loadSession } from '@/lib/server/storage'
24
+ import { loadSession } from '@/lib/server/sessions/session-repository'
25
25
  import { markProviderFailure, markProviderSuccess } from '../provider-health'
26
26
  import { loadRuntimeSettings } from '../runtime/runtime-settings'
27
27
  import { getSessionDepth } from '../agents/subagent-runtime'