hypervibes 0.0.0-stage → 0.4.0

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 (362) hide show
  1. package/.build/client/assets/_authenticated-C87LKvH0.js +1 -0
  2. package/.build/client/assets/_authenticated.account-D-nTh_fC.js +1 -0
  3. package/.build/client/assets/_authenticated.agents-HwcveLyB.js +1 -0
  4. package/.build/client/assets/_authenticated.agents._agentId-DuE8Lv62.js +1 -0
  5. package/.build/client/assets/_authenticated.agents._agentId.chart-Bb_XkvQx.js +4 -0
  6. package/.build/client/assets/_authenticated.agents._agentId.chat-ClMMKw_N.js +1 -0
  7. package/.build/client/assets/_authenticated.agents._agentId.index-DNJwBwn9.js +1 -0
  8. package/.build/client/assets/_authenticated.agents._agentId.indicators-BmVjLtRI.js +1 -0
  9. package/.build/client/assets/_authenticated.agents._agentId.indicators_._indicatorId-CMqaaFQf.js +2 -0
  10. package/.build/client/assets/_authenticated.agents._agentId.indicators_._indicatorId-DtgkoNV5.js +6 -0
  11. package/.build/client/assets/_authenticated.agents._agentId.memories-O-4f6a4G.js +1 -0
  12. package/.build/client/assets/_authenticated.agents._agentId.memories_._memoryRef-BUPtUcj3.js +1 -0
  13. package/.build/client/assets/_authenticated.agents._agentId.notifications-2lyhXCSk.js +1 -0
  14. package/.build/client/assets/_authenticated.agents._agentId.notifications_._notificationId-Da3ZVT4v.js +1 -0
  15. package/.build/client/assets/_authenticated.agents._agentId.settings-C5cWHoYw.js +1 -0
  16. package/.build/client/assets/_authenticated.agents._agentId.sub-agents-HwcveLyB.js +1 -0
  17. package/.build/client/assets/_authenticated.agents._agentId.sub-agents._subagentId-CZ4MeRy4.js +1 -0
  18. package/.build/client/assets/_authenticated.agents._agentId.sub-agents._subagentId_.runs._runId-dVbo5kMU.js +3 -0
  19. package/.build/client/assets/_authenticated.agents._agentId.sub-agents.index-DeWTOYJD.js +1 -0
  20. package/.build/client/assets/_authenticated.agents._agentId.sub-agents.new-D02TflCE.js +37 -0
  21. package/.build/client/assets/_authenticated.agents._agentId.transactions-D3a8Glgj.js +1 -0
  22. package/.build/client/assets/_authenticated.agents.index-CEqeDEgP.js +1 -0
  23. package/.build/client/assets/_authenticated.agents.new-DLRWK5Uk.js +1 -0
  24. package/.build/client/assets/_authenticated.providers-D0iPLXra.js +1 -0
  25. package/.build/client/assets/agent-chat-page-C5eYFgxw.js +1 -0
  26. package/.build/client/assets/agent-enablement-control-CUzOp9ce.js +1 -0
  27. package/.build/client/assets/agent-events-DC8eDZf3.js +1 -0
  28. package/.build/client/assets/agent-input-aolvZ47U.js +1 -0
  29. package/.build/client/assets/agent-notifications-page-B0mr-Zz8.js +1 -0
  30. package/.build/client/assets/agent-queries-CNMBImun.js +1 -0
  31. package/.build/client/assets/app-DjmsvbUC.css +2 -0
  32. package/.build/client/assets/artifact-content-COO4VTSZ.js +1 -0
  33. package/.build/client/assets/auth-client-K13li9H1.js +3 -0
  34. package/.build/client/assets/call-DxlGie4_.js +31 -0
  35. package/.build/client/assets/ccip-BgPlVPYP.js +1 -0
  36. package/.build/client/assets/chart-contracts-Cr5SFCbE.js +1 -0
  37. package/.build/client/assets/chart-queries-Bpvrgd0-.js +1 -0
  38. package/.build/client/assets/chat-bubble-CAJkT-vL.js +1 -0
  39. package/.build/client/assets/config-dialog-C9x9gKW9.js +1 -0
  40. package/.build/client/assets/contracts-HhAL7Tt7.js +1 -0
  41. package/.build/client/assets/createServerFn-DfZIp2Fu.js +3 -0
  42. package/.build/client/assets/custom-BeqXuIIS.js +5 -0
  43. package/.build/client/assets/decimal-B0DEG-1V.js +1 -0
  44. package/.build/client/assets/esm-BTb2nBdW.js +1 -0
  45. package/.build/client/assets/identicon-Lb9pS4O1.js +1 -0
  46. package/.build/client/assets/index-B4hvjMmI.js +10 -0
  47. package/.build/client/assets/indicator-input-fields-CtmrfVfS.js +1 -0
  48. package/.build/client/assets/indicator-queries-CzZ6u1Mh.js +1 -0
  49. package/.build/client/assets/indicator-renderer-CfccsIt2.js +1 -0
  50. package/.build/client/assets/instrument-label-BZYlWXR5.js +1 -0
  51. package/.build/client/assets/instrument-picker-DLPpryOm.js +1 -0
  52. package/.build/client/assets/journal-decimal-DBvd_Ibi.js +1 -0
  53. package/.build/client/assets/jsx-runtime-CIazjfqY.js +1 -0
  54. package/.build/client/assets/lazyRouteComponent-CJgUa7_r.js +1 -0
  55. package/.build/client/assets/lightweight-charts.production-BiBiFZSH.js +1 -0
  56. package/.build/client/assets/login-MNgYxCOY.js +3 -0
  57. package/.build/client/assets/memory-badge-BbN-DJQi.js +1 -0
  58. package/.build/client/assets/memory-contracts-Doi_Q1XJ.js +1 -0
  59. package/.build/client/assets/memory-queries-BttL580R.js +1 -0
  60. package/.build/client/assets/model-picker-YCQNNmyw.js +1 -0
  61. package/.build/client/assets/notification-queries-B12DG3aS.js +1 -0
  62. package/.build/client/assets/preload-helper-Czpn1I53.js +1 -0
  63. package/.build/client/assets/provider-logo-Ddb77aBD.js +1 -0
  64. package/.build/client/assets/provider-queries-DxPwhDOU.js +1 -0
  65. package/.build/client/assets/query-nvK_95Jo.js +1 -0
  66. package/.build/client/assets/random-uuid-DJR3ygJS.js +1 -0
  67. package/.build/client/assets/react-dom-C6bOPxes.js +1 -0
  68. package/.build/client/assets/risk-queries-D_6PHwus.js +1 -0
  69. package/.build/client/assets/run-subagent-dialog-B4yQe9T9.js +1 -0
  70. package/.build/client/assets/secp256k1-BbZnIgDP.js +1 -0
  71. package/.build/client/assets/setup-checklist-CUW-K484.js +1 -0
  72. package/.build/client/assets/sha2-hT-Q4pur.js +1 -0
  73. package/.build/client/assets/subagent-enabled-toggle-Bk5wfScg.js +1 -0
  74. package/.build/client/assets/subagent-input-TQke90I1.js +1 -0
  75. package/.build/client/assets/subagent-mutations-DfsDbmFu.js +1 -0
  76. package/.build/client/assets/subagent-queries-DVv5JCfs.js +1 -0
  77. package/.build/client/assets/subagent-run-history-D55Uh5VK.js +1 -0
  78. package/.build/client/assets/toggle-timestamp-B50FTPmA.js +1 -0
  79. package/.build/client/assets/useBaseQuery-Bxruo_mV.js +1 -0
  80. package/.build/client/assets/useMutation-DrF53EQG.js +1 -0
  81. package/.build/client/assets/useQuery-BMSU16jY.js +1 -0
  82. package/.build/client/assets/useRouter-c0I1aURm.js +1 -0
  83. package/.build/client/assets/useSelector-ERD9RroG.js +2 -0
  84. package/.build/client/assets/useSuspenseQuery-vODFwDb8.js +1 -0
  85. package/.build/client/assets/utils-Bpv4vmSW.js +1 -0
  86. package/.build/client/assets/worker-triggers-DtXyn40O.js +1 -0
  87. package/.build/server/assets/_authenticated-Bch0_2QH.js +264 -0
  88. package/.build/server/assets/_authenticated-DGV5cZYE.js +22 -0
  89. package/.build/server/assets/_authenticated.account-6qu6oQ1y.js +685 -0
  90. package/.build/server/assets/_authenticated.agents-DYDnQxli.js +5 -0
  91. package/.build/server/assets/_authenticated.agents._agentId-4HvB3mhd.js +17 -0
  92. package/.build/server/assets/_authenticated.agents._agentId-Cl608F1T.js +260 -0
  93. package/.build/server/assets/_authenticated.agents._agentId-G2jAHjma.js +15 -0
  94. package/.build/server/assets/_authenticated.agents._agentId.chart-BIA8wy73.js +2644 -0
  95. package/.build/server/assets/_authenticated.agents._agentId.chart-D4aj8Qy9.js +22 -0
  96. package/.build/server/assets/_authenticated.agents._agentId.chat-ClsVle4g.js +13 -0
  97. package/.build/server/assets/_authenticated.agents._agentId.chat-JV3FDsTp.js +18 -0
  98. package/.build/server/assets/_authenticated.agents._agentId.index-Ct4kbu2f.js +1044 -0
  99. package/.build/server/assets/_authenticated.agents._agentId.index-DJgrupvW.js +18 -0
  100. package/.build/server/assets/_authenticated.agents._agentId.indicators-DrKd0aP5.js +184 -0
  101. package/.build/server/assets/_authenticated.agents._agentId.indicators-wiVaY8fW.js +18 -0
  102. package/.build/server/assets/_authenticated.agents._agentId.indicators_._indicatorId-C4Ox2R-a.js +25 -0
  103. package/.build/server/assets/_authenticated.agents._agentId.indicators_._indicatorId-D8Q0hCW0.js +489 -0
  104. package/.build/server/assets/_authenticated.agents._agentId.memories-BUTyVLYy.js +42 -0
  105. package/.build/server/assets/_authenticated.agents._agentId.memories-E8-nSEMT.js +362 -0
  106. package/.build/server/assets/_authenticated.agents._agentId.memories_._memoryRef-C-SC6wzq.js +14 -0
  107. package/.build/server/assets/_authenticated.agents._agentId.memories_._memoryRef-ZCrdrc8C.js +490 -0
  108. package/.build/server/assets/_authenticated.agents._agentId.notifications-DQeQqTzh.js +14 -0
  109. package/.build/server/assets/_authenticated.agents._agentId.notifications-DlFyYyHh.js +16 -0
  110. package/.build/server/assets/_authenticated.agents._agentId.notifications_._notificationId-B0RosY_R.js +15 -0
  111. package/.build/server/assets/_authenticated.agents._agentId.notifications_._notificationId-C1NikQ9e.js +13 -0
  112. package/.build/server/assets/_authenticated.agents._agentId.settings-DDwjT3Ui.js +42 -0
  113. package/.build/server/assets/_authenticated.agents._agentId.settings-PEzEvOfo.js +751 -0
  114. package/.build/server/assets/_authenticated.agents._agentId.sub-agents-z34-J3Tu.js +5 -0
  115. package/.build/server/assets/_authenticated.agents._agentId.sub-agents._subagentId-D-6TOi1U.js +1371 -0
  116. package/.build/server/assets/_authenticated.agents._agentId.sub-agents._subagentId-DipzVdnr.js +13 -0
  117. package/.build/server/assets/_authenticated.agents._agentId.sub-agents._subagentId_.runs._runId-BZR3VLV7.js +610 -0
  118. package/.build/server/assets/_authenticated.agents._agentId.sub-agents._subagentId_.runs._runId-DsYJKfGm.js +12 -0
  119. package/.build/server/assets/_authenticated.agents._agentId.sub-agents.index-BF8Cuzcr.js +231 -0
  120. package/.build/server/assets/_authenticated.agents._agentId.sub-agents.index-D2x0qCnt.js +12 -0
  121. package/.build/server/assets/_authenticated.agents._agentId.sub-agents.new-CYm_vuDD.js +13 -0
  122. package/.build/server/assets/_authenticated.agents._agentId.sub-agents.new-CiVShIka.js +497 -0
  123. package/.build/server/assets/_authenticated.agents._agentId.transactions-BC_XfGqw.js +95 -0
  124. package/.build/server/assets/_authenticated.agents._agentId.transactions-WtBW3xmh.js +495 -0
  125. package/.build/server/assets/_authenticated.agents.index-C8zmu8T8.js +78 -0
  126. package/.build/server/assets/_authenticated.agents.new-Bt-tBR47.js +302 -0
  127. package/.build/server/assets/_authenticated.providers-C0pb-ujV.js +672 -0
  128. package/.build/server/assets/_tanstack-start-manifest_v-Baw5u9sk.js +354 -0
  129. package/.build/server/assets/account-XvMwzRXh.js +66 -0
  130. package/.build/server/assets/account-queries-AJJVkLAV.js +32 -0
  131. package/.build/server/assets/agent-chat-page-DChiz8KQ.js +686 -0
  132. package/.build/server/assets/agent-enablement-control-C90f0uIb.js +143 -0
  133. package/.build/server/assets/agent-events-DK9Yz5__.js +217 -0
  134. package/.build/server/assets/agent-notifications-page-BdyKAyds.js +276 -0
  135. package/.build/server/assets/agent-queries-Cmx8RiUx.js +51 -0
  136. package/.build/server/assets/agents-CZq-0mna.js +106 -0
  137. package/.build/server/assets/agents-CfjmtW7A.js +278 -0
  138. package/.build/server/assets/artifact-content-B6uHmPz7.js +35 -0
  139. package/.build/server/assets/artifacts-HtLwHs8q.js +42 -0
  140. package/.build/server/assets/auth-DTikZAVz.js +12 -0
  141. package/.build/server/assets/auth-client-Vb3O0v7P.js +1897 -0
  142. package/.build/server/assets/auth-user-DkLo8ojH.js +108 -0
  143. package/.build/server/assets/chart-queries-Bc3AfsEb.js +109 -0
  144. package/.build/server/assets/charts-C4dKuwTJ.js +130 -0
  145. package/.build/server/assets/chat-bubble-ik3itVbx.js +109 -0
  146. package/.build/server/assets/config-dialog-9gSAjwwT.js +55 -0
  147. package/.build/server/assets/contracts-CG-nZuYJ.js +155 -0
  148. package/.build/server/assets/createServerRpc-DNqhdU4z.js +12 -0
  149. package/.build/server/assets/decimal-BthtZ77w.js +97 -0
  150. package/.build/server/assets/empty-plugin-adapters-MOWBbCMw.js +5 -0
  151. package/.build/server/assets/errors-DaZqUftX.js +151 -0
  152. package/.build/server/assets/gateway-CHkruO_a.js +193 -0
  153. package/.build/server/assets/identicon-CyawcVnx.js +41 -0
  154. package/.build/server/assets/indicator-input-fields-bzuhIh6t.js +75 -0
  155. package/.build/server/assets/indicator-queries-4dY1-LXx.js +171 -0
  156. package/.build/server/assets/indicator-renderer-B2muPS-8.js +395 -0
  157. package/.build/server/assets/indicators-ENxAHN42.js +192 -0
  158. package/.build/server/assets/insight-contracts-B5rmUDfO.js +14 -0
  159. package/.build/server/assets/instrument-label-C-tTGGsD.js +40 -0
  160. package/.build/server/assets/instrument-picker-CRroLwVq.js +381 -0
  161. package/.build/server/assets/journal-9aE-um9f.js +107 -0
  162. package/.build/server/assets/journal-decimal-Bh9EL029.js +66 -0
  163. package/.build/server/assets/login-BsEFUvTX.js +125 -0
  164. package/.build/server/assets/memories-2WF2fW1G.js +100 -0
  165. package/.build/server/assets/memory-badge-DVYLTtRR.js +53 -0
  166. package/.build/server/assets/memory-contracts-DQGN2IKV.js +22 -0
  167. package/.build/server/assets/memory-queries--4RbPRrk.js +92 -0
  168. package/.build/server/assets/model-picker-CA1uYb--.js +225 -0
  169. package/.build/server/assets/notification-queries-0CJUkBt9.js +52 -0
  170. package/.build/server/assets/notifications-Cn7BiCyw.js +72 -0
  171. package/.build/server/assets/provider-logo-BRdbOr9I.js +32 -0
  172. package/.build/server/assets/provider-queries-BaZWLw8J.js +78 -0
  173. package/.build/server/assets/providers-CyoN_jgW.js +89 -0
  174. package/.build/server/assets/risk-Bqrb6hKQ.js +34 -0
  175. package/.build/server/assets/risk-queries-JQpByHT2.js +18 -0
  176. package/.build/server/assets/router-Hkr5TH_I.js +419 -0
  177. package/.build/server/assets/run-subagent-dialog-C7iUOZa-.js +59 -0
  178. package/.build/server/assets/setup-DqMlwgNr.js +30 -0
  179. package/.build/server/assets/setup-checklist-BICTmBo2.js +60 -0
  180. package/.build/server/assets/start-C-GPoWRt.js +4 -0
  181. package/.build/server/assets/subagent-enabled-toggle-Djte00oo.js +22 -0
  182. package/.build/server/assets/subagent-input-BHQKs2cW.js +57 -0
  183. package/.build/server/assets/subagent-mutations-dUo3B_SG.js +32 -0
  184. package/.build/server/assets/subagent-queries-sAwBjFxC.js +146 -0
  185. package/.build/server/assets/subagent-run-history-C_xmucO-.js +186 -0
  186. package/.build/server/assets/subagents-CIoilC66.js +180 -0
  187. package/.build/server/assets/toggle-timestamp-CToUVn67.js +28 -0
  188. package/.build/server/assets/trading-contracts-zWq1Hxnb.js +26 -0
  189. package/.build/server/assets/validation-BX-krL0i.js +33 -0
  190. package/.build/server/assets/worker-triggers-CJF5HWzR.js +51 -0
  191. package/.build/server/server.js +7501 -0
  192. package/LICENSE +661 -0
  193. package/README.md +145 -2
  194. package/bin/hypervibes.js +24 -0
  195. package/dist/agents/agent-enablement.js +10 -0
  196. package/dist/agents/agent-input.js +38 -0
  197. package/dist/agents/agent-service.js +1004 -0
  198. package/dist/agents/cancel-task-scope.js +52 -0
  199. package/dist/agents/candle-intervals.js +1 -0
  200. package/dist/agents/default-subagents.js +36 -0
  201. package/dist/agents/execution-context-contracts.js +1 -0
  202. package/dist/agents/execution-context.js +206 -0
  203. package/dist/agents/execution-warnings.js +33 -0
  204. package/dist/agents/orchestrator-insights.js +14 -0
  205. package/dist/agents/run-inspection.js +99 -0
  206. package/dist/agents/subagent-dashboard.js +1 -0
  207. package/dist/agents/subagent-extension.js +156 -0
  208. package/dist/agents/subagent-history.js +5 -0
  209. package/dist/agents/subagent-input.js +73 -0
  210. package/dist/agents/subagent-service.js +1177 -0
  211. package/dist/agents/subagent-templates.js +20 -0
  212. package/dist/agents/template-manifest.js +34 -0
  213. package/dist/agents/templates/basic-analysis.json +40 -0
  214. package/dist/agents/templates/basic-review.json +48 -0
  215. package/dist/agents/templates/basic-trading.json +45 -0
  216. package/dist/agents/templates/indicator-developer.json +16 -0
  217. package/dist/agents/time-guidance.js +29 -0
  218. package/dist/agents/worker-events.js +18 -0
  219. package/dist/agents/worker-inbox.js +28 -0
  220. package/dist/agents/worker-triggers.js +23 -0
  221. package/dist/auth-options.js +98 -0
  222. package/dist/auth-user.js +15 -0
  223. package/dist/auth.js +28 -0
  224. package/dist/cache/svg-logo-cache.js +155 -0
  225. package/dist/canonical.js +13 -0
  226. package/dist/charts/chart-service.js +236 -0
  227. package/dist/charts/contracts.js +118 -0
  228. package/dist/charts/durable-extension.js +27 -0
  229. package/dist/charts/trading-contracts.js +14 -0
  230. package/dist/config.js +85 -0
  231. package/dist/db/agent-schema.js +26 -0
  232. package/dist/db/auth-schema.js +122 -0
  233. package/dist/db/database.js +31 -0
  234. package/dist/db/exchange-schema.js +23 -0
  235. package/dist/db/gateway-schema.js +24 -0
  236. package/dist/db/indicator-schema.js +19 -0
  237. package/dist/db/migrations/20261015221717_auth/migration.sql +62 -0
  238. package/dist/db/migrations/20261015231224_agents/migration.sql +35 -0
  239. package/dist/db/migrations/20261016040641_provider_connections/migration.sql +19 -0
  240. package/dist/db/migrations/20261016061831_trade_journal/migration.sql +48 -0
  241. package/dist/db/migrations/20261016080320_order_execution/migration.sql +62 -0
  242. package/dist/db/migrations/20261016181110_subagents/migration.sql +51 -0
  243. package/dist/db/migrations/20261017222959_research_artifacts/migration.sql +37 -0
  244. package/dist/db/migrations/20261018061531_memories_and_insights/migration.sql +162 -0
  245. package/dist/db/migrations/20261020091156_worker_event_inbox/migration.sql +54 -0
  246. package/dist/db/migrations/20261021051229_chart_workspace/migration.sql +33 -0
  247. package/dist/db/migrations/20261021222116_gateways/migration.sql +15 -0
  248. package/dist/db/migrations/20261022015813_pine_indicators/migration.sql +43 -0
  249. package/dist/db/migrations/20261022101559_notifications/migration.sql +51 -0
  250. package/dist/db/migrations.js +12 -0
  251. package/dist/db/order-provenance-schema.js +17 -0
  252. package/dist/db/order-schema.js +32 -0
  253. package/dist/db/profile-lock.js +12 -0
  254. package/dist/db/provider-schema.js +16 -0
  255. package/dist/db/shared-database.js +128 -0
  256. package/dist/db/subagent-schema.js +79 -0
  257. package/dist/gateway/bridge-service.js +223 -0
  258. package/dist/gateway/gateway-store.js +77 -0
  259. package/dist/gateway/handlers.js +122 -0
  260. package/dist/gateway/telegram/connector.js +115 -0
  261. package/dist/gateway/telegram/transport.js +96 -0
  262. package/dist/hyperliquid/account-info.js +117 -0
  263. package/dist/hyperliquid/account-status.js +64 -0
  264. package/dist/hyperliquid/agent-dashboard.js +56 -0
  265. package/dist/hyperliquid/chart-contracts.js +9 -0
  266. package/dist/hyperliquid/constants.js +25 -0
  267. package/dist/hyperliquid/crypto.js +29 -0
  268. package/dist/hyperliquid/currency-logo.js +7 -0
  269. package/dist/hyperliquid/decimal.js +98 -0
  270. package/dist/hyperliquid/durable-extension.js +39 -0
  271. package/dist/hyperliquid/errors.js +8 -0
  272. package/dist/hyperliquid/exchange.js +118 -0
  273. package/dist/hyperliquid/journal-decimal.js +48 -0
  274. package/dist/hyperliquid/journal-model.js +167 -0
  275. package/dist/hyperliquid/journal-service.js +648 -0
  276. package/dist/hyperliquid/live-dashboard.js +245 -0
  277. package/dist/hyperliquid/logo-cache.js +27 -0
  278. package/dist/hyperliquid/market-charts.js +242 -0
  279. package/dist/hyperliquid/market-snapshot.js +45 -0
  280. package/dist/hyperliquid/orders/order-contracts.js +42 -0
  281. package/dist/hyperliquid/orders/order-math.js +89 -0
  282. package/dist/hyperliquid/orders/order-outcomes.js +74 -0
  283. package/dist/hyperliquid/orders/order-prepare.js +109 -0
  284. package/dist/hyperliquid/orders/order-service.js +432 -0
  285. package/dist/hyperliquid/orders/order-store.js +134 -0
  286. package/dist/hyperliquid/orders/order-tools.js +40 -0
  287. package/dist/hyperliquid/orders/protection.js +35 -0
  288. package/dist/hyperliquid/orders/risk-settings.js +44 -0
  289. package/dist/hyperliquid/orders/trade-admission.js +117 -0
  290. package/dist/hyperliquid/orders/trade-planning.js +60 -0
  291. package/dist/hyperliquid/service.js +529 -0
  292. package/dist/hyperliquid/shared-feeds.js +40 -0
  293. package/dist/hyperliquid/signing.js +89 -0
  294. package/dist/indicators/builtin-catalog.js +59 -0
  295. package/dist/indicators/builtins/adx.pine +22 -0
  296. package/dist/indicators/builtins/atr.pine +11 -0
  297. package/dist/indicators/builtins/bollinger.pine +10 -0
  298. package/dist/indicators/builtins/ema.pine +5 -0
  299. package/dist/indicators/builtins/macd.pine +18 -0
  300. package/dist/indicators/builtins/rsi.pine +18 -0
  301. package/dist/indicators/builtins/sma.pine +5 -0
  302. package/dist/indicators/candle-window.js +36 -0
  303. package/dist/indicators/chart-contracts.js +64 -0
  304. package/dist/indicators/contracts.js +14 -0
  305. package/dist/indicators/durable-extension.js +32 -0
  306. package/dist/indicators/indicator-service.js +300 -0
  307. package/dist/indicators/indicator-store.js +137 -0
  308. package/dist/indicators/normalize.js +149 -0
  309. package/dist/indicators/result-schema.js +79 -0
  310. package/dist/indicators/runtime-wrapper.js +52 -0
  311. package/dist/indicators/runtime.js +201 -0
  312. package/dist/indicators/validation.js +124 -0
  313. package/dist/indicators/worker-queue.js +48 -0
  314. package/dist/initialize-config.js +160 -0
  315. package/dist/main.js +59 -0
  316. package/dist/memory/decision-audit.js +47 -0
  317. package/dist/memory/durable-extension.js +40 -0
  318. package/dist/memory/execution-audit.js +83 -0
  319. package/dist/memory/insight-contracts.js +33 -0
  320. package/dist/memory/insight-service.js +117 -0
  321. package/dist/memory/memory-contracts.js +113 -0
  322. package/dist/memory/memory-service.js +179 -0
  323. package/dist/memory/operations.js +46 -0
  324. package/dist/memory/tool-schemas.js +44 -0
  325. package/dist/memory/validation.js +42 -0
  326. package/dist/notifications/contracts.js +1 -0
  327. package/dist/notifications/delivery.js +11 -0
  328. package/dist/notifications/durable-extension.js +20 -0
  329. package/dist/notifications/notification-service.js +324 -0
  330. package/dist/providers/credential-store.js +86 -0
  331. package/dist/providers/ollama-cloud.js +148 -0
  332. package/dist/providers/provider-logo-url.js +18 -0
  333. package/dist/providers/provider-logo.js +31 -0
  334. package/dist/providers/provider-service.js +295 -0
  335. package/dist/research/artifacts.js +151 -0
  336. package/dist/research/call-queue.js +43 -0
  337. package/dist/research/contracts.js +37 -0
  338. package/dist/research/evidence.js +43 -0
  339. package/dist/research/journal.js +30 -0
  340. package/dist/research/research-service.js +191 -0
  341. package/dist/research/tool-activity.js +49 -0
  342. package/dist/research/tool-response.js +25 -0
  343. package/dist/runtime.js +145 -0
  344. package/dist/web/agent-events.js +418 -0
  345. package/dist/web/app.js +206 -0
  346. package/dist/web/artifact-downloads.js +23 -0
  347. package/dist/web/middleware.js +25 -0
  348. package/dist/web/random-uuid.js +14 -0
  349. package/dist/web/start.js +56 -0
  350. package/package.json +93 -3
  351. package/scripts/check-node.mjs +24 -0
  352. package/static/android-chrome-192x192.png +0 -0
  353. package/static/android-chrome-512x512.png +0 -0
  354. package/static/apple-touch-icon.png +0 -0
  355. package/static/favicon-16x16.png +0 -0
  356. package/static/favicon-32x32.png +0 -0
  357. package/static/favicon.ico +0 -0
  358. package/static/github.svg +3 -0
  359. package/static/hypervibes.png +0 -0
  360. package/static/logo.png +0 -0
  361. package/static/site.webmanifest +1 -0
  362. package/static/telegram.svg +1 -0
@@ -0,0 +1,1177 @@
1
+ import { BACKGROUND_CONTEXT as context, withAbortSignal } from "@earendil-works/chord/context";
2
+ import { getSupportedThinkingLevels } from "@earendil-works/pi-ai/models";
3
+ import { defineDoc, defineDocFamily, InboxDoc, LiveDoc } from "@earendil-works/pi-durable";
4
+ import { and, asc, count, desc, eq, inArray, isNull, lte, or, sql } from "drizzle-orm";
5
+ import { randomUUID } from "node:crypto";
6
+ import { agentExecutable, assertAgentExecutable } from "./agent-enablement.js";
7
+ import { cancelTaskScope } from "./cancel-task-scope.js";
8
+ import { hvSubagents, hvSubagentRuns, hvWorkerRequests } from "../db/subagent-schema.js";
9
+ import { runHistoryPageSize, runPagination } from "./subagent-history.js";
10
+ import { AgentError, requestId } from "./agent-input.js";
11
+ import { createSubagentExtension, RunReceipt } from "./subagent-extension.js";
12
+ import { executionWarnings } from "./execution-warnings.js";
13
+ import { toolActivity } from "../research/tool-activity.js";
14
+ import { chartToolNames } from "../charts/contracts.js";
15
+ import { bodyHash, canonical } from "../canonical.js";
16
+ import { subagentTemplate } from "./subagent-templates.js";
17
+ import { workerTriggers } from "./worker-triggers.js";
18
+ import { insertDefaultSubagents } from "./default-subagents.js";
19
+ import { requestBatch, inboxAssignment } from "./worker-inbox.js";
20
+ import { inspectRun, listRuns } from "./run-inspection.js";
21
+ import { assertActiveActor } from "../memory/operations.js";
22
+ import { assignment, defaultMainInstructions, mainToolNames, nextCandleRun, normalizeDelegationTools, subagentInput } from "./subagent-input.js";
23
+ import { timeGuidanceExtension } from "./time-guidance.js";
24
+ const active = ["queued", "running"];
25
+ const parkedRunReason = "Automatic recovery stopped after repeated interruptions. Existing exchange orders were not cancelled. Inspect account/order state before starting a new assignment.";
26
+ /** A public, durable cancellation intent. Never clear it before all work drains. */
27
+ export const ParkedRunRecovery = defineDocFamily({
28
+ kind: "hv.parked-run-recovery", version: 1, family: true, scope: "conversation", history: "latest", fork: "initial",
29
+ initial: () => ({ pending: false, done: false, conversationId: null }), checkpointWhen: () => false,
30
+ });
31
+ /** Drizzle and Durable can wrap native SQLite errors in a cause. */
32
+ function sqliteFailure(error, seen = new Set()) {
33
+ if (!error || typeof error !== "object" || seen.has(error))
34
+ return false;
35
+ seen.add(error);
36
+ const code = Reflect.get(error, "code");
37
+ return (typeof code === "string" && code.startsWith("ERR_SQLITE_"))
38
+ || sqliteFailure(Reflect.get(error, "cause"), seen)
39
+ || (error instanceof AggregateError && error.errors.some((item) => sqliteFailure(item, seen)));
40
+ }
41
+ // Public Durable document: an idempotent, schema-free upgrade marker. Once
42
+ // applied, an explicit later tool deselection survives subsequent restarts.
43
+ export const MarketSnapshotUpgradeDoc = defineDoc({
44
+ kind: "hv.market-snapshot-upgrade", version: 1, scope: "conversation", history: "latest", fork: "initial",
45
+ initial: () => ({ applied: false }), checkpointWhen: () => false,
46
+ });
47
+ export const ChartToolsUpgradeDoc = defineDoc({
48
+ kind: "hv.chart-tools-upgrade", version: 1, scope: "conversation", history: "latest", fork: "initial", initial: () => ({ applied: false }), checkpointWhen: () => false,
49
+ });
50
+ /** Definitions and run intents belong to the app; Durable owns execution and reporting. */
51
+ export class SubagentService {
52
+ db;
53
+ database;
54
+ providers;
55
+ host;
56
+ execution;
57
+ notifications;
58
+ injectedModels;
59
+ log;
60
+ bindings = new Map();
61
+ stoppedAgents = new Set();
62
+ pending = new Map();
63
+ timer;
64
+ ticking = Promise.resolve();
65
+ tickActive = false;
66
+ ready = false;
67
+ starting;
68
+ stopping = false;
69
+ draining;
70
+ dashboardListeners = new Map();
71
+ historyListeners = new Map();
72
+ historyShutdown = new AbortController();
73
+ historyFeeds = new Set();
74
+ schedulerFailure = null;
75
+ get schedulerError() { return this.schedulerFailure; }
76
+ set schedulerError(value) {
77
+ if (this.schedulerFailure === value)
78
+ return;
79
+ this.schedulerFailure = value;
80
+ for (const listeners of this.historyListeners.values())
81
+ for (const listener of listeners)
82
+ listener();
83
+ }
84
+ dashboardChanged(agentId) { for (const listener of this.dashboardListeners.get(agentId) ?? [])
85
+ listener(); }
86
+ historyChanged(agentId, id) {
87
+ for (const listener of this.historyListeners.get(id) ?? [])
88
+ listener();
89
+ this.dashboardChanged(agentId);
90
+ }
91
+ constructor(db, database, providers, host, execution, notifications, injectedModels = false, log) {
92
+ this.db = db;
93
+ this.database = database;
94
+ this.providers = providers;
95
+ this.host = host;
96
+ this.execution = execution;
97
+ this.notifications = notifications;
98
+ this.injectedModels = injectedModels;
99
+ this.log = log;
100
+ }
101
+ extension(agentId, hyperliquid, capabilities) { return createSubagentExtension(this, agentId, hyperliquid, capabilities, this.execution); }
102
+ async bind(agent, harness, main, hyperliquid, execution, capabilities, orchestratorInsights) {
103
+ const resolved = await main.agent(context);
104
+ const now = Date.now();
105
+ await this.db.insert(hvSubagents).values({
106
+ id: `main:${agent.id}`, agentId: agent.id, kind: "orchestrator", name: "Orchestrator", description: "Main conversation", instructions: defaultMainInstructions,
107
+ provider: resolved.model.provider, modelId: resolved.model.modelId, thinkingLevel: resolved.thinkingLevel ?? "", tools: mainToolNames, conversationId: main.id, createdAt: now, updatedAt: now,
108
+ }).onConflictDoUpdate({ target: hvSubagents.id, set: { conversationId: main.id } }).run();
109
+ const binding = { harness, main, hyperliquid, capabilities, orchestratorInsights, ...execution, userId: agent.userId };
110
+ // Normalize saved selections at startup; historical run snapshots stay immutable.
111
+ for (const saved of await this.db.select().from(hvSubagents).where(eq(hvSubagents.agentId, agent.id)).all()) {
112
+ const tools = normalizeDelegationTools(saved.tools);
113
+ if (canonical(tools) !== canonical(saved.tools))
114
+ await this.db.update(hvSubagents).set({ tools, updatedAt: now }).where(eq(hvSubagents.id, saved.id)).run();
115
+ }
116
+ const row = (await this.db.select().from(hvSubagents).where(and(eq(hvSubagents.agentId, agent.id), eq(hvSubagents.kind, "orchestrator"))).get());
117
+ const upgraded = await main.commit(async (tx) => (await tx.doc(MarketSnapshotUpgradeDoc, main.id)).applied, context);
118
+ if (!upgraded) {
119
+ if (!row.tools.includes("get_market_snapshot")) {
120
+ row.tools = [...row.tools, "get_market_snapshot"];
121
+ await this.db.update(hvSubagents).set({ tools: row.tools, updatedAt: now }).where(eq(hvSubagents.id, row.id)).run();
122
+ }
123
+ // Separate commits avoid nesting the shared database queue. A crash before
124
+ // the marker simply retries the idempotent append on the next startup.
125
+ await main.commit(async (tx) => { (await tx.doc(MarketSnapshotUpgradeDoc, main.id)).applied = true; }, context);
126
+ }
127
+ if (!await main.commit(async (tx) => (await tx.doc(ChartToolsUpgradeDoc, main.id)).applied, context)) {
128
+ row.tools = [...new Set([...row.tools, ...chartToolNames])];
129
+ await this.db.update(hvSubagents).set({ tools: row.tools, updatedAt: now }).where(eq(hvSubagents.id, row.id)).run();
130
+ await main.commit(async (tx) => { (await tx.doc(ChartToolsUpgradeDoc, main.id)).applied = true; }, context);
131
+ }
132
+ await this.applyMain(binding, row);
133
+ await this.recoverParkedRuns(agent.id, binding, true);
134
+ // Mark expired children's pending generations/tools before *any* scheduler
135
+ // resumes. abortTask only marks while paused; unlike conversation.abort it
136
+ // does not enable scheduling. The runner then records/reports the timeout.
137
+ const interrupted = await this.db.select().from(hvSubagentRuns).where(and(eq(hvSubagentRuns.agentId, agent.id), inArray(hvSubagentRuns.status, active))).all();
138
+ const expiredChildren = new Set(interrupted.filter((run) => run.conversationId !== null && Date.now() >= run.createdAt + run.config.timeoutSeconds * 1000).map((run) => run.conversationId));
139
+ if (expiredChildren.size)
140
+ for (const task of (await harness.inspect(context)).tasks) {
141
+ if (expiredChildren.has(task.record.conversationId))
142
+ await harness.abortTask(task.record.id, context);
143
+ }
144
+ this.bindings.set(agent.id, binding);
145
+ }
146
+ unbind(agentId) { this.bindings.delete(agentId); }
147
+ async stopAgent(agentId) {
148
+ this.stoppedAgents.add(agentId);
149
+ await this.pending.get(agentId);
150
+ await this.db.update(hvSubagents).set({ enabled: false, nextRunAt: null }).where(and(eq(hvSubagents.agentId, agentId), eq(hvSubagents.kind, "worker"))).run();
151
+ }
152
+ /** Clear application queues without changing each worker's configured enablement. */
153
+ async discardPending(agentId) {
154
+ const now = Date.now();
155
+ const affected = await this.database.appTransaction(async (db, tx) => {
156
+ const discarded = await db.update(hvWorkerRequests).set({ discardedReason: "Agent disabled; request will not replay" }).where(and(eq(hvWorkerRequests.agentId, agentId), isNull(hvWorkerRequests.runId), isNull(hvWorkerRequests.discardedReason))).returning().all();
157
+ for (const request of discarded)
158
+ await this.requestWarningTx(tx, request, request.discardedReason);
159
+ const definitions = await db.select().from(hvSubagents).where(and(eq(hvSubagents.agentId, agentId), eq(hvSubagents.kind, "worker"))).all();
160
+ for (const row of definitions)
161
+ await db.update(hvSubagents).set({ nextRunAt: row.enabled && !row.archived ? nextCandleRun(now, row.schedule, row.delaySeconds) : null }).where(eq(hvSubagents.id, row.id)).run();
162
+ return [...new Set(discarded.map((row) => row.subagentId))];
163
+ });
164
+ for (const id of affected)
165
+ this.historyChanged(agentId, id);
166
+ if (affected.length) {
167
+ this.notifications.changed(agentId);
168
+ this.log?.warn({ agentId }, "Queued worker requests discarded because Agent was disabled");
169
+ }
170
+ this.dashboardChanged(agentId);
171
+ }
172
+ /** Only claim cancellation after the harness has confirmed its ownership tree is idle. */
173
+ async cancelOutstanding(agentId) {
174
+ const now = Date.now();
175
+ const cancelled = await this.db.update(hvSubagentRuns).set({ status: "cancelled", result: "Agent disabled. Existing exchange orders and positions were not changed.", finishedAt: now, reportedAt: now }).where(and(eq(hvSubagentRuns.agentId, agentId), inArray(hvSubagentRuns.status, active))).returning({ id: hvSubagentRuns.subagentId }).all();
176
+ for (const id of new Set(cancelled.map((row) => row.id)))
177
+ this.historyChanged(agentId, id);
178
+ }
179
+ async execute(id, action, signal) {
180
+ return this.serial(id, async () => { await assertAgentExecutable(this.database, id); return action(); }, signal);
181
+ }
182
+ admitAgent(id) {
183
+ if (this.stoppedAgents.has(id) || this.host.signal(id).aborted)
184
+ throw new AgentError("Agent is being deleted", 409);
185
+ }
186
+ admit(readOnly = false) { if (this.stopping || (!readOnly && this.draining))
187
+ throw new AgentError("Subagent execution is shutting down", 503); }
188
+ binding(id) { this.admit(true); this.admitAgent(id); const value = this.bindings.get(id); if (!value)
189
+ throw new AgentError("Agent is unavailable. Retry initialization.", 503); return value; }
190
+ serial(id, action, signal) {
191
+ this.admit();
192
+ this.admitAgent(id);
193
+ const result = (this.pending.get(id) ?? Promise.resolve()).then(() => { this.admit(); this.admitAgent(id); signal?.throwIfAborted(); return action(); });
194
+ const settled = result.then(() => { }, () => { });
195
+ this.pending.set(id, settled);
196
+ void settled.then(() => { if (this.pending.get(id) === settled)
197
+ this.pending.delete(id); });
198
+ if (!signal)
199
+ return result;
200
+ // A task invocation must be able to leave an admission wait when aborted;
201
+ // otherwise cancellation could join a task waiting on its caller's lock.
202
+ let abort;
203
+ const interrupted = new Promise((_resolve, reject) => { abort = () => reject(signal.reason); signal.addEventListener("abort", abort, { once: true }); if (signal.aborted)
204
+ abort(); });
205
+ return Promise.race([result, interrupted]).finally(() => signal.removeEventListener("abort", abort));
206
+ }
207
+ thinking(level) { return (level || null); }
208
+ async validateModel(userId, selection, thinkingLevel) {
209
+ const model = this.injectedModels ? selection : await this.providers.validate(userId, selection);
210
+ const catalog = (await this.host.models(userId)).getModel(model.provider, model.modelId);
211
+ if (!catalog)
212
+ throw new AgentError("Model is unavailable.", 409);
213
+ if (thinkingLevel && !getSupportedThinkingLevels(catalog).some((level) => level === thinkingLevel))
214
+ throw new AgentError("Select a supported thinking level.");
215
+ return model;
216
+ }
217
+ async applyMain(binding, row) {
218
+ const available = [...(binding.hyperliquid.tools ?? []), ...(binding.extension.tools ?? []), ...binding.capabilities.flatMap((extension) => extension.tools ?? [])];
219
+ await binding.main.configure({ model: { provider: row.provider, modelId: row.modelId }, thinkingLevel: this.thinking(row.thinkingLevel), extensions: [timeGuidanceExtension, binding.hyperliquid, binding.extension, ...binding.capabilities.filter((extension) => extension.tools?.some((tool) => row.tools.includes(tool.name))), binding.orchestratorInsights], tools: available.filter((tool) => row.tools.includes(tool.name)), modelTools: null, instructions: row.instructions }, context);
220
+ }
221
+ async list(userId, agentId) {
222
+ await this.host.owned(userId, agentId);
223
+ return this.db.select().from(hvSubagents).where(eq(hvSubagents.agentId, agentId)).orderBy(asc(hvSubagents.kind), asc(hvSubagents.createdAt), asc(hvSubagents.id)).all();
224
+ }
225
+ async dashboard(userId, agentId) {
226
+ await this.host.owned(userId, agentId);
227
+ return this.database.appTransaction(async (db) => {
228
+ const definitions = await db.select({ id: hvSubagents.id, name: hvSubagents.name, enabled: hvSubagents.enabled, schedule: hvSubagents.schedule, nextRunAt: hvSubagents.nextRunAt }).from(hvSubagents).where(and(eq(hvSubagents.agentId, agentId), eq(hvSubagents.kind, "worker"), eq(hvSubagents.archived, false))).orderBy(asc(hvSubagents.createdAt), asc(hvSubagents.id)).all();
229
+ const summaries = [];
230
+ for (const definition of definitions) {
231
+ const { id, status, createdAt, result } = hvSubagentRuns;
232
+ const runs = await db.select({ id, status, createdAt, result: sql `substr(${result}, 1, 240)` }).from(hvSubagentRuns).where(and(eq(hvSubagentRuns.agentId, agentId), eq(hvSubagentRuns.subagentId, definition.id))).orderBy(desc(createdAt), desc(id)).limit(1).all();
233
+ if (runs[0]?.status === "running" || runs[0]?.status === "queued") {
234
+ const previous = await db.select({ id, status, createdAt, result: sql `substr(${result}, 1, 240)` }).from(hvSubagentRuns).where(and(eq(hvSubagentRuns.agentId, agentId), eq(hvSubagentRuns.subagentId, definition.id), sql `${status} not in ('running', 'queued')`)).orderBy(desc(createdAt), desc(id)).limit(1).all();
235
+ runs.push(...previous);
236
+ }
237
+ summaries.push({ ...definition, runs });
238
+ }
239
+ return summaries;
240
+ });
241
+ }
242
+ async detail(userId, agentId, id) {
243
+ await this.host.owned(userId, agentId);
244
+ const definition = await this.definition(agentId, id);
245
+ const filter = and(eq(hvSubagentRuns.agentId, agentId), eq(hvSubagentRuns.subagentId, id));
246
+ const { runs, pagination } = await this.database.appTransaction(async (db) => {
247
+ const total = (await db.select({ total: count() }).from(hvSubagentRuns).where(filter).get()).total;
248
+ const runs = await db.select().from(hvSubagentRuns).where(filter).orderBy(desc(hvSubagentRuns.createdAt), desc(hvSubagentRuns.id)).limit(runHistoryPageSize).all();
249
+ return { runs, pagination: runPagination(total) };
250
+ });
251
+ return { definition, runs, pagination, schedulerError: this.schedulerError, workers: (await this.db.select({ id: hvSubagents.id, name: hvSubagents.name, archived: hvSubagents.archived, tools: hvSubagents.tools }).from(hvSubagents).where(and(eq(hvSubagents.agentId, agentId), eq(hvSubagents.kind, "worker"))).all()).filter((worker) => !worker.archived || [...definition.triggers.researchSources, ...definition.triggers.workerDelegates].includes(worker.id)) };
252
+ }
253
+ async history(userId, agentId, id, page = 1) {
254
+ if (!Number.isSafeInteger(page) || page < 1)
255
+ throw new AgentError("Invalid history page");
256
+ await this.host.owned(userId, agentId);
257
+ const definition = await this.definition(agentId, id);
258
+ const filter = and(eq(hvSubagentRuns.agentId, agentId), eq(hvSubagentRuns.subagentId, id));
259
+ return this.database.appTransaction(async (db) => {
260
+ const total = (await db.select({ total: count() }).from(hvSubagentRuns).where(filter).get()).total;
261
+ const pagination = runPagination(total, page);
262
+ // Only prompt previews are streamed; full prompts/results/configuration
263
+ // belong on run details. Keep 50 valid, large assignments below the SSE cap.
264
+ const { id: runId, subagentId, source, task, status, createdAt } = hvSubagentRuns;
265
+ const runs = await db.select({ id: runId, subagentId, source, task: sql `substr(${task}, 1, 512)`, status, createdAt }).from(hvSubagentRuns).where(filter).orderBy(desc(createdAt), desc(runId)).limit(runHistoryPageSize).offset((pagination.page - 1) * runHistoryPageSize).all();
266
+ return { runs, pagination, schedulerError: this.schedulerError, schedule: { nextRunAt: definition.enabled && !definition.archived ? definition.nextRunAt : null, updatedAt: definition.updatedAt } };
267
+ });
268
+ }
269
+ async watchDashboard(userId, agentId, signal, publish) {
270
+ this.admit(true);
271
+ const lifetime = AbortSignal.any([signal, this.historyShutdown.signal, this.host.signal(agentId)]);
272
+ let finish;
273
+ const finished = new Promise((resolve) => { finish = resolve; });
274
+ this.historyFeeds.add(finished);
275
+ let dirty = true;
276
+ let wake;
277
+ const changed = () => { dirty = true; wake?.(); };
278
+ const listeners = this.dashboardListeners.get(agentId) ?? new Set();
279
+ this.dashboardListeners.set(agentId, listeners);
280
+ listeners.add(changed);
281
+ lifetime.addEventListener("abort", changed);
282
+ try {
283
+ while (!lifetime.aborted) {
284
+ if (dirty) {
285
+ dirty = false;
286
+ const snapshot = await this.dashboard(userId, agentId);
287
+ if (!lifetime.aborted)
288
+ publish(snapshot);
289
+ }
290
+ if (!dirty && !lifetime.aborted)
291
+ await new Promise((resolve) => { wake = resolve; });
292
+ wake = undefined;
293
+ }
294
+ }
295
+ finally {
296
+ lifetime.removeEventListener("abort", changed);
297
+ listeners.delete(changed);
298
+ if (!listeners.size)
299
+ this.dashboardListeners.delete(agentId);
300
+ this.historyFeeds.delete(finished);
301
+ finish();
302
+ }
303
+ }
304
+ async watchHistory(userId, agentId, id, page, signal, publish) {
305
+ this.admit(true);
306
+ const lifetime = AbortSignal.any([signal, this.historyShutdown.signal, this.host.signal(agentId)]);
307
+ let finish;
308
+ const finished = new Promise((resolve) => { finish = resolve; });
309
+ this.historyFeeds.add(finished);
310
+ let dirty = true;
311
+ let wake;
312
+ const changed = () => { dirty = true; wake?.(); };
313
+ const listeners = this.historyListeners.get(id) ?? new Set();
314
+ this.historyListeners.set(id, listeners);
315
+ listeners.add(changed);
316
+ lifetime.addEventListener("abort", changed);
317
+ try {
318
+ while (!lifetime.aborted) {
319
+ if (dirty) {
320
+ dirty = false;
321
+ const snapshot = await this.history(userId, agentId, id, page);
322
+ if (!lifetime.aborted)
323
+ publish(snapshot);
324
+ }
325
+ if (!dirty && !lifetime.aborted)
326
+ await new Promise((resolve) => { wake = resolve; });
327
+ wake = undefined;
328
+ }
329
+ }
330
+ finally {
331
+ lifetime.removeEventListener("abort", changed);
332
+ listeners.delete(changed);
333
+ if (!listeners.size)
334
+ this.historyListeners.delete(id);
335
+ this.historyFeeds.delete(finished);
336
+ finish();
337
+ }
338
+ }
339
+ async runDetail(userId, agentId, id, runId) {
340
+ await this.host.owned(userId, agentId);
341
+ const definition = await this.definition(agentId, id);
342
+ const run = await this.db.select().from(hvSubagentRuns).where(and(eq(hvSubagentRuns.id, runId), eq(hvSubagentRuns.agentId, agentId), eq(hvSubagentRuns.subagentId, id))).get();
343
+ if (!run)
344
+ throw new AgentError("Assignment not found", 404);
345
+ const entries = [], prior = [];
346
+ if (run.conversationId !== null) {
347
+ const child = await this.binding(agentId).harness.conversation(run.conversationId, context);
348
+ if (!child)
349
+ throw new AgentError("Assignment conversation is unavailable", 503);
350
+ // Read a consistent snapshot through Durable's public transaction API.
351
+ // Submission boundaries keep later persistent assignments out of this run.
352
+ await child.commit(async (tx) => {
353
+ const submission = await tx.submissionByRequest(child.id, `assignment:${run.id}`);
354
+ if (submission?.entry === undefined)
355
+ return;
356
+ let cursor;
357
+ let ended = false;
358
+ do {
359
+ const page = await tx.scanEntries({ conversationId: child.id, order: "ascending" }, 200, cursor);
360
+ for (const entry of page.items) {
361
+ // Workers receive assignments only through plain input submissions.
362
+ // A subsequent user entry therefore starts the next assignment,
363
+ // including when this submission ended without an answer.
364
+ if (entry.id > submission.entry && entry.kind === "pi.user") {
365
+ ended = true;
366
+ break;
367
+ }
368
+ (entry.id < submission.entry ? prior : entries).push(entry);
369
+ }
370
+ cursor = page.next;
371
+ } while (cursor && !ended);
372
+ }, context);
373
+ }
374
+ const live = run.conversationId !== null && (run.status === "running" || run.status === "queued") ? await this.binding(agentId).harness.snapshot(LiveDoc, run.conversationId, context) : undefined;
375
+ const publications = await this.database.all("SELECT 'm'||number AS ref,kind,summary FROM hv_memories WHERE agent_id=? AND source_run_id=? ORDER BY number DESC LIMIT 50", agentId, run.id);
376
+ return { definition, run, entries, prior, tools: toolActivity(entries, live), warnings: executionWarnings(entries), context: await this.execution.runContext(agentId, run), publications };
377
+ }
378
+ /** Verifies the viewer owns the Agent, worker and run without reading its transcript. */
379
+ async ownedRun(userId, agentId, id, runId) {
380
+ await this.host.owned(userId, agentId);
381
+ await this.definition(agentId, id);
382
+ const run = await this.internalRun(agentId, runId);
383
+ if (run.subagentId !== id)
384
+ throw new AgentError("Assignment not found", 404);
385
+ return run;
386
+ }
387
+ /** One page-scoped subscription; reconnects receive a fresh run-detail snapshot. */
388
+ async watchRun(userId, agentId, id, runId, signal, publish) {
389
+ this.admit(true);
390
+ await this.ownedRun(userId, agentId, id, runId);
391
+ const lifetime = AbortSignal.any([signal, this.historyShutdown.signal, this.host.signal(agentId)]);
392
+ let finish;
393
+ const finished = new Promise((resolve) => { finish = resolve; });
394
+ this.historyFeeds.add(finished);
395
+ let dirty = true;
396
+ let wake;
397
+ const changed = () => { dirty = true; wake?.(); };
398
+ const listeners = this.historyListeners.get(id) ?? new Set();
399
+ this.historyListeners.set(id, listeners);
400
+ listeners.add(changed);
401
+ lifetime.addEventListener("abort", changed);
402
+ let watch;
403
+ let watched = null;
404
+ try {
405
+ while (!lifetime.aborted) {
406
+ if (dirty) {
407
+ dirty = false;
408
+ // Status changes announce themselves through the history stream; the
409
+ // child conversation watch covers transcript entries, which nothing
410
+ // else observes while a worker executes.
411
+ const run = await this.internalRun(agentId, runId);
412
+ if (run.conversationId !== null && run.conversationId !== watched) {
413
+ await watch?.stop();
414
+ watch = undefined;
415
+ watched = null;
416
+ const child = await this.binding(agentId).harness.conversation(run.conversationId, context);
417
+ if (child && !lifetime.aborted) {
418
+ const attached = await child.watch(context);
419
+ watch = attached;
420
+ watched = child.id;
421
+ attached.start(async () => { if (watch === attached)
422
+ changed(); });
423
+ void attached.closed.then(() => { if (watch === attached) {
424
+ watch = undefined;
425
+ watched = null;
426
+ changed();
427
+ } });
428
+ }
429
+ }
430
+ if (!lifetime.aborted)
431
+ publish(await this.runDetail(userId, agentId, id, runId));
432
+ }
433
+ if (!dirty && !lifetime.aborted)
434
+ await new Promise((resolve) => { wake = resolve; });
435
+ wake = undefined;
436
+ }
437
+ }
438
+ finally {
439
+ lifetime.removeEventListener("abort", changed);
440
+ listeners.delete(changed);
441
+ if (!listeners.size)
442
+ this.historyListeners.delete(id);
443
+ try {
444
+ await watch?.stop();
445
+ }
446
+ finally {
447
+ this.historyFeeds.delete(finished);
448
+ finish();
449
+ }
450
+ }
451
+ }
452
+ async definition(agentId, id) {
453
+ const row = await this.db.select().from(hvSubagents).where(and(eq(hvSubagents.id, id), eq(hvSubagents.agentId, agentId))).get();
454
+ if (!row)
455
+ throw new AgentError("Subagent not found", 404);
456
+ return row;
457
+ }
458
+ async importDefaults(userId, agentId, creationId) {
459
+ await this.host.owned(userId, agentId);
460
+ creationId = requestId(creationId);
461
+ return this.serial(agentId, async () => {
462
+ const main = (await this.list(userId, agentId)).find((row) => row.kind === "orchestrator");
463
+ if (!main)
464
+ throw new AgentError("Initialize the Agent before importing default sub-agents", 409);
465
+ await this.validateModel(userId, main, main.thinkingLevel);
466
+ const rows = await this.database.appTransaction((db) => insertDefaultSubagents(db, agentId, creationId, main));
467
+ this.dashboardChanged(agentId);
468
+ return rows;
469
+ });
470
+ }
471
+ async create(userId, agentId, data, creationId, templateId = "") {
472
+ await this.host.owned(userId, agentId);
473
+ requestId(creationId);
474
+ return this.serial(agentId, async () => {
475
+ const input = subagentInput(data);
476
+ await this.validateModel(userId, input, input.thinkingLevel);
477
+ await this.validateTriggers(agentId, `worker:${creationId}`, workerTriggers(input.triggers), input);
478
+ const template = templateId ? subagentTemplate(templateId) : null;
479
+ const templateOrigin = template ? { id: template.id, version: template.version, hash: bodyHash(canonical(template)) } : null;
480
+ if (template && input.enabled)
481
+ throw new AgentError("Template creation is paused by default; enable it explicitly after reviewing the saved copy.");
482
+ const id = `worker:${creationId}`;
483
+ const existing = await this.db.select().from(hvSubagents).where(eq(hvSubagents.id, id)).get();
484
+ if (existing) {
485
+ if (existing.agentId !== agentId || canonical(existing.templateOrigin) !== canonical(templateOrigin) || Object.entries(input).some(([key, value]) => JSON.stringify(Reflect.get(existing, key)) !== JSON.stringify(value)))
486
+ throw new AgentError("Creation request was already used with different settings.", 409);
487
+ return existing;
488
+ }
489
+ const now = Date.now();
490
+ const row = (await this.db.insert(hvSubagents).values({ ...input, templateOrigin, id, agentId, kind: "worker", nextRunAt: input.enabled ? nextCandleRun(now, input.schedule, input.delaySeconds) : null, createdAt: now, updatedAt: now }).returning().all())[0];
491
+ this.dashboardChanged(agentId);
492
+ return row;
493
+ }).catch((error) => {
494
+ if (!(error instanceof AgentError))
495
+ this.log?.error({ err: error, agentId, templateId, requestId: creationId }, "Sub-agent creation failed");
496
+ throw error;
497
+ });
498
+ }
499
+ async validateTriggers(agentId, id, triggers, input, main = false, previous) {
500
+ if (main && triggers.inboxEnabled)
501
+ throw new AgentError("Only workers can receive event inbox assignments");
502
+ if (triggers.inboxEnabled && input.tools.includes("delegate_subagent"))
503
+ throw new AgentError("Inbox recipients cannot delegate recursively");
504
+ const added = [...triggers.workerDelegates.filter((id) => !previous?.workerDelegates.includes(id)), ...triggers.researchSources.filter((id) => !previous?.researchSources.includes(id))];
505
+ for (const sourceId of new Set(added)) {
506
+ const source = await this.definition(agentId, sourceId);
507
+ if (source.id === id || source.kind !== "worker" || source.archived)
508
+ throw new AgentError("Select another non-archived worker from this Agent");
509
+ }
510
+ }
511
+ async updateTools(userId, agentId, id, tools) {
512
+ return this.updateSettings(userId, agentId, id, undefined, { tools });
513
+ }
514
+ async updateIdentity(userId, agentId, id, name, description) {
515
+ return this.updateSettings(userId, agentId, id, undefined, { name, description });
516
+ }
517
+ async updateAssignments(userId, agentId, id, data) {
518
+ if (!data || typeof data !== "object")
519
+ throw new AgentError("Invalid assignment settings.");
520
+ return this.updateSettings(userId, agentId, id, undefined, {
521
+ delegatable: Reflect.get(data, "delegatable"),
522
+ persistent: Reflect.get(data, "persistent"), timeoutSeconds: Reflect.get(data, "timeoutSeconds"),
523
+ });
524
+ }
525
+ async updateSection(userId, agentId, id, section, data) {
526
+ if (!data || typeof data !== "object")
527
+ throw new AgentError("Invalid settings.");
528
+ const get = (key) => Reflect.get(data, key);
529
+ switch (section) {
530
+ case "enabled": return this.updateSettings(userId, agentId, id, undefined, { enabled: get("enabled") });
531
+ case "instructions": return this.updateSettings(userId, agentId, id, undefined, { instructions: get("instructions") });
532
+ case "model": return this.updateSettings(userId, agentId, id, undefined, { provider: get("provider"), modelId: get("modelId"), thinkingLevel: get("thinkingLevel") });
533
+ case "triggers": return this.updateSettings(userId, agentId, id, undefined, { triggers: get("triggers") });
534
+ case "schedule": return this.updateSettings(userId, agentId, id, undefined, { schedule: get("schedule"), delaySeconds: get("delaySeconds"), completion: get("completion") });
535
+ default: throw new AgentError("Unknown settings section.");
536
+ }
537
+ }
538
+ async update(userId, agentId, id, data) {
539
+ return this.updateSettings(userId, agentId, id, data);
540
+ }
541
+ async updateSettings(userId, agentId, id, data, patch) {
542
+ await this.host.owned(userId, agentId);
543
+ return this.serial(agentId, async () => {
544
+ const current = await this.definition(agentId, id);
545
+ if (current.archived)
546
+ throw new AgentError("Archived workers cannot be edited.", 409);
547
+ if (patch && ("enabled" in patch || "delegatable" in patch || "schedule" in patch) && current.kind === "orchestrator")
548
+ throw new AgentError("The orchestrator cannot be paused or have assignment or schedule settings.", 409);
549
+ const input = subagentInput(patch ? { ...current, ...patch } : data, current.kind === "orchestrator");
550
+ const pausing = patch && "enabled" in patch && !input.enabled;
551
+ if (!pausing)
552
+ await this.validateModel(userId, input, input.thinkingLevel);
553
+ await this.validateTriggers(agentId, id, workerTriggers(input.triggers), input, current.kind === "orchestrator", current.triggers);
554
+ const binding = this.binding(agentId);
555
+ if (current.kind === "orchestrator") {
556
+ const live = await binding.harness.snapshot(LiveDoc, binding.main.id, context), inbox = await binding.harness.snapshot(InboxDoc, binding.main.id, context);
557
+ if (live?.run || inbox?.items.length)
558
+ throw new AgentError("Stop or finish queued work before editing the orchestrator.", 409);
559
+ if (input.provider !== current.provider || input.modelId !== current.modelId) {
560
+ const selected = (await this.host.models(userId)).getModel(input.provider, input.modelId);
561
+ if (!selected?.input.includes("image") && (await binding.main.context(context)).messages.some((message) => message.role === "user" && Array.isArray(message.content) && message.content.some((part) => part.type === "image"))) {
562
+ throw new AgentError("This conversation contains images. Reset context before switching to a text-only model; earlier messages remain available.", 409);
563
+ }
564
+ }
565
+ }
566
+ else if (!pausing && await this.db.select().from(hvSubagentRuns).where(and(eq(hvSubagentRuns.subagentId, id), inArray(hvSubagentRuns.status, active))).get())
567
+ throw new AgentError("Stop or finish the active assignment before editing this worker.", 409);
568
+ const now = Date.now();
569
+ const contextSettings = {
570
+ delegatable: input.delegatable, persistent: input.persistent, timeoutSeconds: input.timeoutSeconds,
571
+ conversationId: input.persistent === current.persistent ? current.conversationId : null,
572
+ };
573
+ const scheduleSettings = { schedule: input.schedule, delaySeconds: input.delaySeconds, completion: input.completion, nextRunAt: input.enabled ? nextCandleRun(now, input.schedule, input.delaySeconds) : null };
574
+ const changes = !patch ? { ...input, ...contextSettings, ...scheduleSettings }
575
+ : "triggers" in patch ? { triggers: workerTriggers(input.triggers) }
576
+ : "tools" in patch ? { tools: input.tools }
577
+ : "name" in patch ? { name: input.name, description: input.description }
578
+ : "instructions" in patch ? { instructions: input.instructions }
579
+ : "provider" in patch ? { provider: input.provider, modelId: input.modelId, thinkingLevel: input.thinkingLevel }
580
+ : "schedule" in patch ? scheduleSettings
581
+ : "enabled" in patch ? { enabled: input.enabled, nextRunAt: input.enabled === current.enabled ? current.nextRunAt : input.enabled ? nextCandleRun(now, input.schedule, input.delaySeconds) : null }
582
+ : contextSettings;
583
+ const row = (await this.db.update(hvSubagents).set({ ...changes, updatedAt: now }).where(eq(hvSubagents.id, id)).returning().all())[0];
584
+ if (current.kind === "orchestrator") {
585
+ await this.applyMain(binding, row);
586
+ if (this.ready && await agentExecutable(this.database, agentId))
587
+ binding.harness.resume();
588
+ }
589
+ this.host.changed(agentId);
590
+ this.dashboardChanged(agentId);
591
+ return row;
592
+ });
593
+ }
594
+ async archive(userId, agentId, id) {
595
+ await this.host.owned(userId, agentId);
596
+ return this.serial(agentId, async () => {
597
+ const row = await this.definition(agentId, id);
598
+ if (row.kind === "orchestrator")
599
+ throw new AgentError("The main orchestrator cannot be archived or deleted.", 409);
600
+ if (await this.db.select().from(hvSubagentRuns).where(and(eq(hvSubagentRuns.subagentId, id), inArray(hvSubagentRuns.status, active))).get())
601
+ throw new AgentError("Stop or finish the active assignment before archiving.", 409);
602
+ await this.db.update(hvSubagents).set({ archived: true, enabled: false, nextRunAt: null, updatedAt: Date.now() }).where(eq(hvSubagents.id, id)).run();
603
+ this.dashboardChanged(agentId);
604
+ });
605
+ }
606
+ async unarchive(userId, agentId, id) {
607
+ await this.host.owned(userId, agentId);
608
+ return this.serial(agentId, async () => {
609
+ const current = await this.definition(agentId, id);
610
+ if (current.kind === "orchestrator")
611
+ throw new AgentError("The main orchestrator cannot be unarchived.", 409);
612
+ if (!current.archived)
613
+ return current;
614
+ await this.validateTriggers(agentId, id, current.triggers, current, false, current.triggers);
615
+ const row = (await this.db.update(hvSubagents).set({ archived: false, enabled: false, nextRunAt: null, updatedAt: Date.now() }).where(eq(hvSubagents.id, id)).returning().all())[0];
616
+ this.host.changed(agentId);
617
+ this.dashboardChanged(agentId);
618
+ return row;
619
+ });
620
+ }
621
+ async listWorkers(agentId, actor) {
622
+ if (actor) {
623
+ if (actor.agentId !== agentId)
624
+ throw new AgentError("Invalid delegation caller", 403);
625
+ await this.database.appTransaction((_db, tx) => assertActiveActor(tx, actor));
626
+ }
627
+ return (await this.db.select().from(hvSubagents).where(and(eq(hvSubagents.agentId, agentId), eq(hvSubagents.kind, "worker"), eq(hvSubagents.archived, false))).orderBy(asc(hvSubagents.createdAt), asc(hvSubagents.id)).all())
628
+ .filter((target) => !actor?.runId || (target.triggers.inboxEnabled && target.enabled && target.delegatable && target.triggers.workerDelegates.includes(actor.subagentId)))
629
+ .map(({ id, name, description, tools, enabled, delegatable, triggers }) => ({ id, name, description, tools, enabled, delegatable, ...(triggers.inboxEnabled ? { inboxEnabled: true } : {}) }));
630
+ }
631
+ async internalRun(agentId, id) {
632
+ const run = await this.db.select().from(hvSubagentRuns).where(and(eq(hvSubagentRuns.agentId, agentId), eq(hvSubagentRuns.id, id))).get();
633
+ if (!run)
634
+ throw new AgentError("Assignment not found", 404);
635
+ return run;
636
+ }
637
+ async canWake(agentId) {
638
+ if (!await agentExecutable(this.database, agentId))
639
+ return false;
640
+ const binding = this.bindings.get(agentId);
641
+ if (!binding || !this.ready || this.stopping)
642
+ return false;
643
+ const model = (await binding.main.agent(context)).model;
644
+ return Boolean(model && (this.injectedModels || await this.providers.configured(binding.userId, model)));
645
+ }
646
+ async reportWhenReady(agentId, deliver, signal) {
647
+ return this.serial(agentId, async () => {
648
+ if (!await this.canWake(agentId))
649
+ return false;
650
+ const binding = this.binding(agentId);
651
+ const inbox = await binding.harness.snapshot(InboxDoc, binding.main.id, context);
652
+ if ((inbox?.items.length ?? 0) >= 20)
653
+ return false;
654
+ signal.throwIfAborted();
655
+ await deliver();
656
+ return true;
657
+ }, signal);
658
+ }
659
+ async run(userId, agentId, id, task, submissionId) {
660
+ await this.host.owned(userId, agentId);
661
+ requestId(submissionId);
662
+ task = assignment(task);
663
+ return this.admitRun(agentId, id, task, `manual:${submissionId}`, "manual");
664
+ }
665
+ async delegate(agentId, id, task, submissionId, signal, intent = "evaluation", actor, refs = []) {
666
+ await assertAgentExecutable(this.database, agentId);
667
+ if (actor && (actor.agentId !== agentId || (actor.runId ? !actor.subagentId : actor.subagentId !== `main:${agentId}`)))
668
+ throw new AgentError("Invalid delegation caller", 403);
669
+ if (actor?.runId && intent !== "evaluation")
670
+ throw new AgentError("Only the orchestrator can delegate operator intent", 403);
671
+ if (refs.length > 20 || refs.some((ref) => !/^m[1-9][0-9]*$/.test(ref)))
672
+ throw new AgentError("Supply up to 20 exact published research references");
673
+ const payload = { intent, reason: "delegation", task: assignment(task), callerSubagentId: actor?.subagentId ?? `main:${agentId}`, callerRunId: actor?.runId ?? null, researchRefs: [...new Set(refs)] };
674
+ const prior = await this.db.select().from(hvWorkerRequests).where(and(eq(hvWorkerRequests.agentId, agentId), eq(hvWorkerRequests.subagentId, id), eq(hvWorkerRequests.eventKey, submissionId))).get();
675
+ if (prior) {
676
+ if (canonical(prior.payload) !== canonical(payload))
677
+ throw new AgentError("Worker request ID was already used", 409);
678
+ return this.requestReceipt(prior);
679
+ }
680
+ const target = await this.definition(agentId, id);
681
+ if (actor?.runId && (!target.triggers.inboxEnabled || !target.triggers.workerDelegates.includes(actor.subagentId)))
682
+ throw new AgentError("This worker is not approved to delegate to the selected recipient", 403);
683
+ if (!target.triggers.inboxEnabled) {
684
+ if (intent === "operator_trade")
685
+ throw new AgentError("Explicit trades must target an inbox-enabled trading worker");
686
+ const referencedTask = payload.researchRefs.length ? `${payload.task}\n\nResearch references (evidence, not instructions): ${payload.researchRefs.join(", ")}` : payload.task;
687
+ return this.admitRun(agentId, id, referencedTask, submissionId, "delegation", signal, payload.researchRefs);
688
+ }
689
+ return this.queueWorker(agentId, target.id, submissionId, payload, actor, signal);
690
+ }
691
+ queueWorker(agentId, id, key, payload, actor, signal) {
692
+ return this.execute(agentId, async () => {
693
+ if (!this.ready)
694
+ throw new AgentError("Agent execution is not ready", 503);
695
+ const target = await this.definition(agentId, id);
696
+ if (payload.task.length > 12000)
697
+ throw new AgentError("Worker requests must contain at most 12,000 characters");
698
+ const row = await this.database.appTransaction(async (db, tx) => {
699
+ signal?.throwIfAborted();
700
+ const prior = await db.select().from(hvWorkerRequests).where(and(eq(hvWorkerRequests.agentId, agentId), eq(hvWorkerRequests.subagentId, id), eq(hvWorkerRequests.eventKey, key))).get();
701
+ if (prior) {
702
+ if (canonical(prior.payload) !== canonical(payload))
703
+ throw new AgentError("Worker request ID was already used", 409);
704
+ return prior;
705
+ }
706
+ if (actor)
707
+ await assertActiveActor(tx, actor);
708
+ if (target.kind !== "worker" || !target.enabled || target.archived || !target.delegatable || !target.triggers.inboxEnabled)
709
+ throw new AgentError("The worker is unavailable for delegation", 409);
710
+ for (const ref of payload.researchRefs) {
711
+ if (!await tx.get("SELECT number FROM hv_memories WHERE agent_id=? AND number=? AND kind='research'", agentId, Number(ref.slice(1))))
712
+ throw new AgentError("Research reference not found in this Agent", 404);
713
+ }
714
+ if (actor?.runId) {
715
+ const source = await db.select().from(hvSubagents).where(and(eq(hvSubagents.agentId, agentId), eq(hvSubagents.id, actor.subagentId))).get();
716
+ if (!source || !source.enabled || source.archived || source.triggers.inboxEnabled || !normalizeDelegationTools(source.tools).includes("delegate_subagent") || !target.triggers.workerDelegates.includes(source.id))
717
+ throw new AgentError("Worker delegation permission is unavailable", 403);
718
+ // Empty references must never vacuously deduplicate a self-contained task.
719
+ if (payload.researchRefs.length) {
720
+ const publications = await db.select().from(hvWorkerRequests).where(and(eq(hvWorkerRequests.agentId, agentId), eq(hvWorkerRequests.subagentId, id), inArray(hvWorkerRequests.eventKey, payload.researchRefs.map((ref) => `research:${ref.slice(1)}`)))).all();
721
+ if (publications.length === payload.researchRefs.length)
722
+ return publications[0];
723
+ }
724
+ }
725
+ const pending = await db.select({ total: count() }).from(hvWorkerRequests).where(and(eq(hvWorkerRequests.subagentId, id), isNull(hvWorkerRequests.runId), isNull(hvWorkerRequests.discardedReason))).get();
726
+ if (pending.total >= 500)
727
+ throw new AgentError("Worker inbox is full", 429);
728
+ return (await db.insert(hvWorkerRequests).values({ id: randomUUID(), agentId, subagentId: id, eventKey: key, payload, createdAt: Date.now() }).returning().get());
729
+ });
730
+ this.historyChanged(agentId, id);
731
+ return this.requestReceipt(row);
732
+ }, signal);
733
+ }
734
+ requestReceipt(row) { return { id: row.id, status: row.runId ? "dispatched" : row.discardedReason ? "discarded" : "pending", runId: row.runId }; }
735
+ listRunEvidence(agentId, input) { return listRuns(this.database, agentId, input); }
736
+ async readRunEvidence(agentId, id, input) {
737
+ const request = await this.db.select().from(hvWorkerRequests).where(and(eq(hvWorkerRequests.agentId, agentId), eq(hvWorkerRequests.id, id))).get();
738
+ if (request && !request.runId)
739
+ return { request: { id: request.id, subagentId: request.subagentId, createdAt: request.createdAt, discardedReason: request.discardedReason }, run: null };
740
+ const run = await this.internalRun(agentId, request?.runId ?? id);
741
+ return { ...(request ? { requestId: request.id } : {}), ...await inspectRun(this.database, this.execution, this.binding(agentId).harness, agentId, run, input) };
742
+ }
743
+ async assignmentStatus(agentId, id) {
744
+ const request = await this.db.select().from(hvWorkerRequests).where(and(eq(hvWorkerRequests.agentId, agentId), eq(hvWorkerRequests.id, id))).get();
745
+ const view = (run) => ({ id: run.id, subagentId: run.subagentId, status: run.status, task: run.task, result: run.result, createdAt: run.createdAt, finishedAt: run.finishedAt });
746
+ if (!request)
747
+ return view(await this.internalRun(agentId, id));
748
+ return { request, run: request.runId ? view(await this.internalRun(agentId, request.runId)) : null };
749
+ }
750
+ async requestWarningTx(tx, request, reason, paused = false) {
751
+ const worker = await tx.get("SELECT name FROM hv_subagents WHERE agent_id=? AND id=?", request.agentId, request.subagentId);
752
+ const name = worker?.name ?? "Worker";
753
+ return this.notifications.systemWarningTx(tx, request.agentId, `worker-request:${request.id}:${paused ? "paused" : "discarded"}`, {
754
+ title: `${name} assignment ${paused ? "waiting while paused" : reason.startsWith("Request expired") ? "expired before starting" : "discarded before starting"}`,
755
+ body: `${reason}\n\nRequest: ${request.id}\nAssignment: ${request.payload.task.slice(0, 10000)}\n\nExisting exchange orders were not cancelled. Inspect the current account before starting a new assignment.`,
756
+ severity: "warning",
757
+ }, { source: name, subagentId: request.subagentId });
758
+ }
759
+ async discardRequest(request, reason) {
760
+ const discarded = await this.database.appTransaction(async (db, tx) => {
761
+ const row = await db.update(hvWorkerRequests).set({ discardedReason: reason }).where(and(eq(hvWorkerRequests.id, request.id), isNull(hvWorkerRequests.runId), isNull(hvWorkerRequests.discardedReason))).returning().get();
762
+ if (!row)
763
+ return false;
764
+ await this.requestWarningTx(tx, row, reason);
765
+ return true;
766
+ });
767
+ if (discarded) {
768
+ this.notifications.changed(request.agentId);
769
+ this.log?.warn({ agentId: request.agentId, subagentId: request.subagentId, requestId: request.id, reason }, "Worker request discarded before dispatch");
770
+ this.historyChanged(request.agentId, request.subagentId);
771
+ }
772
+ }
773
+ async drainInbox(agentId, id, now) {
774
+ await assertAgentExecutable(this.database, agentId);
775
+ const binding = this.bindings.get(agentId);
776
+ if (!binding)
777
+ return;
778
+ const target = await this.definition(agentId, id);
779
+ if (target.archived || !target.triggers.inboxEnabled) {
780
+ const requests = await this.db.select().from(hvWorkerRequests).where(and(eq(hvWorkerRequests.subagentId, id), isNull(hvWorkerRequests.runId), isNull(hvWorkerRequests.discardedReason))).all();
781
+ for (const request of requests)
782
+ await this.discardRequest(request, "Worker inbox disabled or archived");
783
+ return;
784
+ }
785
+ if (!target.enabled) {
786
+ const stalled = await this.db.select().from(hvWorkerRequests).where(and(eq(hvWorkerRequests.subagentId, id), isNull(hvWorkerRequests.runId), isNull(hvWorkerRequests.discardedReason), lte(hvWorkerRequests.createdAt, now - target.timeoutSeconds * 1000))).all();
787
+ for (const request of stalled) {
788
+ const warning = await this.database.transaction((tx) => this.requestWarningTx(tx, request, "The worker is paused. This request remains queued, but it will expire before dispatch when the worker resumes.", true));
789
+ if (warning.created) {
790
+ this.notifications.changed(agentId);
791
+ this.log?.warn({ agentId, subagentId: id, requestId: request.id }, "Worker request waiting while paused");
792
+ }
793
+ }
794
+ return;
795
+ }
796
+ const requests = await this.db.select().from(hvWorkerRequests).where(and(eq(hvWorkerRequests.subagentId, id), isNull(hvWorkerRequests.runId), isNull(hvWorkerRequests.discardedReason))).orderBy(sql `CASE json_extract(${hvWorkerRequests.payload}, '$.intent') WHEN 'account_event' THEN 0 WHEN 'operator_trade' THEN 1 ELSE 2 END`, asc(hvWorkerRequests.createdAt), asc(hvWorkerRequests.id)).limit(256).all();
797
+ const pending = [];
798
+ for (const request of requests) {
799
+ const payload = request.payload;
800
+ const revoked = payload.reason === "research" ? !target.triggers.researchSources.includes(payload.callerSubagentId ?? "")
801
+ : payload.reason === "fill" ? !target.triggers.fills : payload.reason === "order_status" ? !target.triggers.orderChanges
802
+ : !target.delegatable || (payload.callerRunId !== null && !target.triggers.workerDelegates.includes(payload.callerSubagentId ?? ""));
803
+ if (revoked || now >= request.createdAt + target.timeoutSeconds * 1000) {
804
+ await this.discardRequest(request, revoked ? "Trigger/delegation permission removed" : "Request expired before dispatch; inspect account before requesting again");
805
+ }
806
+ else
807
+ pending.push(request);
808
+ }
809
+ if (!pending.length)
810
+ return;
811
+ const running = await this.db.select().from(hvSubagentRuns).where(and(eq(hvSubagentRuns.agentId, agentId), inArray(hvSubagentRuns.status, active))).all();
812
+ if (running.length >= 4 || running.some((run) => run.subagentId === id) || await this.recoveryPending(agentId, id, binding))
813
+ return;
814
+ await this.validateModel(binding.userId, target, target.thinkingLevel);
815
+ const batch = requestBatch(pending);
816
+ const run = await this.database.appTransaction(async (db, tx) => {
817
+ const admitted = (await db.insert(hvSubagentRuns).values({ id: randomUUID(), agentId, subagentId: id, requestId: `inbox:${batch[0].id}`, source: batch[0].payload.reason === "delegation" ? "delegation" : "event", task: assignment(inboxAssignment(batch)), config: target, createdAt: Date.now() }).returning().get());
818
+ await this.execution.snapshotRunTx(tx, admitted);
819
+ await db.update(hvWorkerRequests).set({ runId: admitted.id }).where(inArray(hvWorkerRequests.id, batch.map((request) => request.id))).run();
820
+ return admitted;
821
+ });
822
+ this.historyChanged(agentId, id);
823
+ await this.dispatch(binding, run);
824
+ }
825
+ admitRun(agentId, id, task, submissionId, source, signal, refs = []) {
826
+ return this.execute(agentId, async () => {
827
+ if (!this.ready)
828
+ throw new AgentError("Agent execution is not ready", 503);
829
+ task = assignment(task);
830
+ const binding = this.binding(agentId);
831
+ const previous = await this.db.select().from(hvSubagentRuns).where(and(eq(hvSubagentRuns.agentId, agentId), eq(hvSubagentRuns.requestId, submissionId))).get();
832
+ if (previous) {
833
+ if (previous.subagentId !== id || previous.task !== task)
834
+ throw new AgentError("Assignment request was already used.", 409);
835
+ return previous;
836
+ }
837
+ const definition = await this.definition(agentId, id);
838
+ subagentInput(definition);
839
+ if (definition.kind !== "worker" || (source !== "manual" && !definition.enabled) || definition.archived || (source === "delegation" && !definition.delegatable))
840
+ throw new AgentError("This worker is not available for this assignment.", 409);
841
+ await this.validateModel(binding.userId, definition, definition.thinkingLevel);
842
+ const runs = await this.db.select().from(hvSubagentRuns).where(and(eq(hvSubagentRuns.agentId, agentId), inArray(hvSubagentRuns.status, active))).all();
843
+ if (runs.some((run) => run.subagentId === id) || await this.recoveryPending(agentId, id, binding))
844
+ throw new AgentError("This worker already has an active assignment.", 409);
845
+ if (runs.length >= 4)
846
+ throw new AgentError("This Agent already has four active workers.", 429);
847
+ const run = await this.database.appTransaction(async (db, tx) => {
848
+ signal?.throwIfAborted();
849
+ for (const ref of refs) {
850
+ if (!await tx.get("SELECT number FROM hv_memories WHERE agent_id=? AND number=? AND kind='research'", agentId, Number(ref.slice(1))))
851
+ throw new AgentError("Research reference not found in this Agent", 404);
852
+ }
853
+ const admitted = (await db.insert(hvSubagentRuns).values({ id: randomUUID(), agentId, subagentId: id, requestId: submissionId, source, task, config: definition, createdAt: Date.now() }).returning().all())[0];
854
+ await this.execution.snapshotRunTx(tx, admitted);
855
+ signal?.throwIfAborted();
856
+ return admitted;
857
+ });
858
+ this.historyChanged(agentId, id);
859
+ await this.dispatch(binding, run);
860
+ return run;
861
+ }, signal);
862
+ }
863
+ async dispatch(binding, run, reportOnly = false) {
864
+ await assertAgentExecutable(this.database, run.agentId);
865
+ try {
866
+ const taskId = await binding.main.commit(async (tx) => {
867
+ const receipt = await tx.doc(RunReceipt, binding.main.id, reportOnly ? `${run.id}:report` : run.id, null);
868
+ if (receipt.taskId === null)
869
+ receipt.taskId = await tx.createTask(binding.runner, { runId: run.id, reportOnly }, { ownership: { kind: "conversation" }, background: true });
870
+ return receipt.taskId;
871
+ }, context);
872
+ await this.db.update(hvSubagentRuns).set({ taskId }).where(eq(hvSubagentRuns.id, run.id)).run();
873
+ binding.harness.resume();
874
+ }
875
+ catch (error) {
876
+ // Inbox/schedule admission creates the run inside the item action.
877
+ this.log?.warn({ err: error, agentId: run.agentId, subagentId: run.subagentId, runId: run.id, reportOnly }, "Sub-agent run dispatch deferred");
878
+ throw error;
879
+ }
880
+ }
881
+ async started(run, conversationId) {
882
+ await this.database.appTransaction(async (db) => {
883
+ await db.update(hvSubagentRuns).set({ conversationId, status: "running" }).where(and(eq(hvSubagentRuns.id, run.id), inArray(hvSubagentRuns.status, active))).run();
884
+ if (run.config.persistent)
885
+ await db.update(hvSubagents).set({ conversationId }).where(eq(hvSubagents.id, run.subagentId)).run();
886
+ });
887
+ this.historyChanged(run.agentId, run.subagentId);
888
+ }
889
+ async finish(id, status, result) {
890
+ const run = await this.db.update(hvSubagentRuns).set({ status, result, finishedAt: Date.now() }).where(and(eq(hvSubagentRuns.id, id), inArray(hvSubagentRuns.status, active))).returning({ agentId: hvSubagentRuns.agentId, subagentId: hvSubagentRuns.subagentId }).get();
891
+ if (run)
892
+ this.historyChanged(run.agentId, run.subagentId);
893
+ }
894
+ async reported(id) { await this.db.update(hvSubagentRuns).set({ reportedAt: Date.now() }).where(eq(hvSubagentRuns.id, id)).run(); }
895
+ async stop(userId, agentId, runId) {
896
+ await this.host.owned(userId, agentId);
897
+ return this.serial(agentId, async () => {
898
+ const request = await this.db.select().from(hvWorkerRequests).where(and(eq(hvWorkerRequests.agentId, agentId), eq(hvWorkerRequests.id, runId))).get();
899
+ if (request && !request.runId) {
900
+ await this.db.update(hvWorkerRequests).set({ discardedReason: "Discarded by operator. Existing orders were not canceled." }).where(eq(hvWorkerRequests.id, request.id)).run();
901
+ this.historyChanged(agentId, request.subagentId);
902
+ return;
903
+ }
904
+ if (request?.runId)
905
+ throw new AgentError("This request has already dispatched; stop its run explicitly", 409);
906
+ const run = await this.internalRun(agentId, runId), binding = this.binding(agentId);
907
+ if (!active.includes(run.status))
908
+ return;
909
+ // Recover a dispatch whose application task-ID update was interrupted.
910
+ await this.dispatch(binding, run);
911
+ const current = await this.internalRun(agentId, runId);
912
+ await binding.harness.abortTask(current.taskId, context);
913
+ await binding.harness.waitForTask(current.taskId, context);
914
+ });
915
+ }
916
+ start() {
917
+ this.admit();
918
+ return this.starting ??= this.startScheduler();
919
+ }
920
+ async startScheduler() {
921
+ if (this.ready)
922
+ return;
923
+ // No catch-up burst after downtime; pending admitted assignments are recovered separately.
924
+ const definitions = await this.db.select().from(hvSubagents).where(eq(hvSubagents.kind, "worker")).all();
925
+ for (const row of definitions)
926
+ await this.db.update(hvSubagents).set({ nextRunAt: row.enabled && !row.archived ? nextCandleRun(Date.now(), row.schedule, row.delaySeconds) : null }).where(eq(hvSubagents.id, row.id)).run();
927
+ if (this.stopping || this.draining)
928
+ return;
929
+ this.ready = true;
930
+ this.log?.info("Starting sub-agent scheduler");
931
+ this.timer = setInterval(() => { void this.tick(); }, 1000);
932
+ this.timer.unref();
933
+ await this.tick();
934
+ }
935
+ async recoveryPending(agentId, subagentId, binding) {
936
+ const runs = await this.db.select().from(hvSubagentRuns).where(and(eq(hvSubagentRuns.agentId, agentId), eq(hvSubagentRuns.subagentId, subagentId), or(inArray(hvSubagentRuns.status, active), isNull(hvSubagentRuns.reportedAt)))).all();
937
+ for (const run of runs)
938
+ if (await binding.main.commit(async (tx) => (await tx.doc(ParkedRunRecovery, binding.main.id, run.id, null)).pending, context))
939
+ return true;
940
+ return false;
941
+ }
942
+ /** Outside the admission lock: abort APIs can join invocations waiting for that lock. */
943
+ async recoverParkedRuns(agentId, binding, markOnly = false) {
944
+ const attached = await binding.harness.taskGraph(context);
945
+ const graph = attached.value;
946
+ attached.dispose();
947
+ const liveRunners = new Map();
948
+ for (const node of Object.values(graph.tasks)) {
949
+ if (node.kind !== "hv.subagent-runner")
950
+ continue;
951
+ const task = await binding.harness.getTask(node.id, context);
952
+ const input = task?.input;
953
+ if (input && typeof input === "object" && "runId" in input && typeof input.runId === "string")
954
+ liveRunners.set(input.runId, node.id);
955
+ }
956
+ // Live runner IDs cover a report parked after reportedAt but before its
957
+ // terminal commit. Pending cleanup otherwise keeps reportedAt null.
958
+ const runs = await this.db.select().from(hvSubagentRuns).where(and(eq(hvSubagentRuns.agentId, agentId), or(inArray(hvSubagentRuns.status, active), isNull(hvSubagentRuns.reportedAt), inArray(hvSubagentRuns.id, [...liveRunners.keys()])))).all();
959
+ for (const run of runs) {
960
+ // Dispatch may have committed its receipt before the application task ID.
961
+ const taskId = liveRunners.get(run.id) ?? run.taskId ?? await binding.main.commit(async (tx) => (await tx.doc(RunReceipt, binding.main.id, run.id, null)).taskId, context);
962
+ if (taskId === null)
963
+ continue;
964
+ const recovery = await binding.main.commit(async (tx) => { const doc = await tx.doc(ParkedRunRecovery, binding.main.id, run.id, null); return { ...doc }; }, context);
965
+ if (recovery.done) {
966
+ await this.reported(run.id);
967
+ continue;
968
+ }
969
+ const workerConversation = recovery.pending ? recovery.conversationId : active.includes(run.status) ? run.conversationId : null;
970
+ const ids = new Set([taskId]);
971
+ const conversations = new Set();
972
+ if (workerConversation !== null)
973
+ conversations.add(workerConversation);
974
+ let changed = true;
975
+ while (changed) {
976
+ changed = false;
977
+ for (const node of Object.values(graph.tasks)) {
978
+ if (!ids.has(node.id) && !conversations.has(node.conversationId) && (node.owner === undefined || !ids.has(node.owner)))
979
+ continue;
980
+ if (!ids.has(node.id)) {
981
+ ids.add(node.id);
982
+ changed = true;
983
+ }
984
+ for (const id of node.conversations)
985
+ if (!conversations.has(id)) {
986
+ conversations.add(id);
987
+ changed = true;
988
+ }
989
+ }
990
+ }
991
+ if (!recovery.pending && !Object.values(graph.tasks).some((node) => ids.has(node.id) && node.blocked === "restart_limit"))
992
+ continue;
993
+ await binding.main.commit(async (tx) => { const doc = await tx.doc(ParkedRunRecovery, binding.main.id, run.id, null); doc.pending = true; doc.conversationId = workerConversation; }, context);
994
+ // Preserve the ID and completed results. Finish first so the runner's abort
995
+ // handler cannot replace the failure with a generic cancellation result.
996
+ await this.db.update(hvSubagentRuns).set({ taskId, reportedAt: null }).where(eq(hvSubagentRuns.id, run.id)).run();
997
+ await this.finish(run.id, "failed", parkedRunReason);
998
+ // abortTask marks while paused and bypasses restart parking for cancellation.
999
+ // Mark the whole scope before resume; never retryTask or replay an input.
1000
+ const cancellation = withAbortSignal(AbortSignal.any([this.host.signal(agentId), this.historyShutdown.signal, AbortSignal.timeout(10_000)]), context);
1001
+ for (const id of ids)
1002
+ if (graph.tasks[String(id)])
1003
+ await binding.harness.abortTask(id, cancellation);
1004
+ if (markOnly)
1005
+ continue;
1006
+ for (const id of conversations)
1007
+ await (await binding.harness.conversation(id, cancellation))?.abort(cancellation, { background: true });
1008
+ await cancelTaskScope(binding.harness, [taskId], workerConversation === null ? [] : [workerConversation], cancellation);
1009
+ // Suppress automatic reporting, including report-only tasks. The existing
1010
+ // result and any already delivered report remain unchanged.
1011
+ await binding.main.commit(async (tx) => { const doc = await tx.doc(ParkedRunRecovery, binding.main.id, run.id, null); doc.pending = false; doc.done = true; }, context);
1012
+ await this.reported(run.id);
1013
+ this.historyChanged(agentId, run.subagentId);
1014
+ }
1015
+ }
1016
+ /** Public clock seam for deterministic scheduling tests; never overlaps ticks. */
1017
+ tick(now = Date.now()) {
1018
+ if (this.stopping)
1019
+ return Promise.resolve();
1020
+ // start() initializes schedules asynchronously. A caller that awaits tick()
1021
+ // must also await that initialization and its first pass.
1022
+ if (!this.ready)
1023
+ return this.starting?.then(() => this.ready ? this.tick(now) : undefined) ?? Promise.resolve();
1024
+ if (this.tickActive)
1025
+ return this.ticking;
1026
+ this.tickActive = true;
1027
+ this.ticking = this.ticking.then(async () => {
1028
+ if (this.stopping || this.draining)
1029
+ return;
1030
+ const recoveryFailed = new Set();
1031
+ for (const [agentId, binding] of this.bindings) {
1032
+ if (this.host.signal(agentId).aborted || !await agentExecutable(this.database, agentId))
1033
+ continue;
1034
+ try {
1035
+ await this.host.recoverParkedMain?.(agentId);
1036
+ await this.recoverParkedRuns(agentId, binding);
1037
+ }
1038
+ catch (error) {
1039
+ recoveryFailed.add(agentId);
1040
+ this.log?.warn({ err: error, agentId }, "Parked-task cleanup deferred; new work remains blocked for this Agent");
1041
+ if (!this.database.available || sqliteFailure(error))
1042
+ throw error;
1043
+ }
1044
+ }
1045
+ // Dispatch intents persisted before a crash; detect terminal task failures too.
1046
+ const pending = await this.db.select().from(hvSubagentRuns).where(or(inArray(hvSubagentRuns.status, active), isNull(hvSubagentRuns.reportedAt))).all();
1047
+ for (const run of pending) {
1048
+ if (this.stopping || this.draining)
1049
+ return;
1050
+ const binding = this.bindings.get(run.agentId);
1051
+ if (recoveryFailed.has(run.agentId) || !binding || this.host.signal(run.agentId).aborted || !await agentExecutable(this.database, run.agentId))
1052
+ continue;
1053
+ await this.schedulerItem(run.agentId, run.subagentId, run.id, () => this.serial(run.agentId, async () => {
1054
+ const current = await this.internalRun(run.agentId, run.id);
1055
+ if (current.taskId === null && active.includes(current.status))
1056
+ await this.dispatch(binding, current);
1057
+ else if (current.taskId !== null) {
1058
+ const task = await binding.harness.getTask(current.taskId, context);
1059
+ if (task?.state.status === "terminal" && active.includes(current.status)) {
1060
+ const status = task.state.outcome.status === "aborted" ? "cancelled" : "failed";
1061
+ await this.finish(current.id, status, "Worker execution stopped unexpectedly. Inspect account/order state before retrying.");
1062
+ if (status === "failed")
1063
+ await this.dispatch(binding, current, true);
1064
+ else
1065
+ await this.reported(current.id);
1066
+ }
1067
+ else if (task?.state.status === "terminal" && current.reportedAt === null) {
1068
+ if (task.state.outcome.status !== "completed" && task.state.outcome.status !== "aborted") {
1069
+ await this.db.update(hvSubagentRuns).set({ result: `${current.result}\nCompletion reporting failed. Review this run manually.` }).where(eq(hvSubagentRuns.id, current.id)).run();
1070
+ }
1071
+ await this.reported(current.id);
1072
+ }
1073
+ }
1074
+ }));
1075
+ }
1076
+ const inboxes = await this.db.selectDistinct({ agentId: hvWorkerRequests.agentId, id: hvWorkerRequests.subagentId }).from(hvWorkerRequests).where(and(isNull(hvWorkerRequests.runId), isNull(hvWorkerRequests.discardedReason))).all();
1077
+ for (const inbox of inboxes) {
1078
+ if (this.stopping || this.draining)
1079
+ return;
1080
+ if (recoveryFailed.has(inbox.agentId) || this.host.signal(inbox.agentId).aborted || !await agentExecutable(this.database, inbox.agentId))
1081
+ continue;
1082
+ await this.schedulerItem(inbox.agentId, inbox.id, undefined, () => this.serial(inbox.agentId, () => this.drainInbox(inbox.agentId, inbox.id, now)));
1083
+ }
1084
+ const due = await this.db.select().from(hvSubagents).where(and(eq(hvSubagents.enabled, true), eq(hvSubagents.archived, false), lte(hvSubagents.nextRunAt, now))).all();
1085
+ for (const row of due) {
1086
+ if (this.stopping || this.draining)
1087
+ return;
1088
+ if (recoveryFailed.has(row.agentId) || this.host.signal(row.agentId).aborted || !await agentExecutable(this.database, row.agentId))
1089
+ continue;
1090
+ await this.schedulerItem(row.agentId, row.id, undefined, () => this.schedule(row, now));
1091
+ }
1092
+ this.schedulerError = recoveryFailed.size ? "Parked-task cleanup is pending. Affected Agents will retry cancellation, not assignments." : null;
1093
+ }).catch((error) => {
1094
+ this.schedulerError = "Subagent scheduler encountered an error. It will retry; persisted assignments are retained.";
1095
+ this.log?.error(error, "Subagent scheduler encountered an error");
1096
+ }).finally(() => { this.tickActive = false; });
1097
+ return this.ticking;
1098
+ }
1099
+ /** Item failures can retry independently; storage and lifecycle failures stop the pass. */
1100
+ async schedulerItem(agentId, subagentId, runId, action) {
1101
+ try {
1102
+ await action();
1103
+ }
1104
+ catch (error) {
1105
+ this.log?.warn({ err: error, agentId, subagentId, runId }, "Sub-agent scheduler item deferred");
1106
+ this.admit();
1107
+ this.admitAgent(agentId);
1108
+ if (!this.database.available || sqliteFailure(error) || (error instanceof Error && error.name === "AbortError"))
1109
+ throw error;
1110
+ // Recheck after asynchronous work: deletion/disablement can race admission.
1111
+ if (!await agentExecutable(this.database, agentId))
1112
+ throw error;
1113
+ }
1114
+ }
1115
+ schedule(candidate, now) {
1116
+ return this.execute(candidate.agentId, async () => {
1117
+ const row = await this.definition(candidate.agentId, candidate.id);
1118
+ if (!row.enabled || row.archived || row.nextRunAt !== candidate.nextRunAt)
1119
+ return;
1120
+ const binding = this.bindings.get(row.agentId);
1121
+ let reason = now - row.nextRunAt > 60000 ? "Missed candle interval; stale assignment skipped." : "";
1122
+ if (!binding)
1123
+ reason = "Agent execution is unavailable.";
1124
+ if (!reason && binding) {
1125
+ try {
1126
+ await this.validateModel(binding.userId, row, row.thinkingLevel);
1127
+ }
1128
+ catch (error) {
1129
+ if (!(error instanceof AgentError))
1130
+ throw error;
1131
+ reason = error.message;
1132
+ }
1133
+ }
1134
+ if (!reason && binding && await this.recoveryPending(row.agentId, row.id, binding))
1135
+ reason = "Worker cancellation is still pending.";
1136
+ if (!reason)
1137
+ subagentInput(row);
1138
+ const run = await this.database.appTransaction(async (db, tx) => {
1139
+ const running = await db.select().from(hvSubagentRuns).where(and(eq(hvSubagentRuns.agentId, row.agentId), inArray(hvSubagentRuns.status, active))).all();
1140
+ if (running.some((run) => run.subagentId === row.id))
1141
+ reason = "Worker already has an active assignment.";
1142
+ else if (running.length >= 4)
1143
+ reason = "Agent already has four active workers.";
1144
+ // Advance the candle cursor and persist its intent atomically. Dispatch
1145
+ // is separately idempotent, so a crash cannot lose or duplicate the run.
1146
+ await db.update(hvSubagents).set({ nextRunAt: nextCandleRun(now, row.schedule, row.delaySeconds) }).where(eq(hvSubagents.id, row.id)).run();
1147
+ const admitted = (await db.insert(hvSubagentRuns).values({ id: randomUUID(), agentId: row.agentId, subagentId: row.id, requestId: `schedule:${row.id}:${row.nextRunAt}`, source: "schedule", task: `Scheduled ${row.schedule} assignment. UTC candle close: ${new Date(row.nextRunAt - row.delaySeconds * 1000).toISOString()}. endMs: ${row.nextRunAt - row.delaySeconds * 1000} (UTC milliseconds, exclusive closed-candle end).\n\nCarry out your responsibilities according to your instructions.`, config: row, status: reason ? "skipped" : "queued", result: reason, createdAt: now, finishedAt: reason ? now : null, reportedAt: reason ? now : null }).returning().all())[0];
1148
+ if (!reason)
1149
+ await this.execution.snapshotRunTx(tx, admitted);
1150
+ return admitted;
1151
+ });
1152
+ this.historyChanged(row.agentId, row.id);
1153
+ this.log?.info({ agentId: run.agentId, runId: run.id, subagentId: run.subagentId, status: run.status, reason: reason || undefined }, reason ? "Scheduled sub-agent run skipped" : "Scheduled sub-agent run");
1154
+ if (run.status === "queued" && binding)
1155
+ await this.dispatch(binding, run);
1156
+ });
1157
+ }
1158
+ drain() {
1159
+ if (this.draining)
1160
+ return this.draining;
1161
+ this.ready = false;
1162
+ if (this.timer)
1163
+ clearInterval(this.timer);
1164
+ return this.draining = (async () => { await this.starting; await this.ticking; await Promise.all(this.pending.values()); })();
1165
+ }
1166
+ async close() {
1167
+ const drained = this.drain();
1168
+ this.stopping = true;
1169
+ this.historyShutdown.abort();
1170
+ try {
1171
+ await drained;
1172
+ }
1173
+ finally {
1174
+ await Promise.all(this.historyFeeds);
1175
+ }
1176
+ }
1177
+ }