@swarmclawai/swarmclaw 0.8.4 → 0.8.7

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 (394) hide show
  1. package/README.md +9 -9
  2. package/bin/swarmclaw.js +5 -1
  3. package/bin/worker-cmd.js +73 -0
  4. package/package.json +2 -1
  5. package/src/app/api/agents/[id]/route.ts +17 -7
  6. package/src/app/api/agents/route.ts +21 -8
  7. package/src/app/api/approvals/route.test.ts +6 -6
  8. package/src/app/api/approvals/route.ts +2 -1
  9. package/src/app/api/auth/route.ts +2 -3
  10. package/src/app/api/chatrooms/[id]/chat/route.test.ts +299 -0
  11. package/src/app/api/chatrooms/[id]/chat/route.ts +3 -2
  12. package/src/app/api/chatrooms/[id]/route.ts +7 -6
  13. package/src/app/api/chats/[id]/chat/route.test.ts +496 -0
  14. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  15. package/src/app/api/chats/[id]/clear/route.ts +9 -9
  16. package/src/app/api/chats/[id]/devserver/route.ts +2 -1
  17. package/src/app/api/chats/[id]/edit-resend/route.ts +3 -4
  18. package/src/app/api/chats/[id]/fork/route.ts +3 -5
  19. package/src/app/api/chats/[id]/restore/route.ts +6 -7
  20. package/src/app/api/chats/[id]/retry/route.ts +3 -4
  21. package/src/app/api/chats/[id]/route.ts +61 -62
  22. package/src/app/api/chats/route.ts +7 -1
  23. package/src/app/api/connectors/[id]/route.ts +7 -8
  24. package/src/app/api/connectors/route.ts +5 -4
  25. package/src/app/api/eval/run/route.ts +2 -1
  26. package/src/app/api/eval/suite/route.ts +2 -1
  27. package/src/app/api/external-agents/route.test.ts +1 -1
  28. package/src/app/api/external-agents/route.ts +2 -2
  29. package/src/app/api/files/serve/route.ts +1 -1
  30. package/src/app/api/gateways/[id]/route.ts +7 -5
  31. package/src/app/api/gateways/route.ts +1 -1
  32. package/src/app/api/knowledge/upload/route.ts +1 -1
  33. package/src/app/api/logs/route.ts +5 -7
  34. package/src/app/api/memory-images/[filename]/route.ts +2 -3
  35. package/src/app/api/openclaw/agent-files/route.ts +4 -3
  36. package/src/app/api/openclaw/approvals/route.ts +3 -4
  37. package/src/app/api/openclaw/config-sync/route.ts +3 -2
  38. package/src/app/api/openclaw/cron/route.ts +3 -2
  39. package/src/app/api/openclaw/dotenv-keys/route.ts +2 -1
  40. package/src/app/api/openclaw/exec-config/route.ts +3 -2
  41. package/src/app/api/openclaw/gateway/route.ts +5 -4
  42. package/src/app/api/openclaw/history/route.ts +3 -2
  43. package/src/app/api/openclaw/media/route.ts +2 -1
  44. package/src/app/api/openclaw/permissions/route.ts +3 -2
  45. package/src/app/api/openclaw/sandbox-env/route.ts +3 -2
  46. package/src/app/api/openclaw/skills/install/route.ts +2 -1
  47. package/src/app/api/openclaw/skills/remove/route.ts +2 -1
  48. package/src/app/api/openclaw/skills/route.ts +3 -2
  49. package/src/app/api/orchestrator/run/route.ts +5 -14
  50. package/src/app/api/perf/route.ts +43 -0
  51. package/src/app/api/plugins/dependencies/route.ts +2 -1
  52. package/src/app/api/plugins/install/route.ts +2 -1
  53. package/src/app/api/plugins/marketplace/route.ts +3 -2
  54. package/src/app/api/plugins/settings/route.ts +2 -1
  55. package/src/app/api/preview-server/route.ts +11 -10
  56. package/src/app/api/projects/[id]/route.ts +1 -1
  57. package/src/app/api/schedules/[id]/route.test.ts +128 -0
  58. package/src/app/api/schedules/[id]/route.ts +43 -43
  59. package/src/app/api/schedules/[id]/run/route.ts +11 -62
  60. package/src/app/api/schedules/route.ts +21 -87
  61. package/src/app/api/settings/route.ts +2 -0
  62. package/src/app/api/setup/doctor/route.ts +9 -8
  63. package/src/app/api/tasks/[id]/approve/route.ts +33 -30
  64. package/src/app/api/tasks/[id]/route.ts +12 -35
  65. package/src/app/api/tasks/import/github/route.ts +2 -1
  66. package/src/app/api/tasks/route.ts +79 -91
  67. package/src/app/api/wallets/[id]/approve/route.ts +2 -1
  68. package/src/app/api/wallets/[id]/route.ts +13 -19
  69. package/src/app/api/wallets/[id]/send/route.ts +2 -1
  70. package/src/app/api/wallets/route.ts +2 -1
  71. package/src/app/api/webhooks/[id]/route.ts +2 -1
  72. package/src/app/api/webhooks/route.test.ts +3 -1
  73. package/src/app/page.tsx +23 -331
  74. package/src/cli/index.js +19 -0
  75. package/src/cli/index.ts +38 -7
  76. package/src/cli/spec.js +9 -0
  77. package/src/components/activity/activity-feed.tsx +7 -4
  78. package/src/components/agents/agent-card.tsx +32 -6
  79. package/src/components/agents/agent-chat-list.tsx +55 -22
  80. package/src/components/agents/agent-files-editor.tsx +3 -2
  81. package/src/components/agents/agent-sheet.tsx +123 -22
  82. package/src/components/agents/inspector-panel.tsx +1 -1
  83. package/src/components/agents/openclaw-skills-panel.tsx +2 -1
  84. package/src/components/agents/trash-list.tsx +1 -1
  85. package/src/components/auth/access-key-gate.tsx +8 -2
  86. package/src/components/auth/setup-wizard.tsx +10 -9
  87. package/src/components/auth/user-picker.tsx +3 -2
  88. package/src/components/chat/chat-area.tsx +20 -1
  89. package/src/components/chat/chat-card.tsx +18 -3
  90. package/src/components/chat/chat-header.tsx +24 -4
  91. package/src/components/chat/chat-list.tsx +2 -11
  92. package/src/components/chat/heartbeat-history-panel.tsx +2 -1
  93. package/src/components/chat/message-bubble.tsx +45 -6
  94. package/src/components/chat/message-list.tsx +280 -145
  95. package/src/components/chat/streaming-bubble.tsx +217 -60
  96. package/src/components/chat/swarm-panel.test.ts +274 -0
  97. package/src/components/chat/swarm-panel.tsx +410 -0
  98. package/src/components/chat/swarm-status-card.tsx +346 -0
  99. package/src/components/chat/tool-call-bubble.tsx +48 -23
  100. package/src/components/chatrooms/chatroom-list.tsx +8 -5
  101. package/src/components/chatrooms/chatroom-message.tsx +10 -7
  102. package/src/components/chatrooms/chatroom-view.tsx +12 -9
  103. package/src/components/connectors/connector-health.tsx +6 -4
  104. package/src/components/connectors/connector-list.tsx +16 -11
  105. package/src/components/connectors/connector-sheet.tsx +12 -6
  106. package/src/components/home/home-view.tsx +38 -24
  107. package/src/components/input/chat-input.tsx +10 -1
  108. package/src/components/layout/app-layout.tsx +2 -38
  109. package/src/components/layout/sheet-layer.tsx +50 -0
  110. package/src/components/mcp-servers/mcp-server-list.tsx +37 -5
  111. package/src/components/mcp-servers/mcp-server-sheet.tsx +12 -2
  112. package/src/components/plugins/plugin-list.tsx +8 -4
  113. package/src/components/plugins/plugin-sheet.tsx +2 -1
  114. package/src/components/providers/provider-list.tsx +3 -2
  115. package/src/components/providers/provider-sheet.tsx +2 -1
  116. package/src/components/runs/run-list.tsx +11 -7
  117. package/src/components/schedules/schedule-card.tsx +5 -3
  118. package/src/components/shared/agent-switch-dialog.tsx +1 -1
  119. package/src/components/shared/attachment-chip.tsx +19 -3
  120. package/src/components/shared/notification-center.tsx +6 -3
  121. package/src/components/shared/settings/plugin-manager.tsx +3 -2
  122. package/src/components/shared/settings/section-embedding.tsx +2 -1
  123. package/src/components/shared/settings/section-orchestrator.tsx +2 -1
  124. package/src/components/shared/settings/section-user-preferences.tsx +107 -0
  125. package/src/components/shared/settings/settings-page.tsx +13 -9
  126. package/src/components/skills/clawhub-browser.tsx +15 -4
  127. package/src/components/skills/skill-list.tsx +15 -4
  128. package/src/components/tasks/approvals-panel.tsx +2 -1
  129. package/src/components/tasks/task-board.tsx +35 -37
  130. package/src/components/tasks/task-sheet.tsx +4 -3
  131. package/src/components/ui/full-screen-loader.tsx +164 -0
  132. package/src/components/wallets/wallet-approval-dialog.tsx +2 -1
  133. package/src/components/wallets/wallet-panel.tsx +6 -5
  134. package/src/components/wallets/wallet-section.tsx +3 -2
  135. package/src/components/webhooks/webhook-list.tsx +4 -5
  136. package/src/components/webhooks/webhook-sheet.tsx +6 -6
  137. package/src/hooks/use-app-bootstrap.ts +202 -0
  138. package/src/hooks/use-mounted-ref.ts +14 -0
  139. package/src/hooks/use-now.ts +31 -0
  140. package/src/hooks/use-openclaw-gateway.ts +2 -1
  141. package/src/instrumentation.ts +20 -8
  142. package/src/lib/agent-default-tools.test.ts +52 -0
  143. package/src/lib/agent-default-tools.ts +40 -0
  144. package/src/lib/api-client.test.ts +21 -0
  145. package/src/lib/api-client.ts +6 -11
  146. package/src/lib/canvas-content.test.ts +360 -0
  147. package/src/lib/chat-streaming-state.test.ts +49 -2
  148. package/src/lib/chat-streaming-state.ts +26 -10
  149. package/src/lib/fetch-timeout.test.ts +54 -0
  150. package/src/lib/fetch-timeout.ts +60 -3
  151. package/src/lib/live-tool-events.test.ts +77 -0
  152. package/src/lib/live-tool-events.ts +73 -0
  153. package/src/lib/local-observability.test.ts +2 -2
  154. package/src/lib/openclaw-endpoint.test.ts +1 -1
  155. package/src/lib/providers/anthropic.ts +12 -16
  156. package/src/lib/providers/index.ts +4 -2
  157. package/src/lib/providers/ollama.ts +9 -6
  158. package/src/lib/providers/openai.ts +11 -14
  159. package/src/lib/runtime-env.test.ts +8 -8
  160. package/src/lib/schedule-dedupe-advanced.test.ts +2 -2
  161. package/src/lib/schedule-dedupe.test.ts +1 -1
  162. package/src/lib/schedule-dedupe.ts +3 -2
  163. package/src/lib/server/agent-thread-session.test.ts +6 -6
  164. package/src/lib/server/agent-thread-session.ts +6 -9
  165. package/src/lib/server/alert-dispatch.ts +2 -1
  166. package/src/lib/server/api-routes.test.ts +6 -6
  167. package/src/lib/server/approval-connector-notify.test.ts +4 -4
  168. package/src/lib/server/approvals-auto-approve.test.ts +29 -29
  169. package/src/lib/server/approvals.test.ts +317 -0
  170. package/src/lib/server/approvals.ts +5 -4
  171. package/src/lib/server/autonomy-runtime.test.ts +11 -11
  172. package/src/lib/server/browser-state.ts +2 -2
  173. package/src/lib/server/capability-router.test.ts +1 -1
  174. package/src/lib/server/capability-router.ts +3 -2
  175. package/src/lib/server/chat-execution-advanced.test.ts +15 -2
  176. package/src/lib/server/chat-execution-connector-delivery.ts +67 -0
  177. package/src/lib/server/chat-execution-disabled.test.ts +3 -3
  178. package/src/lib/server/chat-execution-eval-history.test.ts +3 -3
  179. package/src/lib/server/chat-execution-heartbeat.test.ts +42 -1
  180. package/src/lib/server/chat-execution-session-sync.test.ts +119 -0
  181. package/src/lib/server/chat-execution-tool-events.ts +116 -0
  182. package/src/lib/server/chat-execution-utils.test.ts +479 -0
  183. package/src/lib/server/chat-execution-utils.ts +533 -0
  184. package/src/lib/server/chat-execution.ts +153 -748
  185. package/src/lib/server/chat-streaming-utils.ts +174 -0
  186. package/src/lib/server/chat-turn-tool-routing.ts +310 -0
  187. package/src/lib/server/chatroom-session-persistence.test.ts +2 -2
  188. package/src/lib/server/clawhub-client.ts +2 -1
  189. package/src/lib/server/collection-helpers.test.ts +92 -0
  190. package/src/lib/server/collection-helpers.ts +25 -3
  191. package/src/lib/server/connectors/access.ts +146 -0
  192. package/src/lib/server/connectors/bluebubbles.test.ts +1 -1
  193. package/src/lib/server/connectors/bluebubbles.ts +4 -4
  194. package/src/lib/server/connectors/commands.ts +367 -0
  195. package/src/lib/server/connectors/connector-routing.test.ts +4 -4
  196. package/src/lib/server/connectors/delivery.ts +142 -0
  197. package/src/lib/server/connectors/discord.ts +37 -40
  198. package/src/lib/server/connectors/email.ts +11 -10
  199. package/src/lib/server/connectors/googlechat.ts +4 -4
  200. package/src/lib/server/connectors/inbound-audio-transcription.ts +2 -1
  201. package/src/lib/server/connectors/ingress-delivery.ts +23 -0
  202. package/src/lib/server/connectors/manager-roundtrip.test.ts +300 -0
  203. package/src/lib/server/connectors/manager.test.ts +352 -77
  204. package/src/lib/server/connectors/manager.ts +134 -673
  205. package/src/lib/server/connectors/matrix.ts +4 -4
  206. package/src/lib/server/connectors/message-sentinel.ts +7 -0
  207. package/src/lib/server/connectors/openclaw.test.ts +1 -1
  208. package/src/lib/server/connectors/openclaw.ts +8 -10
  209. package/src/lib/server/connectors/outbox.test.ts +192 -0
  210. package/src/lib/server/connectors/outbox.ts +369 -0
  211. package/src/lib/server/connectors/pairing.test.ts +18 -1
  212. package/src/lib/server/connectors/pairing.ts +49 -4
  213. package/src/lib/server/connectors/policy.ts +9 -3
  214. package/src/lib/server/connectors/reconnect-state.ts +71 -0
  215. package/src/lib/server/connectors/response-media.ts +256 -0
  216. package/src/lib/server/connectors/runtime-state.ts +67 -0
  217. package/src/lib/server/connectors/session.test.ts +357 -0
  218. package/src/lib/server/connectors/session.ts +422 -0
  219. package/src/lib/server/connectors/signal.ts +7 -7
  220. package/src/lib/server/connectors/slack.ts +43 -43
  221. package/src/lib/server/connectors/teams.ts +4 -4
  222. package/src/lib/server/connectors/telegram.ts +37 -43
  223. package/src/lib/server/connectors/types.ts +31 -1
  224. package/src/lib/server/connectors/whatsapp.test.ts +108 -0
  225. package/src/lib/server/connectors/whatsapp.ts +106 -34
  226. package/src/lib/server/context-manager.test.ts +409 -0
  227. package/src/lib/server/cost.test.ts +1 -1
  228. package/src/lib/server/daemon-policy.ts +78 -0
  229. package/src/lib/server/daemon-state-connectors.test.ts +167 -0
  230. package/src/lib/server/daemon-state.test.ts +283 -55
  231. package/src/lib/server/daemon-state.ts +106 -109
  232. package/src/lib/server/data-dir.test.ts +5 -5
  233. package/src/lib/server/data-dir.ts +4 -0
  234. package/src/lib/server/delegation-jobs-advanced.test.ts +1 -1
  235. package/src/lib/server/delegation-jobs.test.ts +87 -0
  236. package/src/lib/server/delegation-jobs.ts +42 -48
  237. package/src/lib/server/devserver-launch.ts +1 -1
  238. package/src/lib/server/document-utils.ts +7 -9
  239. package/src/lib/server/elevenlabs.ts +2 -1
  240. package/src/lib/server/embeddings.test.ts +105 -0
  241. package/src/lib/server/ethereum.ts +3 -2
  242. package/src/lib/server/eval/agent-regression.ts +3 -2
  243. package/src/lib/server/eval/runner.ts +2 -1
  244. package/src/lib/server/eval/scorer.ts +2 -1
  245. package/src/lib/server/evm-swap.ts +2 -1
  246. package/src/lib/server/gateway/protocol.test.ts +1 -1
  247. package/src/lib/server/guardian.ts +2 -1
  248. package/src/lib/server/heartbeat-blocked-suppression.test.ts +151 -0
  249. package/src/lib/server/heartbeat-service-timer.test.ts +6 -6
  250. package/src/lib/server/heartbeat-service.test.ts +406 -0
  251. package/src/lib/server/heartbeat-service.ts +54 -7
  252. package/src/lib/server/heartbeat-wake.test.ts +19 -0
  253. package/src/lib/server/heartbeat-wake.ts +17 -16
  254. package/src/lib/server/integrity-monitor.test.ts +149 -0
  255. package/src/lib/server/json-utils.ts +22 -0
  256. package/src/lib/server/knowledge-db.test.ts +13 -13
  257. package/src/lib/server/link-understanding.ts +2 -1
  258. package/src/lib/server/llm-response-cache.test.ts +1 -1
  259. package/src/lib/server/main-agent-loop-advanced.test.ts +65 -3
  260. package/src/lib/server/main-agent-loop.test.ts +6 -6
  261. package/src/lib/server/main-agent-loop.ts +21 -7
  262. package/src/lib/server/mcp-client.test.ts +1 -1
  263. package/src/lib/server/mcp-conformance.test.ts +1 -1
  264. package/src/lib/server/mcp-conformance.ts +3 -2
  265. package/src/lib/server/memory-consolidation.ts +2 -1
  266. package/src/lib/server/memory-db.test.ts +485 -0
  267. package/src/lib/server/memory-db.ts +39 -26
  268. package/src/lib/server/memory-graph.test.ts +2 -2
  269. package/src/lib/server/memory-policy.test.ts +7 -7
  270. package/src/lib/server/memory-retrieval.test.ts +1 -1
  271. package/src/lib/server/openclaw-config-sync.ts +2 -1
  272. package/src/lib/server/openclaw-deploy.test.ts +1 -1
  273. package/src/lib/server/openclaw-deploy.ts +8 -12
  274. package/src/lib/server/openclaw-exec-config.ts +2 -1
  275. package/src/lib/server/openclaw-gateway.ts +6 -7
  276. package/src/lib/server/openclaw-skills-normalize.ts +2 -1
  277. package/src/lib/server/openclaw-sync.ts +7 -5
  278. package/src/lib/server/orchestrator-lg-structure.test.ts +17 -0
  279. package/src/lib/server/orchestrator-lg.ts +199 -327
  280. package/src/lib/server/path-utils.ts +31 -0
  281. package/src/lib/server/perf.ts +161 -0
  282. package/src/lib/server/plugins-approval-guidance.ts +115 -0
  283. package/src/lib/server/plugins.test.ts +1 -1
  284. package/src/lib/server/plugins.ts +22 -132
  285. package/src/lib/server/process-manager.ts +5 -8
  286. package/src/lib/server/provider-health.test.ts +137 -0
  287. package/src/lib/server/provider-health.ts +3 -3
  288. package/src/lib/server/provider-model-discovery.ts +3 -12
  289. package/src/lib/server/queue-followups.test.ts +9 -9
  290. package/src/lib/server/queue-reconcile.test.ts +2 -2
  291. package/src/lib/server/queue-recovery.test.ts +269 -0
  292. package/src/lib/server/queue.test.ts +570 -0
  293. package/src/lib/server/queue.ts +62 -455
  294. package/src/lib/server/resolve-image.ts +30 -0
  295. package/src/lib/server/runtime-settings.test.ts +4 -4
  296. package/src/lib/server/runtime-storage-write-paths.test.ts +60 -0
  297. package/src/lib/server/schedule-normalization.test.ts +279 -0
  298. package/src/lib/server/schedule-service.ts +263 -0
  299. package/src/lib/server/scheduler.ts +17 -74
  300. package/src/lib/server/session-mailbox.test.ts +191 -0
  301. package/src/lib/server/session-run-manager.test.ts +640 -0
  302. package/src/lib/server/session-run-manager.ts +59 -15
  303. package/src/lib/server/session-tools/autonomy-tools.test.ts +20 -20
  304. package/src/lib/server/session-tools/calendar.ts +2 -1
  305. package/src/lib/server/session-tools/canvas.ts +2 -1
  306. package/src/lib/server/session-tools/chatroom.ts +2 -1
  307. package/src/lib/server/session-tools/connector.ts +26 -28
  308. package/src/lib/server/session-tools/context-mgmt.ts +3 -2
  309. package/src/lib/server/session-tools/crawl.ts +4 -3
  310. package/src/lib/server/session-tools/crud.ts +105 -324
  311. package/src/lib/server/session-tools/delegate-fallback.test.ts +9 -9
  312. package/src/lib/server/session-tools/delegate.ts +6 -8
  313. package/src/lib/server/session-tools/discovery-approvals.test.ts +15 -15
  314. package/src/lib/server/session-tools/discovery.ts +4 -3
  315. package/src/lib/server/session-tools/document.ts +2 -1
  316. package/src/lib/server/session-tools/email.ts +2 -1
  317. package/src/lib/server/session-tools/extract.ts +2 -1
  318. package/src/lib/server/session-tools/file.ts +4 -3
  319. package/src/lib/server/session-tools/http.ts +2 -1
  320. package/src/lib/server/session-tools/human-loop.ts +2 -1
  321. package/src/lib/server/session-tools/image-gen.ts +4 -3
  322. package/src/lib/server/session-tools/index.ts +26 -30
  323. package/src/lib/server/session-tools/mailbox.ts +2 -1
  324. package/src/lib/server/session-tools/manage-connectors.test.ts +4 -4
  325. package/src/lib/server/session-tools/manage-schedules.test.ts +12 -12
  326. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +5 -5
  327. package/src/lib/server/session-tools/manage-tasks.test.ts +2 -2
  328. package/src/lib/server/session-tools/monitor.ts +2 -1
  329. package/src/lib/server/session-tools/platform.ts +2 -1
  330. package/src/lib/server/session-tools/plugin-creator.ts +2 -1
  331. package/src/lib/server/session-tools/replicate.ts +3 -2
  332. package/src/lib/server/session-tools/session-tools-wiring.test.ts +6 -6
  333. package/src/lib/server/session-tools/shell.ts +4 -9
  334. package/src/lib/server/session-tools/subagent.ts +322 -170
  335. package/src/lib/server/session-tools/table.ts +6 -5
  336. package/src/lib/server/session-tools/wallet-tool.test.ts +3 -3
  337. package/src/lib/server/session-tools/wallet.ts +7 -6
  338. package/src/lib/server/session-tools/web-browser-config.test.ts +1 -0
  339. package/src/lib/server/session-tools/web-utils.ts +317 -0
  340. package/src/lib/server/session-tools/web.ts +62 -328
  341. package/src/lib/server/skill-prompt-budget.test.ts +1 -1
  342. package/src/lib/server/skills-normalize.ts +2 -1
  343. package/src/lib/server/storage-item-access.test.ts +302 -0
  344. package/src/lib/server/storage.ts +366 -314
  345. package/src/lib/server/stream-agent-chat.test.ts +82 -3
  346. package/src/lib/server/stream-agent-chat.ts +146 -510
  347. package/src/lib/server/stream-continuation.ts +412 -0
  348. package/src/lib/server/subagent-lineage.test.ts +647 -0
  349. package/src/lib/server/subagent-lineage.ts +435 -0
  350. package/src/lib/server/subagent-runtime.test.ts +484 -0
  351. package/src/lib/server/subagent-runtime.ts +419 -0
  352. package/src/lib/server/subagent-swarm.test.ts +391 -0
  353. package/src/lib/server/subagent-swarm.ts +564 -0
  354. package/src/lib/server/system-events.ts +3 -3
  355. package/src/lib/server/task-followups.test.ts +491 -0
  356. package/src/lib/server/task-followups.ts +391 -0
  357. package/src/lib/server/task-lifecycle.test.ts +205 -0
  358. package/src/lib/server/task-lifecycle.ts +200 -0
  359. package/src/lib/server/task-quality-gate.test.ts +1 -1
  360. package/src/lib/server/task-resume.ts +208 -0
  361. package/src/lib/server/task-service.test.ts +108 -0
  362. package/src/lib/server/task-service.ts +264 -0
  363. package/src/lib/server/task-validation.test.ts +1 -1
  364. package/src/lib/server/test-utils/run-with-temp-data-dir.ts +42 -0
  365. package/src/lib/server/tool-capability-policy.test.ts +2 -2
  366. package/src/lib/server/tool-capability-policy.ts +3 -2
  367. package/src/lib/server/tool-planning.ts +2 -1
  368. package/src/lib/server/tool-retry.ts +2 -3
  369. package/src/lib/server/wake-dispatcher.test.ts +303 -0
  370. package/src/lib/server/wake-dispatcher.ts +318 -0
  371. package/src/lib/server/wake-mode.test.ts +161 -0
  372. package/src/lib/server/wake-mode.ts +174 -0
  373. package/src/lib/server/wallet-service.ts +8 -9
  374. package/src/lib/server/watch-jobs.ts +2 -1
  375. package/src/lib/server/workspace-context.ts +2 -2
  376. package/src/lib/shared-utils.test.ts +142 -0
  377. package/src/lib/shared-utils.ts +62 -0
  378. package/src/lib/tool-event-summary.ts +2 -1
  379. package/src/lib/view-routes.test.ts +100 -0
  380. package/src/lib/wallet.test.ts +322 -6
  381. package/src/proxy.test.ts +4 -4
  382. package/src/proxy.ts +2 -3
  383. package/src/stores/set-if-changed.ts +40 -0
  384. package/src/stores/slices/agent-slice.ts +111 -0
  385. package/src/stores/slices/auth-slice.ts +25 -0
  386. package/src/stores/slices/data-slice.ts +301 -0
  387. package/src/stores/slices/index.ts +7 -0
  388. package/src/stores/slices/session-slice.ts +112 -0
  389. package/src/stores/slices/task-slice.ts +63 -0
  390. package/src/stores/slices/ui-slice.ts +192 -0
  391. package/src/stores/use-app-store.ts +17 -822
  392. package/src/stores/use-approval-store.ts +2 -1
  393. package/src/stores/use-chat-store.ts +8 -1
  394. package/src/types/index.ts +10 -0
@@ -0,0 +1,146 @@
1
+ import type { Connector } from '@/types'
2
+ import { loadSettings } from '../storage'
3
+ import { requestApprovalMaybeAutoApprove } from '../approvals'
4
+ import {
5
+ createOrTouchPairingRequest,
6
+ getWhatsAppApprovedSenderIds,
7
+ isSenderAllowed,
8
+ listStoredAllowedSenders,
9
+ parseAllowFromCsv,
10
+ parsePairingPolicy,
11
+ type PairingPolicy,
12
+ } from './pairing'
13
+ import { isReplyToLastOutbound, resolveConnectorSessionPolicy, textMentionsAlias } from './policy'
14
+ import type { ConnectorAgent, ConnectorSession } from './session'
15
+ import type { InboundMessage } from './types'
16
+
17
+ export interface ResolvedPairingAccess {
18
+ policy: PairingPolicy
19
+ configAllowFrom: string[]
20
+ isAllowed: boolean
21
+ hasAnyApprover: boolean
22
+ }
23
+
24
+ export function evaluateGroupPolicy(params: {
25
+ connector: Connector
26
+ msg: InboundMessage
27
+ session?: ConnectorSession | null
28
+ aliases: string[]
29
+ }): { allowed: boolean; reason: string } {
30
+ const { connector, msg, session, aliases } = params
31
+ if (!msg.isGroup) return { allowed: true, reason: 'dm' }
32
+ const policy = resolveConnectorSessionPolicy(connector, msg, session)
33
+ if (policy.groupPolicy === 'open') return { allowed: true, reason: 'open' }
34
+ if (policy.groupPolicy === 'disabled') return { allowed: false, reason: 'disabled' }
35
+ const mentioned = !!msg.mentionsBot || textMentionsAlias(msg.text || '', aliases)
36
+ const replied = isReplyToLastOutbound(msg, session)
37
+ if (policy.groupPolicy === 'mention') {
38
+ return { allowed: mentioned, reason: mentioned ? 'mentioned' : 'mention_required' }
39
+ }
40
+ const allowed = mentioned || replied
41
+ return { allowed, reason: allowed ? (mentioned ? 'mentioned' : 'reply') : 'reply_or_mention_required' }
42
+ }
43
+
44
+ export function resolvePairingAccess(connector: Connector, msg: InboundMessage): ResolvedPairingAccess {
45
+ const policy = parsePairingPolicy(connector.config?.dmPolicy, 'open')
46
+ const globalWhatsAppAllowFrom = connector.platform === 'whatsapp'
47
+ ? getWhatsAppApprovedSenderIds(loadSettings().whatsappApprovedContacts)
48
+ : []
49
+ const configAllowFrom = parseAllowFromCsv([
50
+ connector.config?.allowFrom,
51
+ ...globalWhatsAppAllowFrom,
52
+ ].filter(Boolean).join(','))
53
+ const stored = listStoredAllowedSenders(connector.id)
54
+ const isAllowed = [
55
+ msg.senderId,
56
+ msg.senderIdAlt,
57
+ ]
58
+ .filter((senderId): senderId is string => typeof senderId === 'string' && !!senderId.trim())
59
+ .some((senderId) => isSenderAllowed({
60
+ connectorId: connector.id,
61
+ senderId,
62
+ configAllowFrom,
63
+ }))
64
+ return {
65
+ policy,
66
+ configAllowFrom,
67
+ isAllowed,
68
+ hasAnyApprover: (configAllowFrom.length + stored.length) > 0,
69
+ }
70
+ }
71
+
72
+ export function resolveInboundApprovalSenderId(msg: InboundMessage): string {
73
+ const alt = typeof msg.senderIdAlt === 'string' ? msg.senderIdAlt.trim() : ''
74
+ if (alt) return alt
75
+ return typeof msg.senderId === 'string' ? msg.senderId.trim() : ''
76
+ }
77
+
78
+ export function buildInboundApprovalSubject(msg: InboundMessage): string {
79
+ const senderName = typeof msg.senderName === 'string' ? msg.senderName.trim() : ''
80
+ const senderId = resolveInboundApprovalSenderId(msg)
81
+ if (senderName && senderId && senderName !== senderId) return `${senderName} (${senderId})`
82
+ return senderName || senderId || 'this sender'
83
+ }
84
+
85
+ export async function enforceInboundAccessPolicy(params: {
86
+ connector: Connector
87
+ msg: InboundMessage
88
+ session: ConnectorSession
89
+ agent: ConnectorAgent
90
+ noMessageSentinel: string
91
+ }): Promise<string | null> {
92
+ const { connector, msg, session, agent, noMessageSentinel } = params
93
+ if (msg.isGroup) return null
94
+ const { policy, isAllowed } = resolvePairingAccess(connector, msg)
95
+ if (policy === 'open') return null
96
+
97
+ if (policy === 'disabled') return noMessageSentinel
98
+ if (isAllowed) return null
99
+
100
+ const senderId = resolveInboundApprovalSenderId(msg)
101
+ const senderSubject = buildInboundApprovalSubject(msg)
102
+ const approval = await requestApprovalMaybeAutoApprove({
103
+ category: 'connector_sender',
104
+ title: `Approve ${senderSubject} on ${connector.name}`,
105
+ description: `Allow ${senderSubject} to message ${agent.name} via ${connector.platform}/${connector.name}.`,
106
+ data: {
107
+ connectorId: connector.id,
108
+ connectorName: connector.name,
109
+ platform: connector.platform,
110
+ senderId,
111
+ senderIdRaw: typeof msg.senderId === 'string' ? msg.senderId.trim() : '',
112
+ senderName: typeof msg.senderName === 'string' ? msg.senderName.trim() : '',
113
+ channelId: typeof msg.channelId === 'string' ? msg.channelId.trim() : '',
114
+ policy,
115
+ },
116
+ agentId: agent.id,
117
+ sessionId: session.id,
118
+ })
119
+
120
+ if (approval.status === 'approved') return null
121
+
122
+ if (policy === 'allowlist') {
123
+ return [
124
+ `${senderSubject} is pending approval for this connector.`,
125
+ 'A SwarmClaw approval request has been created for this sender.',
126
+ 'An approved operator can allow this sender in the app or via /pair allow <senderId>.',
127
+ ].join('\n')
128
+ }
129
+
130
+ if (policy === 'pairing') {
131
+ const request = createOrTouchPairingRequest({
132
+ connectorId: connector.id,
133
+ senderId,
134
+ senderName: msg.senderName,
135
+ channelId: msg.channelId,
136
+ })
137
+ return [
138
+ `${senderSubject} is pending approval for this connector.`,
139
+ 'A SwarmClaw approval request has been created for this sender.',
140
+ `Pairing code: ${request.code}`,
141
+ 'Approve in the app, or ask an approved sender to run /pair approve <code>.',
142
+ ].join('\n')
143
+ }
144
+
145
+ return 'This sender is not authorized for this connector.'
146
+ }
@@ -1,6 +1,6 @@
1
1
  import assert from 'node:assert/strict'
2
2
  import { test } from 'node:test'
3
- import bluebubbles from './bluebubbles.ts'
3
+ import bluebubbles from './bluebubbles'
4
4
  import type { Connector } from '@/types'
5
5
  import type { InboundMessage } from './types'
6
6
 
@@ -1,6 +1,6 @@
1
1
  import crypto from 'node:crypto'
2
2
  import type { PlatformConnector, ConnectorInstance, InboundMessage, InboundMedia } from './types'
3
- import { isNoMessage } from './manager'
3
+ import { resolveConnectorIngressReply } from './ingress-delivery'
4
4
 
5
5
  const DEFAULT_TIMEOUT_MS = 10_000
6
6
  const DEFAULT_WEBHOOK_PATH = '/api/connectors/{id}/webhook'
@@ -260,14 +260,14 @@ const bluebubbles: PlatformConnector = {
260
260
  if (!allowed) return {}
261
261
  }
262
262
 
263
- const response = await onMessage(inbound)
264
- if (!response || isNoMessage(response)) return {}
263
+ const reply = await resolveConnectorIngressReply(onMessage, inbound)
264
+ if (!reply) return {}
265
265
 
266
266
  await sendBlueBubblesText({
267
267
  serverUrl,
268
268
  password,
269
269
  channelId: inbound.channelId,
270
- text: response,
270
+ text: reply.visibleText,
271
271
  timeoutMs,
272
272
  })
273
273
  return {}
@@ -0,0 +1,367 @@
1
+ import { getProvider } from '@/lib/providers'
2
+ import type { Connector } from '@/types'
3
+ import { loadAgents } from '../storage'
4
+ import { syncSessionArchiveMemory } from '../session-archive-memory'
5
+ import { resolvePairingAccess } from './access'
6
+ import {
7
+ addAllowedSender,
8
+ approvePairingCode,
9
+ createOrTouchPairingRequest,
10
+ listPendingPairingRequests,
11
+ listStoredAllowedSenders,
12
+ } from './pairing'
13
+ import {
14
+ buildConnectorDoctorWarnings,
15
+ resetConnectorSessionRuntime,
16
+ resolveConnectorSessionPolicy,
17
+ } from './policy'
18
+ import {
19
+ applyConnectorRuntimeDefaults,
20
+ applySessionSetting,
21
+ describeSessionControls,
22
+ persistSession,
23
+ pushSessionMessage,
24
+ type ConnectorAgent,
25
+ type ConnectorSession,
26
+ updateSessionConnectorContext,
27
+ } from './session'
28
+ import type { InboundMessage } from './types'
29
+ import { errorMessage } from '@/lib/shared-utils'
30
+
31
+ export type ConnectorCommandName =
32
+ | 'help'
33
+ | 'status'
34
+ | 'new'
35
+ | 'reset'
36
+ | 'compact'
37
+ | 'think'
38
+ | 'pair'
39
+ | 'session'
40
+ | 'focus'
41
+ | 'doctor'
42
+
43
+ export interface ParsedConnectorCommand {
44
+ name: ConnectorCommandName
45
+ args: string
46
+ }
47
+
48
+ export function parseConnectorCommand(text: string): ParsedConnectorCommand | null {
49
+ const trimmed = text.trim()
50
+ if (!trimmed.startsWith('/')) return null
51
+ const [head, ...rest] = trimmed.split(/\s+/)
52
+ const name = head.slice(1).toLowerCase()
53
+ const args = rest.join(' ').trim()
54
+ switch (name) {
55
+ case 'help':
56
+ case 'status':
57
+ case 'new':
58
+ case 'reset':
59
+ case 'compact':
60
+ case 'think':
61
+ case 'pair':
62
+ case 'session':
63
+ case 'focus':
64
+ case 'doctor':
65
+ return { name, args } as ParsedConnectorCommand
66
+ default:
67
+ return null
68
+ }
69
+ }
70
+
71
+ export async function handlePairCommand(params: {
72
+ connector: Connector
73
+ msg: InboundMessage
74
+ args: string
75
+ }): Promise<string> {
76
+ const { connector, msg, args } = params
77
+ const access = resolvePairingAccess(connector, msg)
78
+ const parts = args.split(/\s+/).map((item) => item.trim()).filter(Boolean)
79
+ const subcommand = (parts[0] || 'status').toLowerCase()
80
+
81
+ if (subcommand === 'request') {
82
+ const request = createOrTouchPairingRequest({
83
+ connectorId: connector.id,
84
+ senderId: msg.senderId,
85
+ senderName: msg.senderName,
86
+ channelId: msg.channelId,
87
+ })
88
+ return request.created
89
+ ? `Pairing request created. Share this code with an approved user: ${request.code}`
90
+ : `Pairing request is already pending. Your code is: ${request.code}`
91
+ }
92
+
93
+ if (subcommand === 'list') {
94
+ if (access.hasAnyApprover && !access.isAllowed) {
95
+ return 'Pairing list is restricted to approved senders.'
96
+ }
97
+ const pending = listPendingPairingRequests(connector.id)
98
+ if (!pending.length) return 'No pending pairing requests.'
99
+ const lines = pending.slice(0, 20).map((entry) => {
100
+ const ageMin = Math.max(1, Math.round((Date.now() - entry.updatedAt) / 60_000))
101
+ const sender = entry.senderName ? `${entry.senderName} (${entry.senderId})` : entry.senderId
102
+ return `- ${entry.code} -> ${sender} (${ageMin}m ago)`
103
+ })
104
+ return `Pending pairing requests (${pending.length}):\n${lines.join('\n')}`
105
+ }
106
+
107
+ if (subcommand === 'approve') {
108
+ const code = (parts[1] || '').trim()
109
+ if (!code) return 'Usage: /pair approve <code>'
110
+ if (access.hasAnyApprover && !access.isAllowed) {
111
+ return 'Pairing approvals are restricted to approved senders.'
112
+ }
113
+ const approved = approvePairingCode(connector.id, code)
114
+ if (!approved.ok) return approved.reason || 'Pairing approval failed.'
115
+ const sender = approved.senderName ? `${approved.senderName} (${approved.senderId})` : approved.senderId
116
+ return `Pairing approved: ${sender}`
117
+ }
118
+
119
+ if (subcommand === 'allow') {
120
+ const senderId = (parts[1] || '').trim()
121
+ if (!senderId) return 'Usage: /pair allow <senderId>'
122
+ if (access.hasAnyApprover && !access.isAllowed) {
123
+ return 'Allowlist updates are restricted to approved senders.'
124
+ }
125
+ const result = addAllowedSender(connector.id, senderId)
126
+ if (!result.normalized) return 'Could not parse senderId.'
127
+ return result.added
128
+ ? `Allowed sender: ${result.normalized}`
129
+ : `Sender is already allowed: ${result.normalized}`
130
+ }
131
+
132
+ const pending = listPendingPairingRequests(connector.id)
133
+ const stored = listStoredAllowedSenders(connector.id)
134
+ const policyLine = `Policy: ${access.policy}`
135
+ const approvedLine = `You are ${access.isAllowed ? 'approved' : 'not approved'} as ${msg.senderId}`
136
+ return [
137
+ 'Pairing controls:',
138
+ policyLine,
139
+ approvedLine,
140
+ `- Stored approvals: ${stored.length}`,
141
+ `- Pending requests: ${pending.length}`,
142
+ '- Commands: /pair request, /pair list, /pair approve <code>, /pair allow <senderId>',
143
+ ].join('\n')
144
+ }
145
+
146
+ function summarizeForCompaction(messages: Array<{ role?: string; text?: string }>): string {
147
+ const preview = messages
148
+ .slice(-8)
149
+ .map((message, index) => {
150
+ const role = (message.role || 'unknown').toUpperCase()
151
+ const body = (message.text || '').replace(/\s+/g, ' ').trim()
152
+ const clipped = body.length > 180 ? `${body.slice(0, 177)}...` : body
153
+ return `${index + 1}. [${role}] ${clipped || '(no text)'}`
154
+ })
155
+ if (!preview.length) return 'No earlier messages to summarize.'
156
+ return preview.join('\n')
157
+ }
158
+
159
+ export async function handleConnectorCommand(params: {
160
+ command: ParsedConnectorCommand
161
+ connector: Connector
162
+ session: ConnectorSession
163
+ msg: InboundMessage
164
+ agentName: string
165
+ inboundText: string
166
+ }): Promise<string> {
167
+ const { command, connector, session, msg, agentName, inboundText } = params
168
+
169
+ if (command.name === 'help') {
170
+ const text = [
171
+ 'Connector commands:',
172
+ '/status — Show active session status',
173
+ '/new or /reset — Clear this connector conversation thread',
174
+ '/compact [keepLastN] — Summarize older history and keep recent messages (default 10)',
175
+ '/think <minimal|low|medium|high> — Set connector thread reasoning guidance',
176
+ '/session — Show session controls',
177
+ '/session set <scope|reply|thread|group|idle|maxAge|resetMode|dailyResetAt|timezone|think|model|provider> <value> — Patch this connector session',
178
+ '/focus here|clear — Bind or clear focus on the current thread/topic',
179
+ '/doctor — Show autonomy and safety warnings for this connector/session',
180
+ '/pair — Pairing/access controls (status, request, list, approve, allow)',
181
+ '/help — Show this list',
182
+ ].join('\n')
183
+ pushSessionMessage(session, 'user', inboundText)
184
+ pushSessionMessage(session, 'assistant', text)
185
+ persistSession(session)
186
+ return text
187
+ }
188
+
189
+ if (command.name === 'status') {
190
+ const policy = resolveConnectorSessionPolicy(connector, msg, session)
191
+ const all = Array.isArray(session.messages) ? session.messages : []
192
+ const userCount = all.filter((message: { role?: string }) => message?.role === 'user').length
193
+ const assistantCount = all.filter((message: { role?: string }) => message?.role === 'assistant').length
194
+ const toolsCount = Array.isArray(session.plugins) ? session.plugins.length : 0
195
+ const statusText = [
196
+ `Status for ${connector.platform} / ${connector.name}:`,
197
+ `- Agent: ${agentName}`,
198
+ `- Session: ${session.id}`,
199
+ `- Model: ${session.provider}/${session.model}`,
200
+ `- Messages: ${all.length} (${userCount} user, ${assistantCount} assistant)`,
201
+ `- Tools enabled: ${toolsCount}`,
202
+ `- Channel: ${msg.channelName || msg.channelId}`,
203
+ `- Last active: ${new Date(session.lastActiveAt || session.createdAt || Date.now()).toLocaleString()}`,
204
+ `- Reset mode: ${policy.resetMode}`,
205
+ `- Reply mode: ${policy.replyMode}`,
206
+ `- Scope: ${policy.scope}`,
207
+ ].join('\n')
208
+ pushSessionMessage(session, 'user', inboundText)
209
+ pushSessionMessage(session, 'assistant', statusText)
210
+ persistSession(session)
211
+ return statusText
212
+ }
213
+
214
+ if (command.name === 'new' || command.name === 'reset') {
215
+ const agent = session.agentId ? (loadAgents() as Record<string, ConnectorAgent>)[session.agentId] : undefined
216
+ try { syncSessionArchiveMemory(session, { agent }) } catch { /* best effort */ }
217
+ const cleared = resetConnectorSessionRuntime(session, 'manual_reset')
218
+ const policy = resolveConnectorSessionPolicy(connector, msg, session)
219
+ const providerInfo = policy.providerOverride ? getProvider(policy.providerOverride) : null
220
+ applyConnectorRuntimeDefaults(session, {
221
+ provider: providerInfo?.id || session.provider,
222
+ model: policy.modelOverride || session.model,
223
+ apiEndpoint: providerInfo?.defaultEndpoint || session.apiEndpoint || null,
224
+ thinkingLevel: policy.thinkingLevel || session.connectorThinkLevel || null,
225
+ })
226
+ updateSessionConnectorContext(session, connector, msg, session.name || session.id)
227
+ persistSession(session)
228
+ return `Reset complete for ${connector.platform} channel thread. Cleared ${cleared} message(s).`
229
+ }
230
+
231
+ if (command.name === 'compact') {
232
+ const keepParsed = Number.parseInt(command.args, 10)
233
+ const keepLastN = Number.isFinite(keepParsed) ? Math.max(4, Math.min(50, keepParsed)) : 10
234
+ const history = Array.isArray(session.messages) ? session.messages : []
235
+ if (history.length <= keepLastN) {
236
+ const text = `Nothing to compact. Current history has ${history.length} message(s), keepLastN=${keepLastN}.`
237
+ pushSessionMessage(session, 'user', inboundText)
238
+ pushSessionMessage(session, 'assistant', text)
239
+ persistSession(session)
240
+ return text
241
+ }
242
+ const oldMessages = history.slice(0, -keepLastN)
243
+ const recentMessages = history.slice(-keepLastN)
244
+ const summary = summarizeForCompaction(oldMessages)
245
+ const summaryMessage = {
246
+ role: 'assistant' as const,
247
+ text: `[Context summary: compacted ${oldMessages.length} message(s)]\n${summary}`,
248
+ time: Date.now(),
249
+ kind: 'system' as const,
250
+ }
251
+ session.messages = [summaryMessage, ...recentMessages]
252
+ session.lastActiveAt = Date.now()
253
+ const text = `Compacted ${oldMessages.length} message(s). Kept ${recentMessages.length} recent message(s) plus a summary.`
254
+ pushSessionMessage(session, 'assistant', text)
255
+ persistSession(session)
256
+ return text
257
+ }
258
+
259
+ if (command.name === 'think') {
260
+ const requested = command.args.trim().toLowerCase()
261
+ const allowed = new Set(['minimal', 'low', 'medium', 'high'] as const)
262
+ if (!requested) {
263
+ const policy = resolveConnectorSessionPolicy(connector, msg, session)
264
+ const current = typeof policy.thinkingLevel === 'string' && allowed.has(policy.thinkingLevel)
265
+ ? policy.thinkingLevel
266
+ : 'medium'
267
+ const text = `Current /think level: ${current}. Usage: /think <minimal|low|medium|high>.`
268
+ pushSessionMessage(session, 'user', inboundText)
269
+ pushSessionMessage(session, 'assistant', text)
270
+ persistSession(session)
271
+ return text
272
+ }
273
+ if (
274
+ requested !== 'minimal'
275
+ && requested !== 'low'
276
+ && requested !== 'medium'
277
+ && requested !== 'high'
278
+ ) {
279
+ const text = 'Invalid /think level. Use one of: minimal, low, medium, high.'
280
+ pushSessionMessage(session, 'user', inboundText)
281
+ pushSessionMessage(session, 'assistant', text)
282
+ persistSession(session)
283
+ return text
284
+ }
285
+ session.connectorThinkLevel = requested
286
+ session.lastActiveAt = Date.now()
287
+ const text = `Set /think level to ${requested} for this connector thread.`
288
+ pushSessionMessage(session, 'user', inboundText)
289
+ pushSessionMessage(session, 'assistant', text)
290
+ persistSession(session)
291
+ return text
292
+ }
293
+
294
+ if (command.name === 'doctor') {
295
+ const warnings = buildConnectorDoctorWarnings({ connector, msg, session })
296
+ const text = warnings.length
297
+ ? ['Connector doctor:', ...warnings.map((item) => `- ${item}`)].join('\n')
298
+ : 'Connector doctor: no obvious autonomy or safety issues detected.'
299
+ pushSessionMessage(session, 'user', inboundText)
300
+ pushSessionMessage(session, 'assistant', text)
301
+ persistSession(session)
302
+ return text
303
+ }
304
+
305
+ if (command.name === 'session') {
306
+ const parts = command.args.split(/\s+/).map((item) => item.trim()).filter(Boolean)
307
+ if (!parts.length || parts[0].toLowerCase() === 'show' || parts[0].toLowerCase() === 'status') {
308
+ const text = describeSessionControls(session, connector, msg)
309
+ pushSessionMessage(session, 'user', inboundText)
310
+ pushSessionMessage(session, 'assistant', text)
311
+ persistSession(session)
312
+ return text
313
+ }
314
+ if (parts[0].toLowerCase() === 'reset') {
315
+ const agent = session.agentId ? (loadAgents() as Record<string, ConnectorAgent>)[session.agentId] : undefined
316
+ try { syncSessionArchiveMemory(session, { agent }) } catch { /* best effort */ }
317
+ const cleared = resetConnectorSessionRuntime(session, 'manual_reset')
318
+ const policy = resolveConnectorSessionPolicy(connector, msg, session)
319
+ const providerInfo = policy.providerOverride ? getProvider(policy.providerOverride) : null
320
+ applyConnectorRuntimeDefaults(session, {
321
+ provider: providerInfo?.id || session.provider,
322
+ model: policy.modelOverride || session.model,
323
+ apiEndpoint: providerInfo?.defaultEndpoint || session.apiEndpoint || null,
324
+ thinkingLevel: policy.thinkingLevel || session.connectorThinkLevel || null,
325
+ })
326
+ updateSessionConnectorContext(session, connector, msg, session.name || session.id)
327
+ persistSession(session)
328
+ return `Connector session reset. Cleared ${cleared} message(s).`
329
+ }
330
+ if (parts[0].toLowerCase() === 'set') {
331
+ const key = parts[1] || ''
332
+ const value = parts.slice(2).join(' ').trim()
333
+ if (!key) return 'Usage: /session set <scope|reply|thread|group|idle|maxAge|resetMode|dailyResetAt|timezone|think|model|provider> <value>'
334
+ try {
335
+ const text = applySessionSetting(session, key, value, msg)
336
+ updateSessionConnectorContext(session, connector, msg, session.name || session.id)
337
+ persistSession(session)
338
+ return text
339
+ } catch (err: unknown) {
340
+ return errorMessage(err)
341
+ }
342
+ }
343
+ return 'Usage: /session, /session show, /session set <key> <value>, /session reset'
344
+ }
345
+
346
+ if (command.name === 'focus') {
347
+ const subcommand = command.args.trim().toLowerCase()
348
+ if (subcommand === 'clear') {
349
+ session.connectorThreadBinding = null
350
+ session.connectorSessionScope = null
351
+ session.connectorContext = { ...(session.connectorContext || {}), threadId: null }
352
+ persistSession(session)
353
+ return 'Cleared connector thread focus.'
354
+ }
355
+ if (!msg.threadId) {
356
+ return 'Focus can only be set from a threaded or topic-bound message.'
357
+ }
358
+ session.connectorThreadBinding = 'strict'
359
+ session.connectorSessionScope = 'thread'
360
+ session.connectorReplyMode = session.connectorReplyMode || 'all'
361
+ session.connectorContext = { ...(session.connectorContext || {}), threadId: msg.threadId }
362
+ persistSession(session)
363
+ return `Focused this connector session on thread ${msg.threadId}.`
364
+ }
365
+
366
+ return 'Unknown command.'
367
+ }
@@ -1,9 +1,9 @@
1
1
  import { describe, it } from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
- import { getPlatform, isNoMessage, formatMediaLine, formatInboundUserText, extractEmbeddedMedia, selectOutboundMediaFiles } from './manager.ts'
4
- import { handleSignalEvent } from './signal.ts'
5
- import type { PlatformConnector } from './types.ts'
6
- import type { InboundMessage, InboundMedia } from './types.ts'
3
+ import { getPlatform, isNoMessage, formatMediaLine, formatInboundUserText, extractEmbeddedMedia, selectOutboundMediaFiles } from './manager'
4
+ import { handleSignalEvent } from './signal'
5
+ import type { PlatformConnector } from './types'
6
+ import type { InboundMessage, InboundMedia } from './types'
7
7
  import fs from 'node:fs'
8
8
  import path from 'node:path'
9
9
  import { UPLOAD_DIR } from '../storage'