@nocobase/app-plugin-agents 0.1.0-beta.1 → 0.1.0-beta.11

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 (649) hide show
  1. package/CHANGELOG.md +398 -0
  2. package/README.md +129 -67
  3. package/dist/client/api/client.d.ts +25 -17
  4. package/dist/client/api/client.d.ts.map +1 -1
  5. package/dist/client/api/client.js +29 -27
  6. package/dist/client/api/client.js.map +1 -1
  7. package/dist/client/api/keys.d.ts +1 -1
  8. package/dist/client/api/keys.d.ts.map +1 -1
  9. package/dist/client/api/keys.js +1 -1
  10. package/dist/client/api/keys.js.map +1 -1
  11. package/dist/client/chat/api.d.ts +2 -7
  12. package/dist/client/chat/api.d.ts.map +1 -1
  13. package/dist/client/chat/api.js +0 -7
  14. package/dist/client/chat/api.js.map +1 -1
  15. package/dist/client/chat/consultation-identity.d.ts +7 -0
  16. package/dist/client/chat/consultation-identity.d.ts.map +1 -0
  17. package/dist/client/chat/consultation-identity.js +39 -0
  18. package/dist/client/chat/consultation-identity.js.map +1 -0
  19. package/dist/client/chat/message-model.d.ts +4 -2
  20. package/dist/client/chat/message-model.d.ts.map +1 -1
  21. package/dist/client/chat/message-model.js +28 -3
  22. package/dist/client/chat/message-model.js.map +1 -1
  23. package/dist/client/chat/use-chat.d.ts +2 -10
  24. package/dist/client/chat/use-chat.d.ts.map +1 -1
  25. package/dist/client/chat/use-chat.js +3 -10
  26. package/dist/client/chat/use-chat.js.map +1 -1
  27. package/dist/client/chat.d.ts +4 -1
  28. package/dist/client/chat.d.ts.map +1 -1
  29. package/dist/client/chat.js +3 -0
  30. package/dist/client/chat.js.map +1 -1
  31. package/dist/client/components/agent-picker.d.ts +6 -23
  32. package/dist/client/components/agent-picker.d.ts.map +1 -1
  33. package/dist/client/components/agent-picker.js +19 -39
  34. package/dist/client/components/agent-picker.js.map +1 -1
  35. package/dist/client/components/variables/variable-dialogs.d.ts +6 -1
  36. package/dist/client/components/variables/variable-dialogs.d.ts.map +1 -1
  37. package/dist/client/components/variables/variable-dialogs.js +27 -11
  38. package/dist/client/components/variables/variable-dialogs.js.map +1 -1
  39. package/dist/client/components/variables/variables-panel.d.ts.map +1 -1
  40. package/dist/client/components/variables/variables-panel.js +9 -2
  41. package/dist/client/components/variables/variables-panel.js.map +1 -1
  42. package/dist/client/hooks/use-agent-picker-labels.d.ts.map +1 -1
  43. package/dist/client/hooks/use-agent-picker-labels.js +3 -12
  44. package/dist/client/hooks/use-agent-picker-labels.js.map +1 -1
  45. package/dist/client/kit.d.ts +9 -12
  46. package/dist/client/kit.d.ts.map +1 -1
  47. package/dist/client/kit.js +12 -14
  48. package/dist/client/kit.js.map +1 -1
  49. package/dist/client/lib/agents.d.ts +5 -13
  50. package/dist/client/lib/agents.d.ts.map +1 -1
  51. package/dist/client/lib/agents.js +17 -32
  52. package/dist/client/lib/agents.js.map +1 -1
  53. package/dist/client/lib/install.js +1 -1
  54. package/dist/client/locales/chat.en-US.d.ts +9 -22
  55. package/dist/client/locales/chat.en-US.d.ts.map +1 -1
  56. package/dist/client/locales/chat.en-US.js +13 -25
  57. package/dist/client/locales/chat.en-US.js.map +1 -1
  58. package/dist/client/locales/chat.zh-CN.d.ts.map +1 -1
  59. package/dist/client/locales/chat.zh-CN.js +10 -22
  60. package/dist/client/locales/chat.zh-CN.js.map +1 -1
  61. package/dist/client/locales/en-US.d.ts +101 -219
  62. package/dist/client/locales/en-US.d.ts.map +1 -1
  63. package/dist/client/locales/en-US.js +106 -219
  64. package/dist/client/locales/en-US.js.map +1 -1
  65. package/dist/client/locales/runWait.en-US.d.ts +38 -0
  66. package/dist/client/locales/runWait.en-US.d.ts.map +1 -0
  67. package/dist/client/locales/runWait.en-US.js +37 -0
  68. package/dist/client/locales/runWait.en-US.js.map +1 -0
  69. package/dist/client/locales/runWait.zh-CN.d.ts +5 -0
  70. package/dist/client/locales/runWait.zh-CN.d.ts.map +1 -0
  71. package/dist/client/locales/runWait.zh-CN.js +32 -0
  72. package/dist/client/locales/runWait.zh-CN.js.map +1 -0
  73. package/dist/client/locales/runtimes.en-US.d.ts +54 -0
  74. package/dist/client/locales/runtimes.en-US.d.ts.map +1 -1
  75. package/dist/client/locales/runtimes.en-US.js +57 -3
  76. package/dist/client/locales/runtimes.en-US.js.map +1 -1
  77. package/dist/client/locales/runtimes.zh-CN.d.ts.map +1 -1
  78. package/dist/client/locales/runtimes.zh-CN.js +57 -3
  79. package/dist/client/locales/runtimes.zh-CN.js.map +1 -1
  80. package/dist/client/locales/skills.en-US.d.ts +1 -0
  81. package/dist/client/locales/skills.en-US.d.ts.map +1 -1
  82. package/dist/client/locales/skills.en-US.js +2 -1
  83. package/dist/client/locales/skills.en-US.js.map +1 -1
  84. package/dist/client/locales/skills.zh-CN.d.ts.map +1 -1
  85. package/dist/client/locales/skills.zh-CN.js +2 -1
  86. package/dist/client/locales/skills.zh-CN.js.map +1 -1
  87. package/dist/client/locales/zh-CN.d.ts.map +1 -1
  88. package/dist/client/locales/zh-CN.js +108 -221
  89. package/dist/client/locales/zh-CN.js.map +1 -1
  90. package/dist/client/online/services.d.ts.map +1 -1
  91. package/dist/client/online/services.js +5 -6
  92. package/dist/client/online/services.js.map +1 -1
  93. package/dist/client/pages/agents/agent-dialog.d.ts +9 -0
  94. package/dist/client/pages/agents/agent-dialog.d.ts.map +1 -0
  95. package/dist/client/pages/agents/agent-dialog.js +106 -0
  96. package/dist/client/pages/agents/agent-dialog.js.map +1 -0
  97. package/dist/client/pages/agents/agent-state.d.ts +11 -0
  98. package/dist/client/pages/agents/agent-state.d.ts.map +1 -0
  99. package/dist/client/pages/agents/agent-state.js +28 -0
  100. package/dist/client/pages/agents/agent-state.js.map +1 -0
  101. package/dist/client/pages/agents/index.d.ts +1 -1
  102. package/dist/client/pages/agents/index.d.ts.map +1 -1
  103. package/dist/client/pages/agents/index.js +6 -100
  104. package/dist/client/pages/agents/index.js.map +1 -1
  105. package/dist/client/pages/agents/runner-agents.d.ts +3 -0
  106. package/dist/client/pages/agents/runner-agents.d.ts.map +1 -0
  107. package/dist/client/pages/agents/runner-agents.js +172 -0
  108. package/dist/client/pages/agents/runner-agents.js.map +1 -0
  109. package/dist/client/pages/runtimes/connect.js +1 -1
  110. package/dist/client/pages/runtimes/connect.js.map +1 -1
  111. package/dist/client/pages/runtimes/index.d.ts.map +1 -1
  112. package/dist/client/pages/runtimes/index.js +3 -2
  113. package/dist/client/pages/runtimes/index.js.map +1 -1
  114. package/dist/client/pages/runtimes/runner-cells.d.ts +1 -1
  115. package/dist/client/pages/runtimes/runner-cells.d.ts.map +1 -1
  116. package/dist/client/pages/runtimes/runner-cells.js +4 -1
  117. package/dist/client/pages/runtimes/runner-cells.js.map +1 -1
  118. package/dist/client/pages/runtimes/runner-sheet.d.ts.map +1 -1
  119. package/dist/client/pages/runtimes/runner-sheet.js +12 -3
  120. package/dist/client/pages/runtimes/runner-sheet.js.map +1 -1
  121. package/dist/client/pages/runtimes/workspace-diagnostics.d.ts +9 -0
  122. package/dist/client/pages/runtimes/workspace-diagnostics.d.ts.map +1 -0
  123. package/dist/client/pages/runtimes/workspace-diagnostics.js +68 -0
  124. package/dist/client/pages/runtimes/workspace-diagnostics.js.map +1 -0
  125. package/dist/client/pages/skills/detail/index.d.ts.map +1 -1
  126. package/dist/client/pages/skills/detail/index.js +7 -10
  127. package/dist/client/pages/skills/detail/index.js.map +1 -1
  128. package/dist/client/pages/skills/index.d.ts.map +1 -1
  129. package/dist/client/pages/skills/index.js +2 -2
  130. package/dist/client/pages/skills/index.js.map +1 -1
  131. package/dist/client/pages/usage/index.js +2 -2
  132. package/dist/client/pages/usage/index.js.map +1 -1
  133. package/dist/client/profile/default-agent.d.ts.map +1 -1
  134. package/dist/client/profile/default-agent.js +2 -4
  135. package/dist/client/profile/default-agent.js.map +1 -1
  136. package/dist/client/routes.d.ts +1 -1
  137. package/dist/client/routes.d.ts.map +1 -1
  138. package/dist/client/routes.js +1 -13
  139. package/dist/client/routes.js.map +1 -1
  140. package/dist/client/runs/format-wait.d.ts +21 -0
  141. package/dist/client/runs/format-wait.d.ts.map +1 -0
  142. package/dist/client/runs/format-wait.js +90 -0
  143. package/dist/client/runs/format-wait.js.map +1 -0
  144. package/dist/client/runs/subject-runs.d.ts +5 -0
  145. package/dist/client/runs/subject-runs.d.ts.map +1 -1
  146. package/dist/client/runs/subject-runs.js +13 -7
  147. package/dist/client/runs/subject-runs.js.map +1 -1
  148. package/dist/client/runs.d.ts +3 -1
  149. package/dist/client/runs.d.ts.map +1 -1
  150. package/dist/client/runs.js +3 -1
  151. package/dist/client/runs.js.map +1 -1
  152. package/dist/database/migrations/.manifest.json +62 -2
  153. package/dist/database/migrations/202610020003_ag_create_runs.d.ts.map +1 -1
  154. package/dist/database/migrations/202610020003_ag_create_runs.js +0 -1
  155. package/dist/database/migrations/202610020003_ag_create_runs.js.map +1 -1
  156. package/dist/database/migrations/202610090001_ag_add_team_runner_variables.d.ts +9 -0
  157. package/dist/database/migrations/202610090001_ag_add_team_runner_variables.d.ts.map +1 -0
  158. package/dist/database/migrations/202610090001_ag_add_team_runner_variables.js +28 -0
  159. package/dist/database/migrations/202610090001_ag_add_team_runner_variables.js.map +1 -0
  160. package/dist/database/migrations/202610090002_ag_create_run_requests.d.ts +10 -0
  161. package/dist/database/migrations/202610090002_ag_create_run_requests.d.ts.map +1 -0
  162. package/dist/database/migrations/202610090002_ag_create_run_requests.js +105 -0
  163. package/dist/database/migrations/202610090002_ag_create_run_requests.js.map +1 -0
  164. package/dist/database/migrations/202610090003_ag_add_execution_history.d.ts +4 -0
  165. package/dist/database/migrations/202610090003_ag_add_execution_history.d.ts.map +1 -0
  166. package/dist/database/migrations/202610090003_ag_add_execution_history.js +17 -0
  167. package/dist/database/migrations/202610090003_ag_add_execution_history.js.map +1 -0
  168. package/dist/database/migrations/202610090004_ag_add_runner_workspace_usage.d.ts +9 -0
  169. package/dist/database/migrations/202610090004_ag_add_runner_workspace_usage.d.ts.map +1 -0
  170. package/dist/database/migrations/202610090004_ag_add_runner_workspace_usage.js +22 -0
  171. package/dist/database/migrations/202610090004_ag_add_runner_workspace_usage.js.map +1 -0
  172. package/dist/database/migrations/202610090005_ag_add_runner_variables.d.ts +10 -0
  173. package/dist/database/migrations/202610090005_ag_add_runner_variables.d.ts.map +1 -0
  174. package/dist/database/migrations/202610090005_ag_add_runner_variables.js +36 -0
  175. package/dist/database/migrations/202610090005_ag_add_runner_variables.js.map +1 -0
  176. package/dist/database/migrations/202610100001_ag_index_run_event_types.d.ts +4 -0
  177. package/dist/database/migrations/202610100001_ag_index_run_event_types.d.ts.map +1 -0
  178. package/dist/database/migrations/202610100001_ag_index_run_event_types.js +17 -0
  179. package/dist/database/migrations/202610100001_ag_index_run_event_types.js.map +1 -0
  180. package/dist/database/migrations/202610100002_ag_add_run_git_credentials.d.ts +9 -0
  181. package/dist/database/migrations/202610100002_ag_add_run_git_credentials.d.ts.map +1 -0
  182. package/dist/database/migrations/202610100002_ag_add_run_git_credentials.js +30 -0
  183. package/dist/database/migrations/202610100002_ag_add_run_git_credentials.js.map +1 -0
  184. package/dist/database/migrations/202610110001_ag_add_fresh_sessions.d.ts +4 -0
  185. package/dist/database/migrations/202610110001_ag_add_fresh_sessions.d.ts.map +1 -0
  186. package/dist/database/migrations/202610110001_ag_add_fresh_sessions.js +23 -0
  187. package/dist/database/migrations/202610110001_ag_add_fresh_sessions.js.map +1 -0
  188. package/dist/database/migrations/202610120001_ag_index_run_retries.d.ts +4 -0
  189. package/dist/database/migrations/202610120001_ag_index_run_retries.d.ts.map +1 -0
  190. package/dist/database/migrations/202610120001_ag_index_run_retries.js +17 -0
  191. package/dist/database/migrations/202610120001_ag_index_run_retries.js.map +1 -0
  192. package/dist/database/migrations/202610120002_ag_add_queue_entered_at.d.ts +4 -0
  193. package/dist/database/migrations/202610120002_ag_add_queue_entered_at.d.ts.map +1 -0
  194. package/dist/database/migrations/202610120002_ag_add_queue_entered_at.js +50 -0
  195. package/dist/database/migrations/202610120002_ag_add_queue_entered_at.js.map +1 -0
  196. package/dist/database/migrations/202610240020_ag_runner_agents.d.ts +4 -0
  197. package/dist/database/migrations/202610240020_ag_runner_agents.d.ts.map +1 -0
  198. package/dist/database/migrations/202610240020_ag_runner_agents.js +115 -0
  199. package/dist/database/migrations/202610240020_ag_runner_agents.js.map +1 -0
  200. package/dist/database/migrations/202610240040_ag_discovery_preferences.d.ts +4 -0
  201. package/dist/database/migrations/202610240040_ag_discovery_preferences.d.ts.map +1 -0
  202. package/dist/database/migrations/202610240040_ag_discovery_preferences.js +18 -0
  203. package/dist/database/migrations/202610240040_ag_discovery_preferences.js.map +1 -0
  204. package/dist/database/migrations/202610250001_ag_comment_runner_consent.d.ts +4 -0
  205. package/dist/database/migrations/202610250001_ag_comment_runner_consent.d.ts.map +1 -0
  206. package/dist/database/migrations/202610250001_ag_comment_runner_consent.js +41 -0
  207. package/dist/database/migrations/202610250001_ag_comment_runner_consent.js.map +1 -0
  208. package/dist/database/migrations/202610260001_ag_drop_directory_index.d.ts +4 -0
  209. package/dist/database/migrations/202610260001_ag_drop_directory_index.d.ts.map +1 -0
  210. package/dist/database/migrations/202610260001_ag_drop_directory_index.js +24 -0
  211. package/dist/database/migrations/202610260001_ag_drop_directory_index.js.map +1 -0
  212. package/dist/database/migrations/202610260001_ag_run_revision.d.ts +4 -0
  213. package/dist/database/migrations/202610260001_ag_run_revision.d.ts.map +1 -0
  214. package/dist/database/migrations/202610260001_ag_run_revision.js +19 -0
  215. package/dist/database/migrations/202610260001_ag_run_revision.js.map +1 -0
  216. package/dist/package.json +8 -10
  217. package/dist/server/cli/run-credential.js +1 -1
  218. package/dist/server/composition.d.ts +41 -37
  219. package/dist/server/composition.d.ts.map +1 -1
  220. package/dist/server/composition.js +66 -66
  221. package/dist/server/composition.js.map +1 -1
  222. package/dist/server/core/agents/agent.service.d.ts +51 -43
  223. package/dist/server/core/agents/agent.service.d.ts.map +1 -1
  224. package/dist/server/core/agents/agent.service.js +342 -317
  225. package/dist/server/core/agents/agent.service.js.map +1 -1
  226. package/dist/server/core/agents/agent.store.d.ts +24 -12
  227. package/dist/server/core/agents/agent.store.d.ts.map +1 -1
  228. package/dist/server/core/agents/agent.store.js +64 -66
  229. package/dist/server/core/agents/agent.store.js.map +1 -1
  230. package/dist/server/core/agents/agent.views.d.ts +29 -0
  231. package/dist/server/core/agents/agent.views.d.ts.map +1 -0
  232. package/dist/server/core/agents/agent.views.js +86 -0
  233. package/dist/server/core/agents/agent.views.js.map +1 -0
  234. package/dist/server/core/agents/discovery.d.ts +12 -0
  235. package/dist/server/core/agents/discovery.d.ts.map +1 -0
  236. package/dist/server/core/agents/discovery.js +39 -0
  237. package/dist/server/core/agents/discovery.js.map +1 -0
  238. package/dist/server/core/agents/index.d.ts +2 -1
  239. package/dist/server/core/agents/index.d.ts.map +1 -1
  240. package/dist/server/core/agents/index.js +2 -1
  241. package/dist/server/core/agents/index.js.map +1 -1
  242. package/dist/server/core/brief/brief.js +1 -1
  243. package/dist/server/core/brief/brief.js.map +1 -1
  244. package/dist/server/core/callers/index.d.ts +1 -1
  245. package/dist/server/core/conversations/availability.d.ts +11 -10
  246. package/dist/server/core/conversations/availability.d.ts.map +1 -1
  247. package/dist/server/core/conversations/availability.js +14 -32
  248. package/dist/server/core/conversations/availability.js.map +1 -1
  249. package/dist/server/core/conversations/chat-settings.d.ts.map +1 -1
  250. package/dist/server/core/conversations/chat-settings.js +5 -3
  251. package/dist/server/core/conversations/chat-settings.js.map +1 -1
  252. package/dist/server/core/conversations/conversation.service.d.ts +3 -5
  253. package/dist/server/core/conversations/conversation.service.d.ts.map +1 -1
  254. package/dist/server/core/conversations/conversation.service.js +161 -221
  255. package/dist/server/core/conversations/conversation.service.js.map +1 -1
  256. package/dist/server/core/conversations/ports.d.ts +3 -17
  257. package/dist/server/core/conversations/ports.d.ts.map +1 -1
  258. package/dist/server/core/reports/report.service.d.ts.map +1 -1
  259. package/dist/server/core/reports/report.service.js +5 -2
  260. package/dist/server/core/reports/report.service.js.map +1 -1
  261. package/dist/server/core/runs/availability.d.ts +18 -7
  262. package/dist/server/core/runs/availability.d.ts.map +1 -1
  263. package/dist/server/core/runs/availability.js +35 -24
  264. package/dist/server/core/runs/availability.js.map +1 -1
  265. package/dist/server/core/runs/claim.d.ts +47 -67
  266. package/dist/server/core/runs/claim.d.ts.map +1 -1
  267. package/dist/server/core/runs/claim.js +805 -313
  268. package/dist/server/core/runs/claim.js.map +1 -1
  269. package/dist/server/core/runs/comment-consent.d.ts +24 -0
  270. package/dist/server/core/runs/comment-consent.d.ts.map +1 -0
  271. package/dist/server/core/runs/comment-consent.js +116 -0
  272. package/dist/server/core/runs/comment-consent.js.map +1 -0
  273. package/dist/server/core/runs/eligibility.d.ts +51 -0
  274. package/dist/server/core/runs/eligibility.d.ts.map +1 -0
  275. package/dist/server/core/runs/eligibility.js +44 -0
  276. package/dist/server/core/runs/eligibility.js.map +1 -0
  277. package/dist/server/core/runs/execution-view.d.ts +10 -0
  278. package/dist/server/core/runs/execution-view.d.ts.map +1 -0
  279. package/dist/server/core/runs/execution-view.js +25 -0
  280. package/dist/server/core/runs/execution-view.js.map +1 -0
  281. package/dist/server/core/runs/execution.d.ts +13 -0
  282. package/dist/server/core/runs/execution.d.ts.map +1 -0
  283. package/dist/server/core/runs/execution.js +85 -0
  284. package/dist/server/core/runs/execution.js.map +1 -0
  285. package/dist/server/core/runs/extensions.d.ts +59 -4
  286. package/dist/server/core/runs/extensions.d.ts.map +1 -1
  287. package/dist/server/core/runs/extensions.js +45 -9
  288. package/dist/server/core/runs/extensions.js.map +1 -1
  289. package/dist/server/core/runs/git-credentials.d.ts +43 -0
  290. package/dist/server/core/runs/git-credentials.d.ts.map +1 -0
  291. package/dist/server/core/runs/git-credentials.js +129 -0
  292. package/dist/server/core/runs/git-credentials.js.map +1 -0
  293. package/dist/server/core/runs/identity.d.ts +9 -0
  294. package/dist/server/core/runs/identity.d.ts.map +1 -0
  295. package/dist/server/core/runs/identity.js +75 -0
  296. package/dist/server/core/runs/identity.js.map +1 -0
  297. package/dist/server/core/runs/index.d.ts +9 -5
  298. package/dist/server/core/runs/index.d.ts.map +1 -1
  299. package/dist/server/core/runs/index.js +9 -5
  300. package/dist/server/core/runs/index.js.map +1 -1
  301. package/dist/server/core/runs/input-gate.d.ts +34 -0
  302. package/dist/server/core/runs/input-gate.d.ts.map +1 -0
  303. package/dist/server/core/runs/input-gate.js +58 -0
  304. package/dist/server/core/runs/input-gate.js.map +1 -0
  305. package/dist/server/core/runs/lifecycle.d.ts +9 -19
  306. package/dist/server/core/runs/lifecycle.d.ts.map +1 -1
  307. package/dist/server/core/runs/lifecycle.js +98 -50
  308. package/dist/server/core/runs/lifecycle.js.map +1 -1
  309. package/dist/server/core/runs/policy.d.ts +8 -0
  310. package/dist/server/core/runs/policy.d.ts.map +1 -1
  311. package/dist/server/core/runs/policy.js +10 -0
  312. package/dist/server/core/runs/policy.js.map +1 -1
  313. package/dist/server/core/runs/ports.d.ts +91 -28
  314. package/dist/server/core/runs/ports.d.ts.map +1 -1
  315. package/dist/server/core/runs/ports.js +2 -13
  316. package/dist/server/core/runs/ports.js.map +1 -1
  317. package/dist/server/core/runs/preview.d.ts.map +1 -1
  318. package/dist/server/core/runs/preview.js +3 -3
  319. package/dist/server/core/runs/preview.js.map +1 -1
  320. package/dist/server/core/runs/run-data-purge.d.ts +15 -0
  321. package/dist/server/core/runs/run-data-purge.d.ts.map +1 -0
  322. package/dist/server/core/runs/run-data-purge.js +82 -0
  323. package/dist/server/core/runs/run-data-purge.js.map +1 -0
  324. package/dist/server/core/runs/run-requests.d.ts +245 -0
  325. package/dist/server/core/runs/run-requests.d.ts.map +1 -0
  326. package/dist/server/core/runs/run-requests.js +543 -0
  327. package/dist/server/core/runs/run-requests.js.map +1 -0
  328. package/dist/server/core/runs/run.service.d.ts +96 -11
  329. package/dist/server/core/runs/run.service.d.ts.map +1 -1
  330. package/dist/server/core/runs/run.service.js +921 -96
  331. package/dist/server/core/runs/run.service.js.map +1 -1
  332. package/dist/server/core/runs/run.store.d.ts +25 -3
  333. package/dist/server/core/runs/run.store.d.ts.map +1 -1
  334. package/dist/server/core/runs/run.store.js +52 -2
  335. package/dist/server/core/runs/run.store.js.map +1 -1
  336. package/dist/server/core/runs/runner-view.d.ts +6 -4
  337. package/dist/server/core/runs/runner-view.d.ts.map +1 -1
  338. package/dist/server/core/runs/runner-view.js +13 -11
  339. package/dist/server/core/runs/runner-view.js.map +1 -1
  340. package/dist/server/core/runs/selection.d.ts +13 -0
  341. package/dist/server/core/runs/selection.d.ts.map +1 -0
  342. package/dist/server/core/runs/selection.js +21 -0
  343. package/dist/server/core/runs/selection.js.map +1 -0
  344. package/dist/server/core/runs/sweeper.d.ts +1 -0
  345. package/dist/server/core/runs/sweeper.d.ts.map +1 -1
  346. package/dist/server/core/runs/sweeper.js +55 -10
  347. package/dist/server/core/runs/sweeper.js.map +1 -1
  348. package/dist/server/core/runs/transitions.d.ts +2 -2
  349. package/dist/server/core/runs/transitions.d.ts.map +1 -1
  350. package/dist/server/core/runs/transitions.js +80 -8
  351. package/dist/server/core/runs/transitions.js.map +1 -1
  352. package/dist/server/core/runs/workload.d.ts +3 -3
  353. package/dist/server/core/runs/workload.d.ts.map +1 -1
  354. package/dist/server/core/runs/workload.js +154 -45
  355. package/dist/server/core/runs/workload.js.map +1 -1
  356. package/dist/server/core/skills/skill.service.d.ts +6 -2
  357. package/dist/server/core/skills/skill.service.d.ts.map +1 -1
  358. package/dist/server/core/skills/skill.service.js +43 -58
  359. package/dist/server/core/skills/skill.service.js.map +1 -1
  360. package/dist/server/core/skills/skill.store.d.ts +14 -0
  361. package/dist/server/core/skills/skill.store.d.ts.map +1 -1
  362. package/dist/server/core/skills/skill.store.js +30 -0
  363. package/dist/server/core/skills/skill.store.js.map +1 -1
  364. package/dist/server/core/variables/index.d.ts +1 -1
  365. package/dist/server/core/variables/index.d.ts.map +1 -1
  366. package/dist/server/core/variables/index.js +1 -1
  367. package/dist/server/core/variables/index.js.map +1 -1
  368. package/dist/server/core/variables/variable.service.d.ts +38 -6
  369. package/dist/server/core/variables/variable.service.d.ts.map +1 -1
  370. package/dist/server/core/variables/variable.service.js +201 -30
  371. package/dist/server/core/variables/variable.service.js.map +1 -1
  372. package/dist/server/distribution/dist.service.d.ts +27 -5
  373. package/dist/server/distribution/dist.service.d.ts.map +1 -1
  374. package/dist/server/distribution/dist.service.js +41 -5
  375. package/dist/server/distribution/dist.service.js.map +1 -1
  376. package/dist/server/distribution/download-tokens.d.ts +1 -1
  377. package/dist/server/distribution/download-tokens.d.ts.map +1 -1
  378. package/dist/server/distribution/download-tokens.js +7 -4
  379. package/dist/server/distribution/download-tokens.js.map +1 -1
  380. package/dist/server/distribution/index.d.ts +11 -3
  381. package/dist/server/distribution/index.d.ts.map +1 -1
  382. package/dist/server/distribution/index.js +31 -8
  383. package/dist/server/distribution/index.js.map +1 -1
  384. package/dist/server/jobs/index.d.ts +1 -1
  385. package/dist/server/jobs/index.d.ts.map +1 -1
  386. package/dist/server/jobs/index.js +1 -1
  387. package/dist/server/jobs/index.js.map +1 -1
  388. package/dist/server/jobs/job.service.d.ts +3 -1
  389. package/dist/server/jobs/job.service.d.ts.map +1 -1
  390. package/dist/server/jobs/job.service.js +11 -3
  391. package/dist/server/jobs/job.service.js.map +1 -1
  392. package/dist/server/jobs/spec.d.ts +4 -0
  393. package/dist/server/jobs/spec.d.ts.map +1 -1
  394. package/dist/server/jobs/spec.js +6 -0
  395. package/dist/server/jobs/spec.js.map +1 -1
  396. package/dist/server/kernel/errors.d.ts +1 -1
  397. package/dist/server/kernel/errors.d.ts.map +1 -1
  398. package/dist/server/kernel/errors.js +1 -0
  399. package/dist/server/kernel/errors.js.map +1 -1
  400. package/dist/server/kernel/events.d.ts +34 -9
  401. package/dist/server/kernel/events.d.ts.map +1 -1
  402. package/dist/server/kernel/events.js.map +1 -1
  403. package/dist/server/kernel/redaction.d.ts +2 -0
  404. package/dist/server/kernel/redaction.d.ts.map +1 -1
  405. package/dist/server/kernel/redaction.js +17 -0
  406. package/dist/server/kernel/redaction.js.map +1 -1
  407. package/dist/server/kernel/scopes.d.ts.map +1 -1
  408. package/dist/server/kernel/scopes.js +3 -1
  409. package/dist/server/kernel/scopes.js.map +1 -1
  410. package/dist/server/online/index.d.ts +0 -5
  411. package/dist/server/online/index.d.ts.map +1 -1
  412. package/dist/server/online/index.js +0 -5
  413. package/dist/server/online/index.js.map +1 -1
  414. package/dist/server/online/routes.d.ts.map +1 -1
  415. package/dist/server/online/routes.js +6 -35
  416. package/dist/server/online/routes.js.map +1 -1
  417. package/dist/server/providers/agents.d.ts +8 -25
  418. package/dist/server/providers/agents.d.ts.map +1 -1
  419. package/dist/server/providers/agents.js +11 -75
  420. package/dist/server/providers/agents.js.map +1 -1
  421. package/dist/server/providers/runners.d.ts.map +1 -1
  422. package/dist/server/providers/runners.js +23 -0
  423. package/dist/server/providers/runners.js.map +1 -1
  424. package/dist/server/routes/admin.d.ts.map +1 -1
  425. package/dist/server/routes/admin.js +158 -209
  426. package/dist/server/routes/admin.js.map +1 -1
  427. package/dist/server/routes/chat.d.ts.map +1 -1
  428. package/dist/server/routes/chat.js +12 -70
  429. package/dist/server/routes/chat.js.map +1 -1
  430. package/dist/server/routes/index.d.ts.map +1 -1
  431. package/dist/server/routes/index.js +11 -1
  432. package/dist/server/routes/index.js.map +1 -1
  433. package/dist/server/routes/roster.d.ts.map +1 -1
  434. package/dist/server/routes/roster.js +6 -21
  435. package/dist/server/routes/roster.js.map +1 -1
  436. package/dist/server/routes/run-requests.d.ts +6 -0
  437. package/dist/server/routes/run-requests.d.ts.map +1 -0
  438. package/dist/server/routes/run-requests.js +216 -0
  439. package/dist/server/routes/run-requests.js.map +1 -0
  440. package/dist/server/routes/runners/admin.d.ts +9 -1
  441. package/dist/server/routes/runners/admin.d.ts.map +1 -1
  442. package/dist/server/routes/runners/admin.js +62 -24
  443. package/dist/server/routes/runners/admin.js.map +1 -1
  444. package/dist/server/routes/runners/dist.d.ts +5 -1
  445. package/dist/server/routes/runners/dist.d.ts.map +1 -1
  446. package/dist/server/routes/runners/dist.js +36 -9
  447. package/dist/server/routes/runners/dist.js.map +1 -1
  448. package/dist/server/routes/runners/install.d.ts.map +1 -1
  449. package/dist/server/routes/runners/install.js +98 -15
  450. package/dist/server/routes/runners/install.js.map +1 -1
  451. package/dist/server/routes/runners/runner.d.ts.map +1 -1
  452. package/dist/server/routes/runners/runner.js +27 -7
  453. package/dist/server/routes/runners/runner.js.map +1 -1
  454. package/dist/server/routes/runners/runs.d.ts +1 -1
  455. package/dist/server/routes/runners/runs.d.ts.map +1 -1
  456. package/dist/server/routes/runners/runs.js +23 -3
  457. package/dist/server/routes/runners/runs.js.map +1 -1
  458. package/dist/server/routes/schemas.d.ts +37 -8
  459. package/dist/server/routes/schemas.d.ts.map +1 -1
  460. package/dist/server/routes/schemas.js +381 -84
  461. package/dist/server/routes/schemas.js.map +1 -1
  462. package/dist/server/runners/index.d.ts +1 -0
  463. package/dist/server/runners/index.d.ts.map +1 -1
  464. package/dist/server/runners/index.js +1 -0
  465. package/dist/server/runners/index.js.map +1 -1
  466. package/dist/server/runners/runner.service.d.ts +32 -8
  467. package/dist/server/runners/runner.service.d.ts.map +1 -1
  468. package/dist/server/runners/runner.service.js +130 -14
  469. package/dist/server/runners/runner.service.js.map +1 -1
  470. package/dist/server/runners/runner.store.d.ts +10 -1
  471. package/dist/server/runners/runner.store.d.ts.map +1 -1
  472. package/dist/server/runners/runner.store.js +117 -4
  473. package/dist/server/runners/runner.store.js.map +1 -1
  474. package/dist/server/runners/sweeper.d.ts +5 -0
  475. package/dist/server/runners/sweeper.d.ts.map +1 -1
  476. package/dist/server/runners/sweeper.js +4 -2
  477. package/dist/server/runners/sweeper.js.map +1 -1
  478. package/dist/server/runners/workspaces.d.ts +33 -0
  479. package/dist/server/runners/workspaces.d.ts.map +1 -0
  480. package/dist/server/runners/workspaces.js +248 -0
  481. package/dist/server/runners/workspaces.js.map +1 -0
  482. package/dist/server/testing.d.ts +0 -2
  483. package/dist/server/testing.d.ts.map +1 -1
  484. package/dist/server/testing.js +1 -4
  485. package/dist/server/testing.js.map +1 -1
  486. package/dist/server/tokens.d.ts +13 -9
  487. package/dist/server/tokens.d.ts.map +1 -1
  488. package/dist/server/tokens.js +7 -2
  489. package/dist/server/tokens.js.map +1 -1
  490. package/dist/shared/access.d.ts +2 -6
  491. package/dist/shared/access.d.ts.map +1 -1
  492. package/dist/shared/access.js +2 -6
  493. package/dist/shared/access.js.map +1 -1
  494. package/dist/shared/agents.d.ts +128 -167
  495. package/dist/shared/agents.d.ts.map +1 -1
  496. package/dist/shared/agents.js +46 -97
  497. package/dist/shared/agents.js.map +1 -1
  498. package/dist/shared/conversations.d.ts +43 -89
  499. package/dist/shared/conversations.d.ts.map +1 -1
  500. package/dist/shared/conversations.js +5 -24
  501. package/dist/shared/conversations.js.map +1 -1
  502. package/dist/shared/model-catalog.d.ts +48 -0
  503. package/dist/shared/model-catalog.d.ts.map +1 -0
  504. package/dist/shared/model-catalog.js +148 -0
  505. package/dist/shared/model-catalog.js.map +1 -0
  506. package/dist/shared/models.js +1 -1
  507. package/dist/shared/models.js.map +1 -1
  508. package/dist/shared/runners.d.ts +107 -17
  509. package/dist/shared/runners.d.ts.map +1 -1
  510. package/dist/shared/runners.js +49 -7
  511. package/dist/shared/runners.js.map +1 -1
  512. package/dist/shared/runs.d.ts +213 -1
  513. package/dist/shared/runs.d.ts.map +1 -1
  514. package/dist/shared/runs.js +24 -0
  515. package/dist/shared/runs.js.map +1 -1
  516. package/dist/shared/skills.d.ts +8 -4
  517. package/dist/shared/skills.d.ts.map +1 -1
  518. package/dist/shared/skills.js +7 -2
  519. package/dist/shared/skills.js.map +1 -1
  520. package/dist/shared/variables.d.ts +22 -0
  521. package/dist/shared/variables.d.ts.map +1 -1
  522. package/dist/shared/variables.js +10 -2
  523. package/dist/shared/variables.js.map +1 -1
  524. package/package.json +24 -26
  525. package/skills/nocobase-app-plugin-agents/SKILL.md +8 -7
  526. package/skills/nocobase-app-plugin-agents/references/agents.md +30 -0
  527. package/skills/nocobase-app-plugin-agents/references/distribution.md +21 -4
  528. package/skills/nocobase-app-plugin-agents/references/extension-points.md +30 -20
  529. package/skills/nocobase-app-plugin-agents/references/model-services.md +17 -0
  530. package/skills/nocobase-app-plugin-agents/references/runners.md +15 -1
  531. package/skills/nocobase-app-plugin-agents/references/setup-and-config.md +4 -7
  532. package/skills/nocobase-app-plugin-agents/references/variables-skills-usage.md +6 -4
  533. package/skills/nocobase-app-plugin-agents/references/verification-and-troubleshooting.md +33 -32
  534. package/dist/client/chat/settings-section.d.ts +0 -5
  535. package/dist/client/chat/settings-section.d.ts.map +0 -1
  536. package/dist/client/chat/settings-section.js +0 -76
  537. package/dist/client/chat/settings-section.js.map +0 -1
  538. package/dist/client/components/agent-fields.d.ts +0 -45
  539. package/dist/client/components/agent-fields.d.ts.map +0 -1
  540. package/dist/client/components/agent-fields.js +0 -83
  541. package/dist/client/components/agent-fields.js.map +0 -1
  542. package/dist/client/components/agent-type.d.ts +0 -32
  543. package/dist/client/components/agent-type.d.ts.map +0 -1
  544. package/dist/client/components/agent-type.js +0 -80
  545. package/dist/client/components/agent-type.js.map +0 -1
  546. package/dist/client/components/model-entries.d.ts +0 -35
  547. package/dist/client/components/model-entries.d.ts.map +0 -1
  548. package/dist/client/components/model-entries.js +0 -82
  549. package/dist/client/components/model-entries.js.map +0 -1
  550. package/dist/client/hooks/use-agent-save.d.ts +0 -9
  551. package/dist/client/hooks/use-agent-save.d.ts.map +0 -1
  552. package/dist/client/hooks/use-agent-save.js +0 -40
  553. package/dist/client/hooks/use-agent-save.js.map +0 -1
  554. package/dist/client/hooks/use-model-catalog.d.ts +0 -9
  555. package/dist/client/hooks/use-model-catalog.d.ts.map +0 -1
  556. package/dist/client/hooks/use-model-catalog.js +0 -27
  557. package/dist/client/hooks/use-model-catalog.js.map +0 -1
  558. package/dist/client/lib/tool-policy.d.ts +0 -5
  559. package/dist/client/lib/tool-policy.d.ts.map +0 -1
  560. package/dist/client/lib/tool-policy.js +0 -10
  561. package/dist/client/lib/tool-policy.js.map +0 -1
  562. package/dist/client/online/default-model.d.ts +0 -5
  563. package/dist/client/online/default-model.d.ts.map +0 -1
  564. package/dist/client/online/default-model.js +0 -65
  565. package/dist/client/online/default-model.js.map +0 -1
  566. package/dist/client/pages/agents/agent-model.d.ts +0 -73
  567. package/dist/client/pages/agents/agent-model.d.ts.map +0 -1
  568. package/dist/client/pages/agents/agent-model.js +0 -172
  569. package/dist/client/pages/agents/agent-model.js.map +0 -1
  570. package/dist/client/pages/agents/detail/brief-preview.d.ts +0 -7
  571. package/dist/client/pages/agents/detail/brief-preview.d.ts.map +0 -1
  572. package/dist/client/pages/agents/detail/brief-preview.js +0 -43
  573. package/dist/client/pages/agents/detail/brief-preview.js.map +0 -1
  574. package/dist/client/pages/agents/detail/history.d.ts +0 -11
  575. package/dist/client/pages/agents/detail/history.d.ts.map +0 -1
  576. package/dist/client/pages/agents/detail/history.js +0 -149
  577. package/dist/client/pages/agents/detail/history.js.map +0 -1
  578. package/dist/client/pages/agents/detail/index.d.ts +0 -3
  579. package/dist/client/pages/agents/detail/index.d.ts.map +0 -1
  580. package/dist/client/pages/agents/detail/index.js +0 -241
  581. package/dist/client/pages/agents/detail/index.js.map +0 -1
  582. package/dist/client/pages/agents/detail/sections.d.ts +0 -22
  583. package/dist/client/pages/agents/detail/sections.d.ts.map +0 -1
  584. package/dist/client/pages/agents/detail/sections.js +0 -311
  585. package/dist/client/pages/agents/detail/sections.js.map +0 -1
  586. package/dist/client/pages/agents/detail/tab-draft.d.ts +0 -43
  587. package/dist/client/pages/agents/detail/tab-draft.d.ts.map +0 -1
  588. package/dist/client/pages/agents/detail/tab-draft.js +0 -115
  589. package/dist/client/pages/agents/detail/tab-draft.js.map +0 -1
  590. package/dist/client/pages/agents/detail/unsaved-bar.d.ts +0 -13
  591. package/dist/client/pages/agents/detail/unsaved-bar.d.ts.map +0 -1
  592. package/dist/client/pages/agents/detail/unsaved-bar.js +0 -21
  593. package/dist/client/pages/agents/detail/unsaved-bar.js.map +0 -1
  594. package/dist/client/pages/agents/new.d.ts +0 -3
  595. package/dist/client/pages/agents/new.d.ts.map +0 -1
  596. package/dist/client/pages/agents/new.js +0 -149
  597. package/dist/client/pages/agents/new.js.map +0 -1
  598. package/dist/client/pages/agents/preset-picker.d.ts +0 -8
  599. package/dist/client/pages/agents/preset-picker.d.ts.map +0 -1
  600. package/dist/client/pages/agents/preset-picker.js +0 -41
  601. package/dist/client/pages/agents/preset-picker.js.map +0 -1
  602. package/dist/server/core/consultations/consultation.service.d.ts +0 -59
  603. package/dist/server/core/consultations/consultation.service.d.ts.map +0 -1
  604. package/dist/server/core/consultations/consultation.service.js +0 -195
  605. package/dist/server/core/consultations/consultation.service.js.map +0 -1
  606. package/dist/server/core/consultations/index.d.ts +0 -3
  607. package/dist/server/core/consultations/index.d.ts.map +0 -1
  608. package/dist/server/core/consultations/index.js +0 -3
  609. package/dist/server/core/consultations/index.js.map +0 -1
  610. package/dist/server/core/consultations/prompt.d.ts +0 -41
  611. package/dist/server/core/consultations/prompt.d.ts.map +0 -1
  612. package/dist/server/core/consultations/prompt.js +0 -64
  613. package/dist/server/core/consultations/prompt.js.map +0 -1
  614. package/dist/server/core/presets/index.d.ts +0 -19
  615. package/dist/server/core/presets/index.d.ts.map +0 -1
  616. package/dist/server/core/presets/index.js +0 -28
  617. package/dist/server/core/presets/index.js.map +0 -1
  618. package/dist/server/online/cli-command.d.ts +0 -20
  619. package/dist/server/online/cli-command.d.ts.map +0 -1
  620. package/dist/server/online/cli-command.js +0 -345
  621. package/dist/server/online/cli-command.js.map +0 -1
  622. package/dist/server/online/consult-tool.d.ts +0 -54
  623. package/dist/server/online/consult-tool.d.ts.map +0 -1
  624. package/dist/server/online/consult-tool.js +0 -177
  625. package/dist/server/online/consult-tool.js.map +0 -1
  626. package/dist/server/online/executor.d.ts +0 -83
  627. package/dist/server/online/executor.d.ts.map +0 -1
  628. package/dist/server/online/executor.js +0 -714
  629. package/dist/server/online/executor.js.map +0 -1
  630. package/dist/server/online/sandbox.d.ts +0 -32
  631. package/dist/server/online/sandbox.d.ts.map +0 -1
  632. package/dist/server/online/sandbox.js +0 -246
  633. package/dist/server/online/sandbox.js.map +0 -1
  634. package/dist/server/online/scripted.d.ts +0 -12
  635. package/dist/server/online/scripted.d.ts.map +0 -1
  636. package/dist/server/online/scripted.js +0 -176
  637. package/dist/server/online/scripted.js.map +0 -1
  638. package/dist/server/online/tools.d.ts +0 -17
  639. package/dist/server/online/tools.d.ts.map +0 -1
  640. package/dist/server/online/tools.js +0 -31
  641. package/dist/server/online/tools.js.map +0 -1
  642. package/dist/server/vendor/THIRD_PARTY_LICENSES.txt +0 -782
  643. package/dist/server/vendor/just-bash.d.ts +0 -83
  644. package/dist/server/vendor/just-bash.js +0 -2102
  645. package/dist/shared/presets.d.ts +0 -30
  646. package/dist/shared/presets.d.ts.map +0 -1
  647. package/dist/shared/presets.js +0 -2
  648. package/dist/shared/presets.js.map +0 -1
  649. package/skills/nocobase-app-plugin-agents/references/online-agents.md +0 -31
@@ -1,3 +1,4 @@
1
+ import { GLOBAL_SKILL_TARGET } from '../../../shared/skills.js';
1
2
  /**
2
3
  * Claiming: handing queued runs to a runner that fits them, within each agent's concurrency limit, one run per
3
4
  * (agent, subject, thread) at a time, through the runners' one long-poll (`routes/runners/runner.ts`), which hands
@@ -6,7 +7,10 @@
6
7
  * team or belongs to the person who woke the agent, its features cover what the run requires (worked out from the
7
8
  * payload: checkouts, directories, skills, secrets), its owner's local policy (`Runner.policy`) lets it take the
8
9
  * agent's runs on the run's subject and repositories, and it has a free slot. A working directory that is a directory
9
- * on one runner pins the run to that runner, one run at a time per directory.
10
+ * on one runner pins the run to that runner, one run at a time per directory. A run's variables go to the runner that
11
+ * takes it, personal ones included, unless one of them is for team runners only (`teamRunnersOnly`): then only a team
12
+ * runner takes the run, and the variables that asked for one are noted on it (`teamOnlyVariables`), so its wait says
13
+ * which.
10
14
  *
11
15
  * A slot is free for a run when the runner's total has room and so does the coding tool it runs with: the tool's limit
12
16
  * (`Runner.toolSlots`, never above the total) over the runs of that tool the runner holds here, and what the runner
@@ -14,6 +18,10 @@
14
18
  * agent's entries whose tool the runner runs and has room for, so an agent listing two tools runs with the second while
15
19
  * the first is full; a run none of whose tools has room is passed over for the runs behind it.
16
20
  *
21
+ * Variables are checked and opened in the claim's own transaction, on its connection: which ones the assembled payload
22
+ * gets, whether any is for team runners only, and their values, so what the runner receives is what was checked. The
23
+ * names taken from the runner go in `workspace.passthrough`, without values.
24
+ *
17
25
  * A claim is one transaction per run. It first writes the agent's row (`lockAgentForClaim`): on databases with row
18
26
  * locks, concurrent claims for the same agent wait there, so the concurrency count read next is current. The run is
19
27
  * then taken with a guarded update (`status = 'queued'`), so two runners can never take the same run. The payload is
@@ -23,38 +31,43 @@
23
31
  * Portable by design: the database layer offers no `FOR UPDATE SKIP LOCKED` or advisory locks, and the guarded update
24
32
  * plus the agent-row write give the same guarantees on every dialect (SQLite serializes writers anyway).
25
33
  *
26
- * Runners take only runner agents' runs. Online agents' runs are taken by the application itself (`claimServer`, for the
27
- * server executor of each instance), through the same guarded update, so two instances never take the same run: the
28
- * holder is the instance (`server:<instance>`), there is no per-agent or per-holder limit, and the payload is a brief,
29
- * the run's token and its skills (read in the run's sandbox, `online/sandbox.ts`), without directories, variables or
30
- * mounts.
34
+ * Polls are the hot path: every runner walks the same candidates, so a write per candidate is a write per candidate per
35
+ * runner per poll. Everything that rules a candidate out is read outside any transaction — identity, `fits()`, slots,
36
+ * concurrency and comment consent — and a transaction opens only for the run about to be claimed. The agent row is
37
+ * the only row a claim locks with a self-update, and only in that final transaction after `fits()` (or when a stale
38
+ * identity has to be repaired first, which current work never needs). PostgreSQL runs at READ COMMITTED, where each
39
+ * statement sees the latest committed rows, so no transaction needs no-op writes to refresh a snapshot; comment
40
+ * consent needs no chain lock, because confirmation is final and rejection or Run as me end the chain's queued runs,
41
+ * which the guarded update sees. A new write or lock here needs a measured reason (`tests/claim-concurrency.test.ts`
42
+ * counts them, and the lock waits on PostgreSQL).
43
+
31
44
  */
32
- import { policyAllowsAgent, policyAllowsRepo, policyAllowsSubject, TIMINGS, } from '@nocobase/agent-protocol';
33
- import { runsTool, toolLimit } from '../../../shared/runners.js';
45
+ import { ANNOUNCED_FAILURES, isProtocolSupported, policyAllowsAgent, policyAllowsRepo, policyAllowsSubject, TIMINGS, } from '@nocobase/agent-protocol';
46
+ import { runnerLowOnDisk, runsEntry, toolLimit, supportsWorkspaceKey, } from '../../../shared/runners.js';
47
+ import { queueStart } from './selection.js';
34
48
  import { createHash } from 'node:crypto';
35
- import { onlineEntriesOrDefault, runnerEntries, sameEntry, } from '../../../shared/agents.js';
36
49
  import { later } from '../../kernel/clock.js';
37
50
  import { notFound } from '../../kernel/errors.js';
38
51
  import { runSecretsKey } from '../../kernel/redaction.js';
39
- import { cleanList, covers } from '../../kernel/values.js';
52
+ import { cleanList, covers, jsonObject } from '../../kernel/values.js';
40
53
  import { findAgent, lockAgentForClaim } from '../agents/index.js';
41
- import { consultSection, } from '../consultations/index.js';
42
- import { joinBrief, onlineSkillsSection, renderBrief, } from '../brief/index.js';
54
+ import { joinBrief, renderBrief } from '../brief/index.js';
43
55
  import { cliPackageFor } from './cli-package.js';
56
+ import { executionHistory } from './execution.js';
57
+ import { describeVariables, variableRefs, } from '../variables/index.js';
44
58
  import { mountsFor, prepareExtensions, repoAccessFor, sessionKeyOf, withSections, } from './extensions.js';
45
- import { dialectOf, } from './ports.js';
46
59
  import { MAX_CLAIM_FAILURES, toolPolicyFor } from './policy.js';
47
60
  import { mintRunToken } from './run-tokens.js';
48
61
  import { runsHeldByTool } from './runner-view.js';
49
- import { ACTIVE, countActive, eventsRepo, findRunRecord, markDelivered, pendingInputs, runsOfKey, runsRepo, sessionsRepo, toInput, toRun, } from './run.store.js';
62
+ import { ACTIVE, countActive, eventsRepo, findRunRecord, markDelivered, pendingInputs, runsOfKey, runsRepo, inputsRepo, sessionsRepo, toInput, toExecutions, toRun, } from './run.store.js';
63
+ import { dropInputs } from './input-gate.js';
50
64
  import { finishRun } from './transitions.js';
65
+ import { queuedIdentityCurrent, refreshQueuedIdentity } from './identity.js';
66
+ import { currentCommentOwner, commentConsent, commentFromOwner, withdrawCommentRequests, } from './comment-consent.js';
67
+ import { insertRunRequest, runRequestsRepo } from './run-requests.js';
51
68
  import { consumeReset, saveBrief } from './workspace.store.js';
52
69
  /** Queued runs looked at per claim, oldest first by priority. */
53
70
  const CANDIDATES = 50;
54
- /** Whether a run holder id is an application instance's (an online run's), not a runner's. */
55
- export function isServerHolder(runnerId) {
56
- return runnerId !== null && runnerId.startsWith('server:');
57
- }
58
71
  class AssemblyError extends Error {
59
72
  cause;
60
73
  constructor(message, cause) {
@@ -70,6 +83,15 @@ class NotForThisRunner extends Error {
70
83
  this.name = 'NotForThisRunner';
71
84
  }
72
85
  }
86
+ /** The run gets variables only team runners may receive, and this runner is a personal one. */
87
+ class TeamRunnersOnly extends NotForThisRunner {
88
+ variables;
89
+ constructor(variables) {
90
+ super();
91
+ this.variables = variables;
92
+ this.name = 'TeamRunnersOnly';
93
+ }
94
+ }
73
95
  function toolRoom(tools) {
74
96
  const left = new Map(Object.entries(tools ?? {}));
75
97
  return {
@@ -82,50 +104,50 @@ function toolRoom(tools) {
82
104
  };
83
105
  }
84
106
  /**
85
- * The entry `runner` takes a run of `agent` with: the first whose tool it has enabled, installed and signed in. Null
86
- * when it has none of them, and always for an online agent, which has no tool.
107
+ * The entry `runner` takes a run of `agent` with: the first whose tool it has enabled, installed and signed in, with
108
+ * the entry's model among those it reports (`runsEntry`). Null when it has none of them.
87
109
  */
88
110
  export function pickEntry(runner, agent) {
89
- return (runnerEntries(agent).find((entry) => runsTool(runner, entry.tool)) ?? null);
111
+ return agent.modelEntries.find((entry) => runsEntry(runner, entry)) ?? null;
90
112
  }
91
113
  /**
92
114
  * The entry `runner` takes a run of `agent` with when some of its tools are full: the first whose tool it runs and
93
115
  * `open` says has room. Null when none has.
94
116
  */
95
117
  export function pickOpenEntry(runner, agent, open) {
96
- return (runnerEntries(agent).find((entry) => runsTool(runner, entry.tool) && open(entry.tool)) ?? null);
118
+ return (agent.modelEntries.find((entry) => runsEntry(runner, entry) && open(entry.tool)) ?? null);
97
119
  }
98
- /** Whether `runner` has one of `agent`'s tools enabled, installed and signed in; never for an online agent. */
120
+ /** Whether `runner` has one of `agent`'s tools and models enabled, installed, signed in and reported. */
99
121
  export function hasTool(runner, agent) {
100
122
  return pickEntry(runner, agent) !== null;
101
123
  }
102
124
  /**
103
- * The entry an online run uses: the one its subject asks for while the agent lists it, else the agent's first; for an
104
- * agent that lists none, the system default chat model (`fallback`).
125
+ * Whether `runner` may run work of `agent` as `actorUserId` that needs `requires`, before any lock is taken and before
126
+ * a payload is known: the agent enabled, its tool and model reported there, its owner's policy, and trust.
105
127
  */
106
- export function onlineEntryOf(agent, wanted, fallback = null) {
107
- const entries = onlineEntriesOrDefault(agent, fallback);
108
- return ((wanted && entries.find((entry) => sameEntry(entry, wanted))) ??
109
- entries[0] ??
110
- null);
111
- }
112
- /** Whether `runner` may run `run` of `agent`, before any lock is taken and before its payload is known. */
113
- export function fits(runner, agent, run) {
114
- if (agent.archivedAt || agent.type !== 'runner')
128
+ export function fitsActor(runner, agent, actorUserId, requires) {
129
+ if (agent.status !== 'enabled' || runnerLowOnDisk(runner))
115
130
  return false;
116
- if (!covers(runner.features, toRun(run).requires))
131
+ if (!isProtocolSupported(runner.protocolVersion) ||
132
+ runner.status === 'upgrade_required' ||
133
+ runner.status === 'revoked')
134
+ return false;
135
+ if (!covers(runner.features, requires))
117
136
  return false;
118
137
  if (!hasTool(runner, agent))
119
138
  return false;
120
139
  if (!policyAllowsAgent(runner.policy, agent))
121
140
  return false;
122
- if (agent.runnerIds.length > 0 && !agent.runnerIds.includes(runner.id))
123
- return false;
124
- return runner.trust === 'team' || runner.ownerUserId === run.actorUserId;
141
+ return runner.trust === 'team' || runner.ownerUserId === actorUserId;
142
+ }
143
+ /** Whether `runner` may run `run` of `agent`, before any lock is taken and before its payload is known. */
144
+ export function fits(runner, agent, run) {
145
+ return fitsActor(runner, agent, run.actorUserId, toRun(run).requires);
125
146
  }
126
147
  /** The runner features a payload needs. */
127
- export function payloadRequires(dirs, skills, variables) {
148
+ export function payloadRequires(dirs, skills, variables, workspaceKey = false) {
128
149
  return [
150
+ ...(workspaceKey ? ['workspaceKey'] : []),
129
151
  ...(dirs.some((dir) => dir.kind === 'repo') ? ['checkout'] : []),
130
152
  ...(dirs.some((dir) => dir.kind === 'directory')
131
153
  ? ['directories']
@@ -149,11 +171,11 @@ export function variableTargets(agent, assembly) {
149
171
  { scope: 'agent', scopeId: agent.id },
150
172
  ];
151
173
  }
152
- /** Where a run's skills come from, the agent's first so its own win a shared slug. */
153
- export function skillTargets(agent, assembly) {
174
+ /** Team defaults, application scopes and directories, from lowest to highest priority. */
175
+ export function skillTargets(_agent, assembly) {
154
176
  return [
155
- { scope: 'agent', scopeId: agent.id },
156
- ...assembly.scopes,
177
+ GLOBAL_SKILL_TARGET,
178
+ ...assembly.scopes.filter((target) => target.scope !== 'agent' && target.scope !== 'global'),
157
179
  ...assembly.dirs.flatMap((dir) => dir.scopeId ? [{ scope: 'workdir', scopeId: dir.scopeId }] : []),
158
180
  ];
159
181
  }
@@ -172,7 +194,6 @@ function fingerprint(agent, entry, layers, skills) {
172
194
  /** The brief for `assembly`, with the skills a run would get. */
173
195
  export function briefOf(agent, assembly, skills, cli, appName) {
174
196
  const rendered = renderBrief({
175
- dialect: dialectOf(agent),
176
197
  agentName: agent.name,
177
198
  subject: { noun: assembly.subject.noun, key: assembly.subject.key },
178
199
  task: assembly.task,
@@ -192,6 +213,10 @@ export function briefOf(agent, assembly, skills, cli, appName) {
192
213
  : assembly.turn.prompt;
193
214
  return { layers, turn, prompt: joinBrief(layers) };
194
215
  }
216
+ /** Whether `runner` asks for repository credentials on demand rather than taking them with the claim. */
217
+ export function asksCredentialsOnDemand(runner) {
218
+ return runner.features.includes('gitCredentials');
219
+ }
195
220
  const DIRECTORY_KEY_SEPARATOR = '\n';
196
221
  /** The directories a run holds on its runner, as stored in `agRuns.directoryKey`. */
197
222
  function directoryKeyOf(runnerId, dirs) {
@@ -214,8 +239,215 @@ async function directoryBusy(conn, runnerId, runId, key) {
214
239
  .split(DIRECTORY_KEY_SEPARATOR)
215
240
  .some((part) => part !== '' && wanted.has(part)));
216
241
  }
242
+ /** Resolve omitted subject input in the claim transaction, keeping it in the audit history. */
243
+ async function settleClaimInputs(conn, pending, effective, now) {
244
+ const ids = new Set(effective.map((input) => input.id));
245
+ await dropInputs(conn, pending.filter((input) => !ids.has(input.id)).map((input) => input.id), now);
246
+ await markDelivered(conn, pending.filter((input) => ids.has(input.id)), now);
247
+ }
217
248
  export function createClaimService(deps) {
218
249
  const { tx, clock } = deps;
250
+ /** Database reads only; used by the actual claim and ordered-model diagnostics. */
251
+ async function assemblyCompatibility(conn, run, agent, runner, assembly) {
252
+ if (!isProtocolSupported(runner.protocolVersion) ||
253
+ (assembly.workspaceKey && !supportsWorkspaceKey(runner)))
254
+ return 'incompatible';
255
+ const skills = await deps.skills.forRun(conn, skillTargets(agent, assembly), run.id);
256
+ const targets = variableTargets(agent, assembly);
257
+ const names = await deps.variables.namesOf(conn, targets);
258
+ if (!covers(runner.features, [
259
+ ...run.requires,
260
+ ...payloadRequires(payloadDirs(assembly.dirs), skills.length, names.length, Boolean(assembly.workspaceKey)),
261
+ ]))
262
+ return 'incompatible';
263
+ if (!fitsActor(runner, agent, run.actorUserId, run.requires) ||
264
+ !policyAllowsSubject(runner.policy, assembly.subject.key) ||
265
+ assembly.dirs.some((dir) => (dir.kind === 'repo' && !policyAllowsRepo(runner.policy, dir.url)) ||
266
+ (dir.kind === 'directory' && dir.runnerId !== runner.id)))
267
+ return 'restricted';
268
+ if (runner.trust !== 'team' &&
269
+ (await deps.variables.teamOnly(conn, targets)).length > 0)
270
+ return 'restricted';
271
+ return 'ready';
272
+ }
273
+ async function selectionFacts(conn, run, agent, requirePolicy = true, trust) {
274
+ const policy = await deps.selectionPolicies?.forRun(conn, run);
275
+ if ((requirePolicy && !policy) || !deps.runners)
276
+ return null;
277
+ const now = clock.now();
278
+ const start = queueStart(run);
279
+ const registered = (await deps.runners.all(conn)).filter((runner) => !trust ||
280
+ (trust === 'team' ? runner.trust === 'team' : runner.trust !== 'team'));
281
+ const binding = deps.subjects.get(run.subject.kind);
282
+ if (!binding)
283
+ throw new Error(`No context provider for ${run.subject.kind}.`);
284
+ const inputs = (await pendingInputs(conn, run.id)).map(toInput);
285
+ const active = await countActive(conn, 'agentId', agent.id);
286
+ const sameWork = (await runsOfKey(conn, {
287
+ agentId: agent.id,
288
+ subjectKind: run.subject.kind,
289
+ subjectId: run.subject.id,
290
+ threadScope: run.threadScope,
291
+ }, ACTIVE)).length > 0;
292
+ const entries = [];
293
+ for (const entry of agent.modelEntries) {
294
+ const single = { ...agent, modelEntries: [entry] };
295
+ const candidates = registered.filter((runner) => runner.status !== 'revoked');
296
+ const connected = candidates.filter((runner) => (runner.status === 'online' ||
297
+ runner.status === 'upgrade_required') &&
298
+ runner.lastSeenAt !== null &&
299
+ new Date(runner.lastSeenAt).getTime() + TIMINGS.offlineAfterMs >
300
+ now.getTime());
301
+ const installed = connected.filter((runner) => runner.tools.some((tool) => tool.kind === entry.tool));
302
+ const usable = installed.filter((runner) => runsEntry(runner, entry));
303
+ const online = usable.filter((runner) => runner.trust === 'team' || runner.ownerUserId === run.actorUserId);
304
+ const compatible = [];
305
+ const loads = [];
306
+ let unknown = false;
307
+ let incompatible = false;
308
+ let ready = false;
309
+ let directoryBlocked = false;
310
+ for (const runner of online) {
311
+ if (!isProtocolSupported(runner.protocolVersion) ||
312
+ runner.status === 'upgrade_required') {
313
+ incompatible = true;
314
+ loads.push({
315
+ name: runner.name,
316
+ ownerUserId: runner.ownerUserId,
317
+ reason: 'incompatible',
318
+ lastSeenAt: runner.lastSeenAt,
319
+ });
320
+ continue;
321
+ }
322
+ const assembly = await binding.context
323
+ .assemble(conn, {
324
+ run: run,
325
+ agent,
326
+ runner,
327
+ inputs,
328
+ cli: deps.cli.name,
329
+ appName: deps.app.name,
330
+ })
331
+ .catch(() => null);
332
+ if (!assembly) {
333
+ unknown = true;
334
+ loads.push({
335
+ name: runner.name,
336
+ ownerUserId: runner.ownerUserId,
337
+ reason: 'unknown',
338
+ lastSeenAt: runner.lastSeenAt,
339
+ });
340
+ continue;
341
+ }
342
+ const reason = await assemblyCompatibility(conn, run, single, runner, assembly);
343
+ if (reason !== 'ready') {
344
+ incompatible ||= reason === 'incompatible';
345
+ loads.push({
346
+ name: runner.name,
347
+ ownerUserId: runner.ownerUserId,
348
+ reason,
349
+ lastSeenAt: runner.lastSeenAt,
350
+ });
351
+ continue;
352
+ }
353
+ const requiredVariables = await deps.variables.passthroughOf(conn, variableTargets(single, assembly));
354
+ if (requiredVariables.length > 0 && runner.variables == null) {
355
+ unknown = true;
356
+ continue;
357
+ }
358
+ if (requiredVariables.some((name) => !runner.variables?.includes(name)))
359
+ continue;
360
+ compatible.push(runner);
361
+ const byTool = await runsHeldByTool(conn, runner.id);
362
+ const used = await deps.slotsUsed(conn, runner.id);
363
+ const directory = directoryKeyOf(runner.id, payloadDirs(assembly.dirs));
364
+ const blockedDirectory = directory !== null &&
365
+ (await directoryBusy(conn, runner.id, run.id, directory));
366
+ directoryBlocked ||= blockedDirectory;
367
+ const hasRoom = !runner.draining &&
368
+ (runner.globalFree ?? 1) > 0 &&
369
+ used < runner.slots &&
370
+ (byTool[entry.tool] ?? 0) < toolLimit(runner, entry.tool) &&
371
+ (runner.toolLoad?.[entry.tool]?.free ?? 1) > 0 &&
372
+ !blockedDirectory;
373
+ ready ||= hasRoom && runner.globalFree !== undefined;
374
+ if (hasRoom && runner.globalFree === undefined)
375
+ unknown = true;
376
+ loads.push({
377
+ name: runner.name,
378
+ ownerUserId: runner.ownerUserId,
379
+ reason: hasRoom
380
+ ? runner.globalFree === undefined
381
+ ? 'unknown'
382
+ : 'ready'
383
+ : runner.draining
384
+ ? runner.draining
385
+ : blockedDirectory
386
+ ? 'restricted'
387
+ : 'busy',
388
+ lastSeenAt: runner.lastSeenAt,
389
+ used,
390
+ limit: runner.slots,
391
+ toolUsed: byTool[entry.tool] ?? 0,
392
+ toolLimit: toolLimit(runner, entry.tool),
393
+ reportedFree: runner.toolLoad?.[entry.tool]?.free,
394
+ reportedTotalFree: runner.globalFree,
395
+ });
396
+ }
397
+ let reason = compatible.length
398
+ ? ready
399
+ ? 'ready'
400
+ : 'busy'
401
+ : online.length
402
+ ? incompatible
403
+ ? 'incompatible'
404
+ : 'restricted'
405
+ : usable.length
406
+ ? 'noSharedRunner'
407
+ : installed.length
408
+ ? 'toolUnavailable'
409
+ : connected.length
410
+ ? 'toolNotInstalled'
411
+ : 'offline';
412
+ if (directoryBlocked && !ready)
413
+ reason = 'restricted';
414
+ if (!ready &&
415
+ compatible.length > 0 &&
416
+ loads.every((runner) => runner.reason === 'updatePending' || runner.reason === 'pruning'))
417
+ reason = loads.some((runner) => runner.reason === 'updatePending')
418
+ ? 'updatePending'
419
+ : 'pruning';
420
+ if (unknown &&
421
+ !ready &&
422
+ !loads.some((runner) => runner.reason === 'busy'))
423
+ reason = 'unknown';
424
+ if (compatible.length && sameWork)
425
+ reason = 'sameWorkActive';
426
+ else if (compatible.length && active >= agent.maxConcurrentRuns)
427
+ reason = 'concurrencyFull';
428
+ else if (start > now.getTime())
429
+ reason = 'delayed';
430
+ entries.push({
431
+ tool: entry.tool,
432
+ model: entry.model,
433
+ reason,
434
+ compatibleCount: compatible.length,
435
+ blockers: [
436
+ ...(unknown ? ['unknown'] : []),
437
+ ...new Set(loads
438
+ .map((runner) => runner.reason)
439
+ .filter((reason) => reason !== undefined && reason !== 'ready')),
440
+ ],
441
+ runners: loads,
442
+ });
443
+ }
444
+ return {
445
+ queueEnteredAt: new Date(start).toISOString(),
446
+ fallbackAfterSeconds: policy?.fallbackAfterSeconds ?? 0,
447
+ sampledAt: now.toISOString(),
448
+ entries,
449
+ };
450
+ }
219
451
  async function assemble({ conn }, run, agent, runner, inputs, leaseExpiresAt, prepared, entry) {
220
452
  const binding = deps.subjects.get(run.subjectKind);
221
453
  if (!binding)
@@ -228,9 +460,13 @@ export function createClaimService(deps) {
228
460
  inputs,
229
461
  cli: deps.cli.name,
230
462
  appName: deps.app.name,
231
- dialect: 'cli',
232
463
  };
233
- const assembly = await binding.context.assemble(conn, claim);
464
+ const assembly = await binding.context.assemble(conn, claim, prepared.get('subject'));
465
+ // workspace.key is required for coordinating runs; old runners must not silently ignore it.
466
+ if ((await assemblyCompatibility(conn, toRun(run), agent, runner, assembly)) === 'incompatible')
467
+ throw new NotForThisRunner();
468
+ inputs = assembly.inputs ?? inputs;
469
+ const effectiveClaim = { ...claim, inputs };
234
470
  // Its owner's local policy keeps this runner off the subject, or off one of the repositories: another runner takes
235
471
  // the run.
236
472
  if (!policyAllowsSubject(runner.policy, assembly.subject.key) ||
@@ -249,16 +485,25 @@ export function createClaimService(deps) {
249
485
  const names = await deps.variables.namesOf(conn, variables);
250
486
  const requires = cleanList([
251
487
  ...view.requires,
252
- ...payloadRequires(dirs, skills.length, names.length),
488
+ ...payloadRequires(dirs, skills.length, names.length, Boolean(assembly.workspaceKey)),
253
489
  ]);
254
490
  if (!covers(runner.features, requires))
255
491
  throw new NotForThisRunner();
492
+ // A variable for team runners only keeps a personal runner off the run.
493
+ if (names.length > 0 && runner.trust !== 'team') {
494
+ const teamOnly = await deps.variables.teamOnly(conn, variables);
495
+ if (teamOnly.length > 0)
496
+ throw new TeamRunnersOnly(teamOnly);
497
+ }
256
498
  const env = names.length > 0
257
499
  ? await deps.variables.forRun(conn, variables, {
258
500
  runId: run.id,
259
501
  runnerId: runner.id,
260
502
  })
261
503
  : [];
504
+ // Variables taken from the runner: names only. A runner that does not provide one fails the run while preparing
505
+ // it, saying how to provide it; runners are not chosen by them.
506
+ const passthrough = await deps.variables.passthroughOf(conn, variables);
262
507
  const nowText = clock.now().toISOString();
263
508
  const clean = await consumeReset(conn, { kind: run.subjectKind, id: run.subjectId }, run.id, nowText);
264
509
  const session = await sessionsRepo(conn).findOne({
@@ -274,6 +519,7 @@ export function createClaimService(deps) {
274
519
  const git = await repoAccessFor(conn, deps.repoAccess, {
275
520
  claim,
276
521
  repos: dirs.filter((dir) => dir.kind === 'repo'),
522
+ onDemand: asksCredentialsOnDemand(runner),
277
523
  }, prepared);
278
524
  deps.secrets?.remember(runSecretsKey(run.id), [
279
525
  ...env.map((variable) => variable.value),
@@ -284,9 +530,10 @@ export function createClaimService(deps) {
284
530
  filter: { runId: run.id },
285
531
  sort: (sort) => sort.field('seq').desc(),
286
532
  });
287
- const brief = briefOf(agent, await withSections(conn, deps.sections, claim, assembly, prepared), skills, deps.cli.name, deps.app.name);
533
+ const brief = briefOf(agent, await withSections(conn, deps.sections, effectiveClaim, assembly, prepared), skills, deps.cli.name, deps.app.name);
288
534
  const briefFingerprint = fingerprint(agent, entry, brief.layers, skills);
289
- const resume = !clean &&
535
+ const resume = !run.freshSession &&
536
+ !clean &&
290
537
  session &&
291
538
  !session.poisoned &&
292
539
  session.fingerprint === briefFingerprint;
@@ -294,13 +541,23 @@ export function createClaimService(deps) {
294
541
  // hears what changed in them in its turn instead.
295
542
  const { mounts, resumeNotes } = runner.features.includes('mounts')
296
543
  ? await mountsFor(conn, deps.mounts, {
297
- claim,
544
+ claim: effectiveClaim,
298
545
  assembly,
299
546
  session: resume ? 'resume' : 'fresh',
300
547
  sessionKey: sessionKeyOf(view),
301
548
  }, prepared)
302
549
  : { mounts: [], resumeNotes: [] };
303
- const turn = [brief.turn, ...resumeNotes].join('\n\n');
550
+ const sessionNote = resume ? assembly.turn.resumed : assembly.turn.fresh;
551
+ const turn = [
552
+ brief.turn,
553
+ ...(toExecutions(run.executionHistory).find((execution) => execution.attempt === Number(run.attempt) - 1)?.failureReason === 'runnerUpgrade'
554
+ ? [
555
+ 'The previous attempt was interrupted by a runner upgrade. Continue from the current state.',
556
+ ]
557
+ : []),
558
+ ...resumeNotes,
559
+ ...(sessionNote ? [sessionNote] : []),
560
+ ].join('\n\n');
304
561
  await saveBrief(conn, () => deps.ids.next(), {
305
562
  runId: run.id,
306
563
  attempt: Number(run.attempt),
@@ -319,6 +576,7 @@ export function createClaimService(deps) {
319
576
  leaseExpiresAt,
320
577
  requires,
321
578
  firstSeq: (last ? Number(last.seq) : 0) + 1,
579
+ acceptedFailures: Object.keys(ANNOUNCED_FAILURES),
322
580
  },
323
581
  app: deps.app,
324
582
  subject: {
@@ -340,9 +598,13 @@ export function createClaimService(deps) {
340
598
  },
341
599
  inputs,
342
600
  workspace: {
601
+ sessionScope: run.threadScope,
343
602
  dirs,
344
603
  env,
604
+ ...(passthrough.length > 0 ? { passthrough } : {}),
345
605
  ...(clean ? { clean: true } : {}),
606
+ ...(assembly.workspaceKey ? { key: assembly.workspaceKey } : {}),
607
+ ...(assembly.readOnly ? { readOnly: true } : {}),
346
608
  ...(git ? { git } : {}),
347
609
  },
348
610
  skills,
@@ -368,75 +630,444 @@ export function createClaimService(deps) {
368
630
  requires,
369
631
  directoryKey,
370
632
  entry,
633
+ gitOnDemand: git?.onDemand ?? [],
371
634
  };
372
635
  }
373
- async function attempt(runner, agent, candidate, reported) {
374
- // Before the transaction: on SQLite it holds the only connection, so the providers read what else they need now.
375
- const prepared = await prepareExtensions(toRun(candidate), deps.sections, deps.mounts, (error) => deps.onClaimFailure?.(candidate.id, error), deps.repoAccess);
376
- return tx.run(async (unit) => {
377
- const { conn } = unit;
378
- const now = clock.now();
379
- const nowText = now.toISOString();
380
- await lockAgentForClaim(conn, agent.id, nowText);
381
- if ((await countActive(conn, 'agentId', agent.id)) >=
382
- agent.maxConcurrentRuns)
383
- return { kind: 'skipped' };
384
- // Slots are shared with the jobs the runner holds.
385
- if ((await deps.slotsUsed(conn, runner.id)) >= runner.slots)
386
- return { kind: 'skipped' };
387
- // The first of the agent's tools with room, by its limit here and by what the runner said it can take.
388
- const byTool = await runsHeldByTool(conn, runner.id);
389
- const entry = pickOpenEntry(runner, agent, (tool) => (byTool[tool] ?? 0) < toolLimit(runner, tool) && reported.has(tool));
390
- if (!entry)
391
- return { kind: 'skipped' };
392
- const busy = await runsOfKey(conn, candidate, ACTIVE);
393
- if (busy.length > 0)
394
- return { kind: 'skipped' };
395
- const leaseExpiresAt = later(now, TIMINGS.leaseMs);
396
- const taken = await runsRepo(conn).updateMany({
397
- filter: (f) => f.and([
398
- f.string('id').eq(candidate.id),
399
- f.string('status').eq('queued'),
400
- ]),
401
- values: {
402
- status: 'dispatched',
403
- runnerId: runner.id,
404
- leaseExpiresAt,
405
- dispatchedAt: nowText,
406
- lastActivityAt: nowText,
407
- availableAt: null,
408
- updatedAt: nowText,
636
+ /**
637
+ * Notes on a queued run which of its variables kept a personal runner off it, and tells its actor and owner the first
638
+ * time; nothing when that is what it says already.
639
+ */
640
+ async function noteTeamOnly(candidate, variables) {
641
+ const same = (value) => JSON.stringify(variableRefs(value)) === JSON.stringify(variables);
642
+ if (same(candidate.teamOnlyVariables))
643
+ return;
644
+ await tx.run(async (unit) => {
645
+ const run = await findRunRecord(unit.conn, candidate.id);
646
+ if (!run || run.status !== 'queued' || same(run.teamOnlyVariables))
647
+ return;
648
+ await runsRepo(unit.conn).updateMany({
649
+ filter: (f) => f.and([f.string('id').eq(run.id), f.string('status').eq('queued')]),
650
+ values: { teamOnlyVariables: variables.map((each) => ({ ...each })) },
651
+ });
652
+ unit.emit({ type: 'run.changed', runId: run.id, status: 'queued' });
653
+ if (variableRefs(run.teamOnlyVariables).length === 0)
654
+ unit.emit({
655
+ type: 'notice',
656
+ notice: {
657
+ key: `run-secrets:${run.id}`,
658
+ type: 'run_secrets_not_allowed',
659
+ userIds: [
660
+ ...new Set([run.actorUserId, run.ownerUserId].filter((id) => Boolean(id))),
661
+ ],
662
+ subject: { kind: 'run', id: run.id, label: run.id },
663
+ title: 'A run waits for a team runtime',
664
+ body: `Some of its variables are for team runtimes only: ${describeVariables(variables)}.`,
665
+ params: {
666
+ runId: run.id,
667
+ agentId: run.agentId,
668
+ variables: describeVariables(variables),
669
+ },
670
+ },
671
+ });
672
+ });
673
+ }
674
+ /** What reconciling a comment run would change, read without writing: most runs need nothing. */
675
+ async function planCommentConsent(conn, runId) {
676
+ const none = { kind: 'none' };
677
+ const run = await findRunRecord(conn, runId);
678
+ if (!run?.commentRootRunId)
679
+ return none;
680
+ const root = await findRunRecord(conn, run.commentRootRunId);
681
+ if (!root)
682
+ return none;
683
+ if (run.status !== 'queued') {
684
+ const pending = await runRequestsRepo(conn).findMany({
685
+ filter: {
686
+ commentRootRunId: root.id,
687
+ commentRunId: run.id,
688
+ purpose: 'personalRunner',
689
+ status: 'pending',
409
690
  },
691
+ limit: 1,
692
+ });
693
+ return pending.length > 0 ? { kind: 'withdraw', rootId: root.id } : none;
694
+ }
695
+ if (!(await queuedIdentityCurrent(conn, deps, run)))
696
+ return { kind: 'refresh', run };
697
+ if (!(await currentCommentOwner(conn, deps.subjects, root)))
698
+ return {
699
+ kind: 'cancel',
700
+ run,
701
+ detail: 'The comment belongs to an earlier owner assignment.',
702
+ };
703
+ if (root.commentExecution === 'requester' ||
704
+ run.actorUserId !== root.ownerUserId)
705
+ return none;
706
+ const { decision } = await commentConsent(conn, root);
707
+ if (decision === 'confirmed' || decision === 'pending')
708
+ return none;
709
+ if (await commentFromOwner(conn, root))
710
+ return none;
711
+ if (decision === 'rejected')
712
+ return {
713
+ kind: 'cancel',
714
+ run,
715
+ detail: 'The comment personal runner authorization is no longer valid.',
716
+ };
717
+ const agent = await findAgent(conn, run.agentId);
718
+ if (!agent)
719
+ return none;
720
+ const team = await selectionFacts(conn, toRun(run), agent, false, 'team');
721
+ if (!team ||
722
+ team.entries.some((e) => e.compatibleCount > 0 ||
723
+ e.reason === 'unknown' ||
724
+ e.blockers?.includes('unknown')))
725
+ return none;
726
+ const personal = await selectionFacts(conn, toRun(run), agent, false, 'personal');
727
+ if (!personal?.entries.some((e) => e.compatibleCount > 0))
728
+ return none;
729
+ const sourceInputs = await inputsRepo(conn).findMany({
730
+ filter: { runId: root.id },
731
+ sort: (sort) => [sort.field('createdAt').asc(), sort.field('id').asc()],
732
+ });
733
+ const input = sourceInputs.find((input) => input.type === 'comment' &&
734
+ jsonObject(input.payload).commentSourceSnapshot === true) ?? sourceInputs.find((input) => input.type === 'comment');
735
+ return input ? { kind: 'request', run, root, input } : none;
736
+ }
737
+ /**
738
+ * Brings a comment run's consent up to date. It takes no lock of its own: callers that may create a request hold
739
+ * the run's agent row (enqueue, retry and the sweeper do), which serializes the duplicate check in insertRunRequest
740
+ * with every other enqueue and claim of that agent. Everything else is a guarded update.
741
+ */
742
+ async function reconcileCommentConsent(unit, runId) {
743
+ let plan = await planCommentConsent(unit.conn, runId);
744
+ if (plan.kind === 'refresh') {
745
+ if (!(await refreshQueuedIdentity(unit, deps, plan.run)))
746
+ return;
747
+ plan = await planCommentConsent(unit.conn, runId);
748
+ }
749
+ switch (plan.kind) {
750
+ case 'withdraw':
751
+ await withdrawCommentRequests(unit, plan.rootId, clock.now().toISOString(), runId);
752
+ return;
753
+ case 'cancel':
754
+ await finishRun(unit, deps, plan.run, {
755
+ status: 'cancelled',
756
+ reason: null,
757
+ detail: plan.detail,
758
+ });
759
+ return;
760
+ case 'request': {
761
+ const { run, root, input } = plan;
762
+ await insertRunRequest(unit, deps, {
763
+ purpose: 'personalRunner',
764
+ commentRootRunId: root.id,
765
+ commentRunId: run.id,
766
+ commentOwnerKey: root.commentOwnerKey ?? null,
767
+ agentId: run.agentId,
768
+ subject: toRun(run).subject,
769
+ threadScope: run.threadScope,
770
+ // The plan only asks for runs acting as the chain's owner.
771
+ responsibleUserId: run.actorUserId,
772
+ requestedByUserId: root.requestedByUserId ?? root.actorUserId,
773
+ ownerUserId: run.actorUserId,
774
+ priority: Number(run.priority),
775
+ requires: toRun(run).requires,
776
+ fireAt: run.availableAt,
777
+ maxAttempts: Number(run.maxAttempts),
778
+ input: toInput(input),
779
+ });
780
+ return;
781
+ }
782
+ default:
783
+ return;
784
+ }
785
+ }
786
+ /**
787
+ * Whether this runner may take this candidate under comment consent, read outside any transaction. Immutable facts
788
+ * settle most candidates without a query: no comment chain, work run as the commenter, or work not run as the
789
+ * chain's owner. The rest read the chain's requests once. A decision never needs a lock: confirmation is final,
790
+ * and rejection or Run as me end the chain's queued runs, which the claim's guarded `status = 'queued'` update sees.
791
+ */
792
+ async function commentConsentFor(conn, runner, run) {
793
+ const allowed = { allowed: true, confirmedByUserId: null };
794
+ const refused = { allowed: false, confirmedByUserId: null };
795
+ if (!run.commentRootRunId || run.commentExecution === 'requester')
796
+ return allowed;
797
+ const root = await findRunRecord(conn, run.commentRootRunId);
798
+ if (!root || root.commentRootRunId !== root.id)
799
+ return refused;
800
+ if (root.commentExecution === 'requester' ||
801
+ run.actorUserId !== root.ownerUserId)
802
+ return allowed;
803
+ const { decision, confirmation } = await commentConsent(conn, root);
804
+ if (decision === 'rejected')
805
+ return refused;
806
+ if (decision === 'confirmed')
807
+ return {
808
+ allowed: true,
809
+ confirmedByUserId: confirmation?.settledById ?? null,
810
+ };
811
+ if (runner.trust === 'team')
812
+ return allowed;
813
+ return (await commentFromOwner(conn, root)) ? allowed : refused;
814
+ }
815
+ /** Withdraws the request a team claim made obsolete, writing only when one is pending. */
816
+ async function withdrawIfPending(rootId, runId) {
817
+ try {
818
+ const pending = await runRequestsRepo(tx.read()).findMany({
819
+ filter: {
820
+ commentRootRunId: rootId,
821
+ commentRunId: runId,
822
+ purpose: 'personalRunner',
823
+ status: 'pending',
824
+ },
825
+ limit: 1,
826
+ });
827
+ if (pending.length > 0)
828
+ await tx.run((unit) => withdrawCommentRequests(unit, rootId, clock.now().toISOString(), runId));
829
+ }
830
+ catch (error) {
831
+ deps.onClaimFailure?.(runId, error);
832
+ }
833
+ }
834
+ async function admit(unit, run, agent, runner, inputs, prepared) {
835
+ const rejection = await deps.subjects.get(run.subjectKind)?.context.admit?.(unit.conn, {
836
+ run: toRun(run),
837
+ agent,
838
+ runner,
839
+ inputs,
840
+ cli: deps.cli.name,
841
+ appName: deps.app.name,
842
+ }, prepared);
843
+ if (!rejection)
844
+ return true;
845
+ await finishRun(unit, deps, run, {
846
+ status: 'cancelled',
847
+ reason: null,
848
+ detail: `${rejection.reason}${rejection.detail ? `: ${rejection.detail}` : ''}`,
849
+ });
850
+ return false;
851
+ }
852
+ async function attempt(runner, agent, candidate, reported) {
853
+ const conn = tx.read();
854
+ // Rules that do not depend on who the run acts as come first, and settle the rest of the pass: an agent at its
855
+ // limit is checked once per poll, not once per queued run.
856
+ if ((await deps.slotsUsed(conn, runner.id)) >= runner.slots)
857
+ return { kind: 'full', scope: 'runner' };
858
+ if ((await countActive(conn, 'agentId', agent.id)) >= agent.maxConcurrentRuns)
859
+ return { kind: 'full', scope: 'agent' };
860
+ // Repair pre-upgrade queued work before even checking personal runner ownership. Reading first keeps a poll over
861
+ // current work free of transactions and row locks; only work whose identity changed is written.
862
+ if (deps.subjects.get(candidate.subjectKind)?.actsAs &&
863
+ !(await queuedIdentityCurrent(conn, deps, candidate))) {
864
+ const current = await tx.run(async (unit) => {
865
+ await lockAgentForClaim(unit.conn, agent.id, clock.now().toISOString());
866
+ const run = await findRunRecord(unit.conn, candidate.id);
867
+ return run?.status === 'queued'
868
+ ? refreshQueuedIdentity(unit, deps, run)
869
+ : null;
410
870
  });
411
- if (taken.updatedCount !== 1)
871
+ if (!current)
412
872
  return { kind: 'skipped' };
413
- const run = (await findRunRecord(conn, candidate.id));
414
- const pending = await pendingInputs(conn, run.id);
415
- let assembled;
873
+ candidate = current;
874
+ }
875
+ // Avoid assembling or asking authorization when a cheap claim rule already rules this work out.
876
+ if (!fits(runner, agent, candidate) ||
877
+ (await runsOfKey(conn, candidate, ACTIVE)).length > 0)
878
+ return { kind: 'skipped' };
879
+ const consent = await commentConsentFor(conn, runner, candidate);
880
+ if (!consent.allowed)
881
+ return { kind: 'skipped' };
882
+ // Before the transaction: on SQLite it holds the only connection, so the providers read what else they need now.
883
+ const prepared = new Map(await prepareExtensions(toRun(candidate), deps.sections, deps.mounts, (error) => deps.onClaimFailure?.(candidate.id, error), deps.repoAccess, { onDemand: asksCredentialsOnDemand(runner) }));
884
+ const subjectContext = deps.subjects.get(candidate.subjectKind)?.context;
885
+ if (subjectContext?.prepare) {
416
886
  try {
417
- assembled = await assemble(unit, run, agent, runner, pending.map(toInput), leaseExpiresAt, prepared, entry);
887
+ prepared.set('subject', await subjectContext.prepare(toRun(candidate)));
418
888
  }
419
889
  catch (error) {
420
- if (error instanceof NotForThisRunner)
421
- throw error;
422
- throw new AssemblyError(error instanceof Error ? error.message : String(error), error);
890
+ deps.onClaimFailure?.(candidate.id, error);
423
891
  }
424
- await markDelivered(conn, pending, nowText);
425
- await runsRepo(conn).updateMany({
426
- filter: { id: run.id },
427
- values: {
428
- payloadFingerprint: assembled.fingerprint,
429
- requires: [...assembled.requires],
430
- directoryKey: assembled.directoryKey,
431
- tool: assembled.entry.tool,
432
- modelService: null,
433
- model: assembled.entry.model,
434
- effort: assembled.entry.effort ?? null,
435
- },
892
+ }
893
+ let taken;
894
+ try {
895
+ taken = await tx.run(async (unit) => {
896
+ const { conn } = unit;
897
+ const now = clock.now();
898
+ const nowText = now.toISOString();
899
+ await lockAgentForClaim(conn, agent.id, nowText);
900
+ const queuedRun = await findRunRecord(conn, candidate.id);
901
+ if (!queuedRun || queuedRun.status !== 'queued')
902
+ return null;
903
+ const identity = await refreshQueuedIdentity(unit, deps, queuedRun);
904
+ // Providers were prepared for the candidate's identity outside this transaction.
905
+ if (!identity || identity.actorUserId !== candidate.actorUserId)
906
+ return null;
907
+ if ((await countActive(conn, 'agentId', agent.id)) >=
908
+ agent.maxConcurrentRuns)
909
+ return null;
910
+ // Slots are shared with the jobs the runner holds.
911
+ if ((await deps.slotsUsed(conn, runner.id)) >= runner.slots)
912
+ return null;
913
+ // The first of the agent's tools with room, by its limit here and by what the runner said it can take.
914
+ const byTool = await runsHeldByTool(conn, runner.id);
915
+ const current = await findRunRecord(conn, candidate.id);
916
+ if (!current ||
917
+ current.status !== 'queued' ||
918
+ (current.availableAt &&
919
+ new Date(current.availableAt).getTime() > now.getTime()))
920
+ return null;
921
+ const entry = pickOpenEntry(runner, agent, (tool) => (byTool[tool] ?? 0) < toolLimit(runner, tool) && reported.has(tool));
922
+ if (!entry)
923
+ return null;
924
+ const busy = await runsOfKey(conn, candidate, ACTIVE);
925
+ if (busy.length > 0)
926
+ return null;
927
+ const leaseExpiresAt = later(now, TIMINGS.leaseMs);
928
+ const claimed = await runsRepo(conn).updateMany({
929
+ filter: (f) => f.and([
930
+ f.string('id').eq(candidate.id),
931
+ f.string('status').eq('queued'),
932
+ f.string('actorUserId').eq(candidate.actorUserId),
933
+ ]),
934
+ values: {
935
+ status: 'dispatched',
936
+ failureReason: null,
937
+ failureDetail: null,
938
+ runnerId: runner.id,
939
+ // Brief providers observe this reservation inside the claim transaction.
940
+ // Replace a previous attempt's tool before computing the roster.
941
+ tool: entry.tool,
942
+ leaseExpiresAt,
943
+ dispatchedAt: nowText,
944
+ lastActivityAt: nowText,
945
+ availableAt: null,
946
+ updatedAt: nowText,
947
+ ...(consent.confirmedByUserId
948
+ ? { confirmedByUserId: consent.confirmedByUserId }
949
+ : {}),
950
+ },
951
+ });
952
+ if (claimed.updatedCount !== 1)
953
+ return null;
954
+ const run = (await findRunRecord(conn, candidate.id));
955
+ const pending = (await pendingInputs(conn, run.id)).filter((input) => !run.commentRootRunId ||
956
+ input.type !== 'comment' ||
957
+ jsonObject(input.payload).commentRootRunId === run.commentRootRunId);
958
+ let assembled;
959
+ let selectionSnapshot;
960
+ try {
961
+ if (!(await admit(unit, run, agent, runner, pending.map(toInput), prepared.get('subject'))))
962
+ return null;
963
+ // Admission must precede preflight assembly: a missing or obsolete
964
+ // subject is cancelled normally, rather than counted as setup failure.
965
+ const selection = await selectionFacts(conn, toRun(current), agent);
966
+ const elapsed = Math.max(0, (now.getTime() - queueStart(toRun(current))) / 1000);
967
+ const preferredAvailable = (selection?.entries[0]?.compatibleCount ?? 0) > 0 ||
968
+ selection?.entries[0]?.reason === 'unknown' ||
969
+ selection?.entries[0]?.blockers?.includes('unknown') === true;
970
+ const chosenIndex = agent.modelEntries.indexOf(entry);
971
+ if (selection && chosenIndex > 0) {
972
+ // Throw to roll back the provisional dispatch; the original run keeps
973
+ // waiting for its first choice without consuming an attempt or token.
974
+ if (preferredAvailable && elapsed < selection.fallbackAfterSeconds)
975
+ throw new NotForThisRunner();
976
+ selectionSnapshot = {
977
+ reason: preferredAvailable
978
+ ? 'preferredQueued'
979
+ : 'preferredUnavailable',
980
+ preferredTool: agent.modelEntries[0].tool,
981
+ preferredModel: agent.modelEntries[0].model,
982
+ queueEnteredAt: selection.queueEnteredAt,
983
+ waitedSeconds: elapsed,
984
+ };
985
+ }
986
+ assembled = await assemble(unit, run, agent, runner, pending.map(toInput), leaseExpiresAt, prepared, entry);
987
+ }
988
+ catch (error) {
989
+ if (error instanceof NotForThisRunner)
990
+ throw error;
991
+ throw new AssemblyError(error instanceof Error ? error.message : String(error), error);
992
+ }
993
+ await settleClaimInputs(conn, pending, assembled.payload.inputs, nowText);
994
+ await runsRepo(conn).updateMany({
995
+ filter: { id: run.id },
996
+ values: {
997
+ payloadFingerprint: assembled.fingerprint,
998
+ requires: [...assembled.requires],
999
+ directoryKey: assembled.directoryKey,
1000
+ tool: assembled.entry.tool,
1001
+ executionHistory: executionHistory(run, {
1002
+ ...(selectionSnapshot ? { selection: selectionSnapshot } : {}),
1003
+ attempt: Number(run.attempt),
1004
+ runnerId: runner.id,
1005
+ runnerName: runner.name,
1006
+ runnerOwnerUserId: runner.ownerUserId,
1007
+ runnerOwnerName: runner.ownerUserId
1008
+ ? ((await deps.people?.names(conn, [runner.ownerUserId]))?.get(runner.ownerUserId) ?? runner.ownerName)
1009
+ : null,
1010
+ runnerTrust: runner.trust,
1011
+ tool: assembled.entry.tool,
1012
+ toolVersion: runner.tools.find((tool) => tool.kind === assembled.entry.tool)
1013
+ ?.version ?? null,
1014
+ modelService: null,
1015
+ model: assembled.entry.model,
1016
+ actualModels: [],
1017
+ effort: assembled.entry.effort ?? null,
1018
+ dispatchedAt: nowText,
1019
+ finishedAt: null,
1020
+ failureReason: null,
1021
+ }),
1022
+ modelService: null,
1023
+ model: assembled.entry.model,
1024
+ effort: assembled.entry.effort ?? null,
1025
+ teamOnlyVariables: null,
1026
+ gitCredentialUrls: assembled.gitOnDemand.length > 0
1027
+ ? [...assembled.gitOnDemand]
1028
+ : null,
1029
+ },
1030
+ });
1031
+ unit.emit({ type: 'run.changed', runId: run.id, status: 'dispatched' });
1032
+ if (variableRefs(candidate.teamOnlyVariables).length > 0)
1033
+ unit.emit({
1034
+ type: 'notice.cleared',
1035
+ notice: {
1036
+ type: 'run_secrets_not_allowed',
1037
+ subject: { kind: 'run', id: run.id, label: run.id },
1038
+ },
1039
+ });
1040
+ return assembled.payload;
436
1041
  });
437
- unit.emit({ type: 'run.changed', runId: run.id, status: 'dispatched' });
438
- return { kind: 'claimed', payload: assembled.payload };
439
- });
1042
+ }
1043
+ catch (error) {
1044
+ await discardRepoAccess(candidate, prepared);
1045
+ if (error instanceof TeamRunnersOnly) {
1046
+ await noteTeamOnly(candidate, error.variables);
1047
+ return { kind: 'skipped' };
1048
+ }
1049
+ throw error;
1050
+ }
1051
+ if (!taken) {
1052
+ await discardRepoAccess(candidate, prepared);
1053
+ return { kind: 'skipped' };
1054
+ }
1055
+ // After the claim commits, never inside it: decisions settle the request before touching runs, so a claim that
1056
+ // held its run while waiting for the request could deadlock with them. A request left pending by a crash here is
1057
+ // withdrawn by the next reconciliation, and confirming it finds the run no longer waiting.
1058
+ if (candidate.commentRootRunId && runner.trust === 'team')
1059
+ await withdrawIfPending(candidate.commentRootRunId, candidate.id);
1060
+ return { kind: 'claimed', payload: taken };
1061
+ }
1062
+ async function discardRepoAccess(run, prepared) {
1063
+ for (const provider of deps.repoAccess?.list() ?? []) {
1064
+ try {
1065
+ await provider.discard?.(toRun(run), prepared.get(`repo:${provider.key}`));
1066
+ }
1067
+ catch (error) {
1068
+ deps.onClaimFailure?.(run.id, error);
1069
+ }
1070
+ }
440
1071
  }
441
1072
  /** Counts a failed assembly; the run fails once it has failed too often. */
442
1073
  async function recordFailure(runId, error) {
@@ -458,20 +1089,18 @@ export function createClaimService(deps) {
458
1089
  filter: { id: runId },
459
1090
  values: {
460
1091
  claimFailures: failures,
1092
+ failureReason: 'setupFailed',
461
1093
  failureDetail: error.message.slice(0, 10_000),
462
1094
  },
463
1095
  });
464
1096
  });
465
1097
  }
466
- /** Queued runs of agents of `type` that may be claimed now, in claim order. */
467
- async function candidatesOf(type) {
1098
+ /** Queued runs that may be claimed now, in claim order. */
1099
+ async function candidates() {
468
1100
  const now = clock.now();
469
1101
  return runsRepo(tx.read()).findMany({
470
1102
  filter: (f) => f.and([
471
1103
  f.string('status').eq('queued'),
472
- f.string('agentType').eq(type),
473
- // A consultation is taken by the holder of the run that asked it, never from the queue.
474
- f.string('parentRunId').empty(),
475
1104
  f.or([
476
1105
  f.date('availableAt').empty(),
477
1106
  f.date('availableAt').notAfter(now),
@@ -485,151 +1114,23 @@ export function createClaimService(deps) {
485
1114
  limit: CANDIDATES,
486
1115
  });
487
1116
  }
488
- /** Takes an online run for `holder` in one transaction, or skips it when another holder or run got there first. */
489
- async function attemptServer(holder, agent, candidate) {
490
- const prepared = await prepareExtensions(toRun(candidate), deps.sections, undefined, (error) => deps.onClaimFailure?.(candidate.id, error));
491
- return tx.run(async (unit) => {
492
- const { conn } = unit;
493
- const now = clock.now();
494
- const nowText = now.toISOString();
495
- // Serializes claims and enqueues of this agent, so one key never has two runs held.
496
- await lockAgentForClaim(conn, agent.id, nowText);
497
- if ((await runsOfKey(conn, candidate, ACTIVE)).length > 0)
498
- return null;
499
- const leaseExpiresAt = later(now, TIMINGS.leaseMs);
500
- const taken = await runsRepo(conn).updateMany({
501
- filter: (f) => f.and([
502
- f.string('id').eq(candidate.id),
503
- f.string('status').eq('queued'),
504
- ]),
505
- values: {
506
- status: 'dispatched',
507
- runnerId: holder.id,
508
- leaseExpiresAt,
509
- dispatchedAt: nowText,
510
- lastActivityAt: nowText,
511
- availableAt: null,
512
- updatedAt: nowText,
513
- },
514
- });
515
- if (taken.updatedCount !== 1)
516
- return null;
517
- const run = (await findRunRecord(conn, candidate.id));
518
- const pending = await pendingInputs(conn, run.id);
519
- const inputs = pending.map(toInput);
520
- let claimed;
521
- try {
522
- claimed = await assembleServer(unit, run, agent, holder, inputs, leaseExpiresAt, prepared);
523
- }
524
- catch (error) {
525
- throw new AssemblyError(error instanceof Error ? error.message : String(error), error);
526
- }
527
- await markDelivered(conn, pending, nowText);
528
- await runsRepo(conn).updateMany({
529
- filter: { id: run.id },
530
- values: {
531
- tool: null,
532
- modelService: claimed.model?.modelService ?? null,
533
- model: claimed.model?.model ?? null,
534
- effort: claimed.model?.effort ?? null,
535
- },
536
- });
537
- unit.emit({ type: 'run.changed', runId: run.id, status: 'dispatched' });
538
- return {
539
- ...claimed,
540
- run: {
541
- ...claimed.run,
542
- tool: null,
543
- modelService: claimed.model?.modelService ?? null,
544
- model: claimed.model?.model ?? null,
545
- effort: claimed.model?.effort ?? null,
546
- },
547
- };
548
- });
549
- }
550
- async function assembleServer({ conn }, run, agent, holder, inputs, leaseExpiresAt, prepared) {
551
- const binding = deps.subjects.get(run.subjectKind);
552
- if (!binding)
553
- throw new Error(`No context provider is registered for subjects of kind "${run.subjectKind}".`);
554
- const view = toRun(run);
555
- const claim = {
556
- run: view,
557
- agent,
558
- runner: null,
559
- inputs,
560
- cli: deps.cli.name,
561
- appName: deps.app.name,
562
- dialect: 'tools',
563
- };
564
- const assembly = await binding.context.assemble(conn, claim);
565
- if (assembly.dirs.length > 0)
566
- throw new Error('An online agent has no working directory, and this work needs one.');
567
- const skills = await deps.skills.forRun(conn, skillTargets(agent, assembly), run.id);
568
- const own = new Set(skills.map((skill) => skill.slug));
569
- const rendered = briefOf(agent, await withSections(conn, deps.sections, claim, assembly, prepared), [], deps.cli.name, deps.app.name);
570
- // The skills an online run reads through its tools, and the agents it may consult, whatever system layer its
571
- // subject gives it.
572
- const consultable = (await deps.consultations?.targets(conn, run, agent)) ?? [];
573
- const depth = run.parentRunId
574
- ? ((await deps.consultations?.chain(conn, run))?.length ?? 1) - 1
575
- : 0;
576
- const section = [
577
- ...onlineSkillsSection([
578
- ...skills,
579
- ...(deps.onlineSkills?.() ?? []).filter((skill) => !own.has(skill.slug)),
580
- ]),
581
- ...(consultable.length > 0 ? ['', ...consultSection(consultable)] : []),
582
- ];
583
- const layers = section.length > 0
584
- ? {
585
- ...rendered.layers,
586
- system: [rendered.layers.system, '', ...section].join('\n'),
587
- }
588
- : rendered.layers;
589
- const brief = { ...rendered, layers, prompt: joinBrief(layers) };
590
- const token = await mintRunToken(conn, deps, run, holder.id);
591
- deps.secrets?.remember(runSecretsKey(run.id), [token.value]);
592
- const last = await eventsRepo(conn).findOne({
593
- filter: { runId: run.id },
594
- sort: (sort) => sort.field('seq').desc(),
595
- });
596
- await saveBrief(conn, () => deps.ids.next(), {
597
- runId: run.id,
598
- attempt: Number(run.attempt),
599
- layers: brief.layers,
600
- turn: brief.turn,
601
- prompt: brief.prompt,
602
- createdAt: clock.now().toISOString(),
603
- });
604
- return {
605
- runId: run.id,
606
- attempt: Number(run.attempt),
607
- agent,
608
- run: view,
609
- model: onlineEntryOf(agent, assembly.model, (await deps.defaultModel?.(conn)) ?? null),
610
- system: brief.prompt,
611
- turn: brief.turn,
612
- inputs,
613
- token: token.value,
614
- skills,
615
- leaseExpiresAt,
616
- firstSeq: (last ? Number(last.seq) : 0) + 1,
617
- consultable,
618
- depth,
619
- };
620
- }
621
1117
  async function claimOne(runner, seen, reported) {
1118
+ if (runner.draining)
1119
+ return null;
622
1120
  const conn = tx.read();
623
- const candidates = await candidatesOf('runner');
1121
+ const queued = await candidates();
624
1122
  const agents = new Map();
625
- for (const candidate of candidates) {
626
- if (seen.has(candidate.id))
1123
+ const full = new Set();
1124
+ for (const candidate of queued) {
1125
+ if (seen.has(candidate.id) || full.has(candidate.agentId))
627
1126
  continue;
628
1127
  seen.add(candidate.id);
629
1128
  if (!agents.has(candidate.agentId))
630
1129
  agents.set(candidate.agentId, await findAgent(conn, candidate.agentId));
631
1130
  const agent = agents.get(candidate.agentId);
632
- if (!agent || !fits(runner, agent, candidate))
1131
+ if (!agent ||
1132
+ (!deps.subjects.get(candidate.subjectKind)?.actsAs &&
1133
+ !fits(runner, agent, candidate)))
633
1134
  continue;
634
1135
  // None of its tools has room on the runner's side: the runs behind it may still fit.
635
1136
  if (!pickOpenEntry(runner, agent, (tool) => reported.has(tool)))
@@ -638,6 +1139,13 @@ export function createClaimService(deps) {
638
1139
  const result = await attempt(runner, agent, candidate, reported);
639
1140
  if (result.kind === 'claimed')
640
1141
  return result.payload;
1142
+ if (result.kind === 'full') {
1143
+ if (result.scope === 'runner')
1144
+ return null;
1145
+ // Seen candidates are passed over for good in this claim; unvisited ones stay eligible for the next pass.
1146
+ seen.delete(candidate.id);
1147
+ full.add(candidate.agentId);
1148
+ }
641
1149
  }
642
1150
  catch (error) {
643
1151
  // Rolled back: the run stays queued for a runner it fits.
@@ -651,8 +1159,51 @@ export function createClaimService(deps) {
651
1159
  return null;
652
1160
  }
653
1161
  return {
1162
+ reconcileCommentConsent,
1163
+ async reconcileCommentQueue() {
1164
+ const conn = tx.read();
1165
+ const queued = await runsRepo(conn).findMany({
1166
+ filter: (f) => f.and([
1167
+ f.string('status').eq('queued'),
1168
+ f.string('commentRootRunId').notEmpty(),
1169
+ ]),
1170
+ sort: (s) => [s.field('createdAt').asc()],
1171
+ limit: 100,
1172
+ });
1173
+ const pending = await runRequestsRepo(conn).findMany({
1174
+ filter: { purpose: 'personalRunner', status: 'pending' },
1175
+ limit: 100,
1176
+ });
1177
+ for (const id of new Set([
1178
+ ...queued.map((run) => run.id),
1179
+ ...pending
1180
+ .map((request) => request.commentRunId)
1181
+ .filter((id) => Boolean(id)),
1182
+ ])) {
1183
+ // Read first: a sweep over settled chains opens no transaction at all.
1184
+ const plan = await planCommentConsent(conn, id);
1185
+ if (plan.kind === 'none')
1186
+ continue;
1187
+ await tx.run(async (unit) => {
1188
+ if (plan.kind === 'request' || plan.kind === 'refresh')
1189
+ await lockAgentForClaim(unit.conn, plan.run.agentId, clock.now().toISOString());
1190
+ await reconcileCommentConsent(unit, id);
1191
+ });
1192
+ }
1193
+ },
1194
+ diagnose: (conn, run, agent) => selectionFacts(conn, run, agent, false),
1195
+ async selectionWait(runId) {
1196
+ const conn = tx.read();
1197
+ const run = await findRunRecord(conn, runId);
1198
+ if (!run || run.status !== 'queued')
1199
+ return null;
1200
+ const agent = await findAgent(conn, run.agentId);
1201
+ return agent ? selectionFacts(conn, toRun(run), agent, false) : null;
1202
+ },
654
1203
  async claim(runner, free, tools) {
655
- if (runner.status !== 'online')
1204
+ if (runner.status !== 'online' || runnerLowOnDisk(runner))
1205
+ return [];
1206
+ if ((await deps.slotsUsed(tx.read(), runner.id)) >= runner.slots)
656
1207
  return [];
657
1208
  const payloads = [];
658
1209
  const seen = new Set();
@@ -667,64 +1218,6 @@ export function createClaimService(deps) {
667
1218
  }
668
1219
  return payloads;
669
1220
  },
670
- async claimServer(holder, limit) {
671
- const claimed = [];
672
- const conn = tx.read();
673
- const agents = new Map();
674
- for (const candidate of await candidatesOf('online')) {
675
- if (claimed.length >= limit)
676
- break;
677
- if (!agents.has(candidate.agentId))
678
- agents.set(candidate.agentId, await findAgent(conn, candidate.agentId));
679
- const agent = agents.get(candidate.agentId);
680
- if (!agent || agent.archivedAt || agent.type !== 'online')
681
- continue;
682
- try {
683
- const taken = await attemptServer(holder, agent, candidate);
684
- if (taken)
685
- claimed.push(taken);
686
- }
687
- catch (error) {
688
- if (!(error instanceof AssemblyError))
689
- throw error;
690
- await recordFailure(candidate.id, error);
691
- }
692
- }
693
- return claimed;
694
- },
695
- async claimChild(holder, runId) {
696
- const conn = tx.read();
697
- const candidate = await findRunRecord(conn, runId);
698
- if (!candidate?.parentRunId || candidate.status !== 'queued')
699
- throw notFound('Run');
700
- const parent = await findRunRecord(conn, candidate.parentRunId);
701
- if (parent?.runnerId !== holder.id)
702
- throw notFound('Run');
703
- const agent = await findAgent(conn, candidate.agentId);
704
- if (!agent || agent.archivedAt || agent.type !== 'online')
705
- throw notFound('Agent');
706
- try {
707
- const taken = await attemptServer(holder, agent, candidate);
708
- if (!taken)
709
- throw notFound('Run');
710
- return taken;
711
- }
712
- catch (error) {
713
- if (!(error instanceof AssemblyError))
714
- throw error;
715
- // One attempt: a consultation that cannot be assembled fails at once.
716
- await tx.run(async (unit) => {
717
- const run = await findRunRecord(unit.conn, runId);
718
- if (run)
719
- await finishRun(unit, deps, run, {
720
- status: 'failed',
721
- reason: 'setupFailed',
722
- detail: error.message,
723
- });
724
- });
725
- throw error;
726
- }
727
- },
728
1221
  async runSkills(runner, runId) {
729
1222
  const conn = tx.read();
730
1223
  const run = await findRunRecord(conn, runId);
@@ -741,8 +1234,7 @@ export function createClaimService(deps) {
741
1234
  inputs: [],
742
1235
  cli: deps.cli.name,
743
1236
  appName: deps.app.name,
744
- dialect: dialectOf(agent),
745
- });
1237
+ }, await binding.context.prepare?.(toRun(run)));
746
1238
  return deps.skills.forRun(conn, skillTargets(agent, assembly), run.id);
747
1239
  },
748
1240
  };