agentworkshop 0.7.24 → 0.7.27

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 (243) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/2jaJHWIJ.js +1 -0
  3. package/.output/public/_nuxt/B7gZokg82.js +209 -0
  4. package/.output/public/_nuxt/{Dfe8vXdG.js → B8kt898d.js} +1 -1
  5. package/.output/public/_nuxt/{BvNJUu3s.js → BFGpEo8S.js} +1 -1
  6. package/.output/public/_nuxt/{BAfp6tmu.js → BG4zduPf.js} +5 -5
  7. package/.output/public/_nuxt/{BMb80_tm.js → BGhOrgle.js} +1 -1
  8. package/.output/public/_nuxt/{Bc-_IsBP.js → BHcpzI2n.js} +1 -1
  9. package/.output/public/_nuxt/{Djir5rSD.js → BTLEvDxM.js} +1 -1
  10. package/.output/public/_nuxt/{b50gYVZl.js → BWyR3awA.js} +1 -1
  11. package/.output/public/_nuxt/Bc89zKq2.js +1 -0
  12. package/.output/public/_nuxt/{CxY6LfeN.js → BetmGSO4.js} +1 -1
  13. package/.output/public/_nuxt/{DAR70Lf52.js → BgCrZKv_2.js} +1 -1
  14. package/.output/public/_nuxt/{DUToq-k_.js → BowTbasd.js} +1 -1
  15. package/.output/public/_nuxt/{D6HDb-vN2.js → BvtknqSC2.js} +1 -1
  16. package/.output/public/_nuxt/{CLZWFhZC.js → CTfqWGdy.js} +1 -1
  17. package/.output/public/_nuxt/Chc5pfAA.js +1 -0
  18. package/.output/public/_nuxt/CobSUzQv.js +2 -0
  19. package/.output/public/_nuxt/{kZV5TQvh2.js → CucID8qg2.js} +1 -1
  20. package/.output/public/_nuxt/{CAGL7TNK.js → DHbTyoAV.js} +1 -1
  21. package/.output/public/_nuxt/{DSa2O56y.js → DfLUN8kY.js} +1 -1
  22. package/.output/public/_nuxt/{Coik560n.js → DjU9Dx5Y.js} +1 -1
  23. package/.output/public/_nuxt/DnziMlcX.js +1 -0
  24. package/.output/public/_nuxt/{Ct68Z0232.js → DosOJEmZ2.js} +1 -1
  25. package/.output/public/_nuxt/{CDLv-n51.js → Q_SJDSgx.js} +1 -1
  26. package/.output/public/_nuxt/{TownView.zu2rU5nS.css → TownView.CtskuMXS.css} +1 -1
  27. package/.output/public/_nuxt/{CuVGPuP1.js → XY2z78rj.js} +1 -1
  28. package/.output/public/_nuxt/_id_.DdbYeLqn.css +1 -0
  29. package/.output/public/_nuxt/{C1CSiSBN.js → bFnZa2yd.js} +1 -1
  30. package/.output/public/_nuxt/builds/latest.json +1 -1
  31. package/.output/public/_nuxt/builds/meta/f7a8e33d-5422-45ac-895d-27168f97e3ad.json +1 -0
  32. package/.output/public/_nuxt/daq.DGV9QS8d.css +1 -0
  33. package/.output/public/_nuxt/{entry.AZM1Culo.css → entry.BN02X8iR.css} +1 -1
  34. package/.output/public/_nuxt/{X6IraCTB.js → rkVgzg62.js} +1 -1
  35. package/.output/public/_nuxt/settings.W6qrYAui.css +1 -0
  36. package/.output/public/_nuxt/teams.BBXVkyKq.css +1 -0
  37. package/.output/public/assets/game/character/yangyang-walk.glb +0 -0
  38. package/.output/server/chunks/_/host-bindings.mjs +1 -1
  39. package/.output/server/chunks/_/nitro.mjs +1013 -844
  40. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  41. package/.output/server/chunks/build/{TownView-DxeQe1Bl.mjs → TownView-ftIylqiD.mjs} +178 -151
  42. package/.output/server/chunks/build/{TownView-DxeQe1Bl.mjs.map → TownView-ftIylqiD.mjs.map} +1 -1
  43. package/.output/server/chunks/build/{_id_-DQDqj-cZ.mjs → _id_-3Axz2-gV.mjs} +35 -35
  44. package/.output/server/chunks/build/{_id_-DQDqj-cZ.mjs.map → _id_-3Axz2-gV.mjs.map} +1 -1
  45. package/.output/server/chunks/build/{_id_-DY6v6sqB.mjs → _id_-CahfkmEt.mjs} +5 -5
  46. package/.output/server/chunks/build/{_id_-DY6v6sqB.mjs.map → _id_-CahfkmEt.mjs.map} +1 -1
  47. package/.output/server/chunks/build/{_wsId_-D38Vx6Ky.mjs → _wsId_-BymlQhzS.mjs} +7 -7
  48. package/.output/server/chunks/build/{_wsId_-D38Vx6Ky.mjs.map → _wsId_-BymlQhzS.mjs.map} +1 -1
  49. package/.output/server/chunks/build/{agents-GnmUGZir.mjs → agents-DuMiSWKp.mjs} +4 -4
  50. package/.output/server/chunks/build/{agents-GnmUGZir.mjs.map → agents-DuMiSWKp.mjs.map} +1 -1
  51. package/.output/server/chunks/build/{channel-templates-B4z6v8Z5.mjs → channel-templates-BStqJ-of.mjs} +2 -2
  52. package/.output/server/chunks/build/{channel-templates-B4z6v8Z5.mjs.map → channel-templates-BStqJ-of.mjs.map} +1 -1
  53. package/.output/server/chunks/build/{checkbox-dzcpU_Cd.mjs → checkbox-C1gABOca.mjs} +4 -4
  54. package/.output/server/chunks/build/{checkbox-dzcpU_Cd.mjs.map → checkbox-C1gABOca.mjs.map} +1 -1
  55. package/.output/server/chunks/build/{daq-B-3xCp_2.mjs → daq-ChYCABz1.mjs} +312 -426
  56. package/.output/server/chunks/build/daq-ChYCABz1.mjs.map +1 -0
  57. package/.output/server/chunks/build/{dcw-BL2dhuz7.mjs → dcw-CcQNGggh.mjs} +3 -3
  58. package/.output/server/chunks/build/{dcw-BL2dhuz7.mjs.map → dcw-CcQNGggh.mjs.map} +1 -1
  59. package/.output/server/chunks/build/{default-CLf5m0NE.mjs → default-95-zOTPt.mjs} +2 -2
  60. package/.output/server/chunks/build/{default-CLf5m0NE.mjs.map → default-95-zOTPt.mjs.map} +1 -1
  61. package/.output/server/chunks/build/{logs-Dto_umFY.mjs → logs-D-2UyGtE.mjs} +3 -3
  62. package/.output/server/chunks/build/{logs-Dto_umFY.mjs.map → logs-D-2UyGtE.mjs.map} +1 -1
  63. package/.output/server/chunks/build/{monitor-Cp4V-H2n.mjs → monitor-mfG5jBtz.mjs} +3 -3
  64. package/.output/server/chunks/build/{monitor-Cp4V-H2n.mjs.map → monitor-mfG5jBtz.mjs.map} +1 -1
  65. package/.output/server/chunks/build/{pages-Dc2H1QFu.mjs → pages-Dfosfchr.mjs} +4 -4
  66. package/.output/server/chunks/build/{pages-Dc2H1QFu.mjs.map → pages-Dfosfchr.mjs.map} +1 -1
  67. package/.output/server/chunks/build/{permissions-BTRW2CVd.mjs → permissions-B-hhZuzm.mjs} +3 -3
  68. package/.output/server/chunks/build/{permissions-BTRW2CVd.mjs.map → permissions-B-hhZuzm.mjs.map} +1 -1
  69. package/.output/server/chunks/build/{settings-UfGyDgM7.mjs → settings-BajN9TQW.mjs} +249 -20
  70. package/.output/server/chunks/build/settings-BajN9TQW.mjs.map +1 -0
  71. package/.output/server/chunks/build/{table-Cswq-l2r.mjs → table-Bf1r2JjE.mjs} +2 -2
  72. package/.output/server/chunks/build/{table-Cswq-l2r.mjs.map → table-Bf1r2JjE.mjs.map} +1 -1
  73. package/.output/server/chunks/build/{teams-Cbg27MWW.mjs → teams-t2SkRlJP.mjs} +346 -46
  74. package/.output/server/chunks/build/teams-t2SkRlJP.mjs.map +1 -0
  75. package/.output/server/chunks/build/{tokens-Bmm6rDfj.mjs → tokens-DiCojbN8.mjs} +3 -3
  76. package/.output/server/chunks/build/{tokens-Bmm6rDfj.mjs.map → tokens-DiCojbN8.mjs.map} +1 -1
  77. package/.output/server/chunks/build/{town-Dsh0jlwZ.mjs → town-C7QM8DYK.mjs} +5 -5
  78. package/.output/server/chunks/build/{town-Dsh0jlwZ.mjs.map → town-C7QM8DYK.mjs.map} +1 -1
  79. package/.output/server/chunks/build/{useDaqStream-DkRL3tEW.mjs → useDaqStream-BeA2X8KR.mjs} +26 -11
  80. package/.output/server/chunks/build/useDaqStream-BeA2X8KR.mjs.map +1 -0
  81. package/.output/server/chunks/build/{useDcwStream-DyB2ufQI.mjs → useDcwStream-BJW_MitY.mjs} +14 -6
  82. package/.output/server/chunks/build/useDcwStream-BJW_MitY.mjs.map +1 -0
  83. package/.output/server/chunks/build/{useDeviceTwins-ZbT86L9O.mjs → useDeviceTwins-Dwz_EY_i.mjs} +2 -2
  84. package/.output/server/chunks/build/{useDeviceTwins-ZbT86L9O.mjs.map → useDeviceTwins-Dwz_EY_i.mjs.map} +1 -1
  85. package/.output/server/chunks/build/{useOpsLog-8HCYFb6n.mjs → useOpsLog-Dm8QEb1I.mjs} +2 -2
  86. package/.output/server/chunks/build/{useOpsLog-8HCYFb6n.mjs.map → useOpsLog-Dm8QEb1I.mjs.map} +1 -1
  87. package/.output/server/chunks/build/{useWorkshopApi-D2gq06N2.mjs → useWorkshopApi-B-L3MmIX.mjs} +9 -2
  88. package/.output/server/chunks/build/{useWorkshopApi-D2gq06N2.mjs.map → useWorkshopApi-B-L3MmIX.mjs.map} +1 -1
  89. package/.output/server/chunks/build/{users-B50HHrfi.mjs → users-B9SAdEtH.mjs} +3 -3
  90. package/.output/server/chunks/build/{users-B50HHrfi.mjs.map → users-B9SAdEtH.mjs.map} +1 -1
  91. package/.output/server/chunks/routes/api/plugins/_name/_...path_.mjs +14 -2
  92. package/.output/server/chunks/routes/api/plugins/_name/_...path_.mjs.map +1 -1
  93. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  94. package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs +40 -0
  95. package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs.map +1 -0
  96. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.get.mjs +21 -9
  97. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.get.mjs.map +1 -1
  98. package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs +33 -0
  99. package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs.map +1 -0
  100. package/.output/server/chunks/routes/api/workshop/channels/_id/terminals.get.mjs +1 -1
  101. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +15 -1
  102. package/.output/server/chunks/routes/api/workshop/channels.post.mjs.map +1 -1
  103. package/.output/server/chunks/routes/api/workshop/daq/_id/bind.post.mjs +1 -1
  104. package/.output/server/chunks/routes/api/workshop/daq/_id/bindings.put.mjs +1 -1
  105. package/.output/server/chunks/routes/api/workshop/daq/_id/frames/content.get.mjs +1 -1
  106. package/.output/server/chunks/routes/api/workshop/daq/_id/frames.get.mjs +1 -1
  107. package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs +1 -1
  108. package/.output/server/chunks/routes/api/workshop/daq/_id/test.post.mjs +1 -1
  109. package/.output/server/chunks/routes/api/workshop/daq/_id_.delete.mjs +1 -1
  110. package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs +1 -1
  111. package/.output/server/chunks/routes/api/workshop/daq/alarms/_id/ack.post.mjs +1 -1
  112. package/.output/server/chunks/routes/api/workshop/daq/controller.post.mjs +1 -1
  113. package/.output/server/chunks/routes/api/workshop/daq/index.post.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/daq/infra.get.mjs +1 -1
  116. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs +1 -1
  117. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs +1 -1
  118. package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs +1 -1
  119. package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +1 -1
  120. package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs +1 -1
  121. package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs +1 -1
  122. package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +1 -1
  123. package/.output/server/chunks/routes/api/workshop/dcw/_id/test.post.mjs +1 -1
  124. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +1 -1
  125. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +1 -1
  126. package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs +1 -1
  127. package/.output/server/chunks/routes/api/workshop/dcw/controller.post.mjs +1 -1
  128. package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs +1 -1
  129. package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +1 -1
  130. package/.output/server/chunks/routes/api/workshop/dcw/index2.get.mjs +1 -1
  131. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +1 -1
  132. package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs +1 -1
  133. package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +1 -1
  134. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +1 -1
  135. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +1 -1
  136. package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +1 -1
  137. package/.output/server/chunks/routes/api/workshop/dcw/journal/node/_nodeId/rollback.post.mjs +1 -1
  138. package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs +1 -1
  139. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  140. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  141. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +1 -1
  142. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/judge.post.mjs +1 -1
  143. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/rollback.post.mjs +1 -1
  144. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs +1 -1
  145. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +1 -1
  146. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.delete.mjs +1 -1
  147. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.patch.mjs +1 -1
  148. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/apply.post.mjs +1 -1
  149. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/mark-good.post.mjs +1 -1
  150. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/rollback-good.post.mjs +1 -1
  151. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs +1 -1
  152. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +1 -1
  153. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/data.get.mjs +1 -1
  154. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/rollback.post.mjs +1 -1
  155. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  156. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  157. package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs +1 -1
  158. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.delete.mjs +1 -1
  159. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.patch.mjs +1 -1
  160. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  161. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  162. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  163. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  164. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  165. package/.output/server/chunks/routes/api/workshop/index3.post.mjs +1 -1
  166. package/.output/server/chunks/routes/api/workshop/index4.get.mjs +1 -1
  167. package/.output/server/chunks/routes/api/workshop/index4.post.mjs +1 -1
  168. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +1 -1
  169. package/.output/server/chunks/routes/api/workshop/index5.post.mjs +1 -1
  170. package/.output/server/chunks/routes/api/workshop/index6.post.mjs +1 -1
  171. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  172. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  173. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  174. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  175. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  176. package/.output/server/chunks/routes/renderer.mjs +2 -2
  177. package/.output/server/chunks/virtual/entry.mjs +352 -433
  178. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  179. package/.output/server/package.json +1 -1
  180. package/app/components/workshop/DaqNodeRow.vue +398 -0
  181. package/app/components/workshop/town/ModelPreview3D.vue +26 -4
  182. package/app/components/workshop/town/TownScene3D.ts +60 -12
  183. package/app/components/workshop/town/TownView.vue +4 -3
  184. package/app/composables/workshop/useCharacterAssets.ts +1 -0
  185. package/app/composables/workshop/useDaqStream.ts +24 -5
  186. package/app/composables/workshop/useDcwStream.ts +13 -4
  187. package/app/composables/workshop/useWorkshopApi.ts +294 -259
  188. package/app/pages/daq/[id].vue +27 -3
  189. package/app/pages/daq/index.vue +8 -355
  190. package/app/pages/settings.vue +253 -2
  191. package/app/pages/workshop/teams.vue +212 -2
  192. package/i18n/dicts/_en.json +361 -1
  193. package/i18n/dicts/plugins.json +10 -0
  194. package/i18n/dicts/settings.json +1 -0
  195. package/i18n/dicts/teamPlugins.json +10 -0
  196. package/i18n/locales/en.ts +1860 -1900
  197. package/i18n/locales/zh-CN.ts +1788 -1829
  198. package/package.json +149 -148
  199. package/public/assets/game/character/yangyang-walk.glb +0 -0
  200. package/scripts/_dbg-cpu-profile.mjs +53 -0
  201. package/scripts/_dbg-render-perf.mjs +142 -0
  202. package/scripts/_dbg-render-regression.mjs +130 -0
  203. package/scripts/i18n-merge.mjs +152 -30
  204. package/scripts/production-closed-loop-e2e.mjs +254 -0
  205. package/scripts/team-plugins-api-smoke.mjs +149 -0
  206. package/scripts/three-system-e2e.mjs +448 -0
  207. package/server/api/plugins/[name]/[...path].ts +46 -31
  208. package/server/api/workshop/channels/[id]/plugins/index.get.ts +35 -0
  209. package/server/api/workshop/channels/[id]/plugins/index.put.ts +39 -0
  210. package/server/api/workshop/channels.post.ts +17 -0
  211. package/server/data/dcw-lines.json +7 -0
  212. package/server/data/dcw-products.json +7 -0
  213. package/server/data/dcw-recipes.json +1 -1
  214. package/server/data/dcw-rollback.json +523 -11
  215. package/server/data/dcw-writes.json +72 -0
  216. package/server/data/dcws.json +368 -368
  217. package/server/data/scene-layouts.json +66 -0
  218. package/server/plugins/aw-plugins.ts +35 -27
  219. package/server/plugins-builtin/diag-bridge/index.mjs +556 -0
  220. package/server/plugins-builtin/rag-bridge/index.mjs +364 -0
  221. package/server/services/workshop/agents/host-tool-bridge.ts +659 -642
  222. package/server/services/workshop/agents/omp-agent.ts +1178 -1178
  223. package/server/services/workshop/agents/plugin-tools.ts +10 -1
  224. package/server/services/workshop/db/channel-plugins.repo.ts +65 -0
  225. package/server/services/workshop/plugins/host.mjs +52 -4
  226. package/server/services/workshop/runtime/manager.ts +2976 -2968
  227. package/shared/config/schema.json +4 -0
  228. package/.output/public/_nuxt/BUcmySDa2.js +0 -209
  229. package/.output/public/_nuxt/CYzpZzB_.js +0 -1
  230. package/.output/public/_nuxt/CjXIpPMR.js +0 -1
  231. package/.output/public/_nuxt/DKHcFE1i.js +0 -1
  232. package/.output/public/_nuxt/DjtE7AYt.js +0 -1
  233. package/.output/public/_nuxt/JOwsShod.js +0 -2
  234. package/.output/public/_nuxt/_id_.C-YXjn8Q.css +0 -1
  235. package/.output/public/_nuxt/builds/meta/024de5f7-6a10-4f53-82ab-bc65f3ec9e35.json +0 -1
  236. package/.output/public/_nuxt/daq.3OKj58HH.css +0 -1
  237. package/.output/public/_nuxt/settings.BqMF2nfj.css +0 -1
  238. package/.output/public/_nuxt/teams.DN0pAGGu.css +0 -1
  239. package/.output/server/chunks/build/daq-B-3xCp_2.mjs.map +0 -1
  240. package/.output/server/chunks/build/settings-UfGyDgM7.mjs.map +0 -1
  241. package/.output/server/chunks/build/teams-Cbg27MWW.mjs.map +0 -1
  242. package/.output/server/chunks/build/useDaqStream-DkRL3tEW.mjs.map +0 -1
  243. package/.output/server/chunks/build/useDcwStream-DyB2ufQI.mjs.map +0 -1
@@ -1,642 +1,659 @@
1
- /**
2
- * HostToolBridge —— workspace 工具桥(harness 无关)。
3
- *
4
- * 把 omp-agent 时代的 host tool 定义/角色装配/分发逻辑抽为共享模块:
5
- * - omp:保持原生 host_tool_call → dispatch(经本桥);
6
- * - codex/dsh/opencode:经 stdio MCP 桥(server/harness/aw-mcp-bridge.mjs)
7
- * → HTTP agent-tools 面 → impl.dispatchHostTool → 同一本桥;
8
- * - REST 直调(agent-tools/invoke)同样落本桥。
9
- * 从而"工具注册一套逻辑、注入与分发全引擎一致"。
10
- *
11
- * 分发语义与原 omp 实现逐字节对齐:插件工具优先 → workspace 协作/任务/记忆面 →
12
- * 工业工具族 → 插件兜底 → 未知工具报错。
13
- */
14
- import { randomUUID } from 'node:crypto'
15
- import type {
16
- AgentInfo,
17
- AgentWorkspace,
18
- } from './agent-interface'
19
- import type { A2AArtifact, Part } from '../types/a2a'
20
- import type { WorkspaceTask } from '../types/task'
21
- import type { RpcHostToolDefinition } from './adapters/omp-rpc-client'
22
- import { loadHostToolDefs } from '../prompts/loader'
23
- import { daqRuntimeSettings } from '../settings'
24
- import { toolDaqFrames, toolDaqQuery, toolDcwControl, toolDcwJudge, toolDcwJournal, toolDcwRead, toolDcwRollback, toolLineContext, toolMyIndustrialNodes, toolOpsLog, toolRecipeLog, toolRecipeRollback, toolRecipeUpdate, toolRecipeVersions } from './industrial-tools'
25
- import { listPluginTools } from './plugin-tools'
26
- import { extractTaskMode } from '../runtime/execution-mode'
27
-
28
- /** host tool 定义(外置 .AgentWorkShop/prompts/host-tools.json;加载器缓存) */
29
- export const HOST_TOOLS: RpcHostToolDefinition[] = loadHostToolDefs()
30
-
31
- /** 仅 lead 可见的工具名(dispatch/调度/团队管理面;worker 注册时剔除,压缩工具上下文) */
32
- export const LEAD_ONLY_TOOL_NAMES = new Set([
33
- 'dispatch_task',
34
- 'get_queue_overview',
35
- 'read_channel_mail',
36
- 'reassign_task',
37
- 'update_task',
38
- 'create_team_agent',
39
- 'update_team_agent',
40
- 'remove_team_agent',
41
- ])
42
-
43
- /** 占位符动态注入:工具描述里的运行时配置值(每次装配实时计算,配置热重载后 Agent 拿到新值) */
44
- function applyDescriptionPlaceholders(tools: RpcHostToolDefinition[]): RpcHostToolDefinition[] {
45
- let q: { defaultBucketMs: number, minBucketMs: number } | null = null
46
- try {
47
- q = daqRuntimeSettings().query
48
- }
49
- catch { /* 设置系统不可用(单测) → 保留原始描述 */ }
50
- if (!q) return tools
51
- const map: Record<string, string> = {
52
- queryDefaultBucketMs: String(q.defaultBucketMs),
53
- queryMinBucketMs: String(q.minBucketMs),
54
- }
55
- return tools.map(t => ({
56
- ...t,
57
- description: t.description.replace(/\{(queryDefaultBucketMs|queryMinBucketMs)\}/g, (_, k) => map[k] ?? _),
58
- parameters: JSON.parse(JSON.stringify(t.parameters ?? {}).replace(/\{(queryDefaultBucketMs|queryMinBucketMs)\}/g, (_m: string, k: string) => map[k] ?? _m)),
59
- }))
60
- }
61
-
62
- /**
63
- * 按角色装配 host tools:lead = 全量;worker = 剔除 lead 专属(执行面 + 通信面 + 记忆面);
64
- * 尾部合并插件注册工具(roles 过滤,缺省双角色可用)。
65
- * 全 harness 共用:omp 经 set_host_tools 下发;其余引擎经 MCP 桥 tools/list 拉取。
66
- */
67
- export function hostToolsForRole(role: 'lead' | 'worker'): RpcHostToolDefinition[] {
68
- const base = role === 'lead'
69
- ? HOST_TOOLS
70
- : HOST_TOOLS.filter(t => !LEAD_ONLY_TOOL_NAMES.has(t.name))
71
- const out = [...base]
72
- for (const [name, tool] of listPluginTools()) {
73
- if (out.some(t => t.name === name)) continue
74
- if (tool.roles && !tool.roles.includes(role)) continue
75
- out.push({ name, label: tool.label ?? name, description: tool.description, parameters: tool.parameters ?? {} })
76
- }
77
- return applyDescriptionPlaceholders(out)
78
- }
79
-
80
- /** 工具调用请求(与引擎协议解耦的规范化形状) */
81
- export interface HostToolCall {
82
- toolName: string
83
- arguments: Record<string, unknown>
84
- }
85
-
86
- export interface HostToolResult {
87
- text: string
88
- isError?: boolean
89
- }
90
-
91
- /** 会话态(impl 持有,桥读写):当前执行任务 + 待回执上下文 */
92
- export interface HostToolSessionState {
93
- currentTaskId: string | null
94
- replyContext: { fromId: string, messageId: string } | null
95
- }
96
-
97
- export function createSessionState(): HostToolSessionState {
98
- return { currentTaskId: null, replyContext: null }
99
- }
100
-
101
- /** 桥上下文:impl 注入身份与 workspace 取值器 */
102
- export interface HostToolBridgeContext {
103
- identity: { agentId: string, channelId: string, role: 'lead' | 'worker', name: string }
104
- state: HostToolSessionState
105
- getWorkspace(): AgentWorkspace | null
106
- }
107
-
108
- /** 从消息 parts 提取纯文本(多 harness 共用) */
109
- export function partsToText(parts: Part[]): string {
110
- return parts
111
- .map((p) => {
112
- if ('text' in p) return p.text
113
- if ('data' in p) return JSON.stringify(p.data)
114
- if ('url' in p) return p.url
115
- if ('raw' in p) return p.raw
116
- return ''
117
- })
118
- .join('\n')
119
- }
120
-
121
- /**
122
- * 分发一次 host tool 调用(全引擎唯一入口):
123
- * 插件工具优先 → workspace 面 → 工业工具族 → 插件兜底 → 未知工具。
124
- */
125
- export async function dispatchHostTool(ctx: HostToolBridgeContext, req: HostToolCall): Promise<HostToolResult> {
126
- const identity = ctx.identity
127
- // 插件工具分发(ctx.omp.registerTool 注册的自定义工具;不依赖 workspace,优先于内置面)
128
- const pluginTool = listPluginTools().get(req.toolName)
129
- if (pluginTool) {
130
- try {
131
- return await pluginTool.handler(req.arguments ?? {}, identity)
132
- }
133
- catch (err) {
134
- return {
135
- text: `工具执行异常(${req.toolName}): ${err instanceof Error ? err.message : String(err)}`,
136
- isError: true,
137
- }
138
- }
139
- }
140
- const args = req.arguments ?? {}
141
-
142
- // 工业工具族不依赖 workspace(只按 agentId 查绑定与节点),先于 workspace 门控执行 ——
143
- // 否则 worker 首回合前的 REST/MCP 直调(my_industrial_nodes 等)会被误拒
144
- switch (req.toolName) {
145
- case 'my_industrial_nodes':
146
- return toolMyIndustrialNodes(identity.agentId)
147
- case 'dcw_control':
148
- return toolDcwControl(identity.agentId, args as { node_id?: string, value?: number | string, hypothesis?: string, task_id?: string })
149
- case 'dcw_read':
150
- return toolDcwRead(identity.agentId, args as { node_id?: string })
151
- case 'daq_query':
152
- return toolDaqQuery(identity.agentId, args as Parameters<typeof toolDaqQuery>[1])
153
- case 'daq_frames':
154
- return toolDaqFrames(identity.agentId, args as Parameters<typeof toolDaqFrames>[1])
155
- case 'dcw_judge':
156
- return toolDcwJudge(identity.agentId, args as { record_id?: string, verdict?: string, reason?: string })
157
- case 'dcw_rollback':
158
- return toolDcwRollback(identity.agentId, args as { record_id?: string, node_id?: string, to?: string })
159
- case 'dcw_journal':
160
- return toolDcwJournal(identity.agentId, args as { node_id?: string, recipe_id?: string, limit?: number | string })
161
- case 'ops_log':
162
- return toolOpsLog(identity.agentId, args as Parameters<typeof toolOpsLog>[1])
163
- case 'recipe_log':
164
- return toolRecipeLog(identity.agentId, args as Parameters<typeof toolRecipeLog>[1])
165
- case 'line_context':
166
- return toolLineContext(identity.agentId, args as { line_id?: string })
167
- case 'recipe_versions':
168
- return toolRecipeVersions(identity.agentId, args as { recipe_id?: string, limit?: number | string })
169
- case 'recipe_update':
170
- return toolRecipeUpdate(identity.agentId, args as Parameters<typeof toolRecipeUpdate>[1])
171
- case 'recipe_rollback':
172
- return toolRecipeRollback(identity.agentId, args as Parameters<typeof toolRecipeRollback>[1])
173
- }
174
-
175
- const ws = ctx.getWorkspace()
176
- if (!ws) {
177
- return { text: 'workspace 未就绪', isError: true }
178
- }
179
-
180
- try {
181
- switch (req.toolName) {
182
- case 'report_progress': {
183
- const progress = args.progress as number
184
- const message = args.message as string | undefined
185
- const taskId = ctx.state.currentTaskId
186
- if (!taskId) return { text: '无当前任务上下文', isError: true }
187
- await ws.reportTask({ taskId, progress, message })
188
- return { text: `进度已上报: ${progress}%${message ? ` (${message})` : ''}` }
189
- }
190
-
191
- case 'complete_task': {
192
- const summary = args.summary as string
193
- const deliverable = args.deliverable as string | undefined
194
- const taskId = (args.task_id as string | undefined) ?? ctx.state.currentTaskId
195
- if (!taskId) return { text: '无任务 ID', isError: true }
196
- // 父任务保护:有未完成子任务时拒绝完成(lead 须等 worker 交付)
197
- const allTasks = await ws.listTasks()
198
- const incompleteChildren = allTasks.filter(t => t.parentId === taskId && t.state !== 'COMPLETED' && t.state !== 'CANCELED')
199
- if (incompleteChildren.length > 0) {
200
- return {
201
- text: `任务 ${taskId} 有 ${incompleteChildren.length} 个未完成子任务,不能完成父任务。请等待子任务完成。`,
202
- isError: true,
203
- }
204
- }
205
- // 终态幂等:任务已被平台收口(看门狗取消/调度器完成)时不撞状态机 ——
206
- // 给 Agent 明确的"无需再完成,继续下一项"信号,杜绝重复重试烧 token
207
- const current = await ws.getTask(taskId)
208
- if (current && current.state !== 'SUBMITTED' && current.state !== 'ASSIGNED' && current.state !== 'WORKING' && current.state !== 'WAITING') {
209
- if (current.state === 'COMPLETED') {
210
- return { text: `任务 ${taskId} 已是完成状态(可能已被平台收口),无需重复完成。` }
211
- }
212
- try {
213
- const q = await ws.myQueue()
214
- const next = q.queued[0]
215
- return {
216
- text: `任务 ${taskId} 已被平台${current.state === 'CANCELED' ? '取消(如停滞回收/上级作废)' : '判定失败'},不能再标记完成 —— 这不是你的错误,也无需重试。${next ? `队列还有 ${q.queued.length} 项,下一项「${next.title}」即将开始,请继续处理。` : '队列为空,保持待命。'}`,
217
- isError: false,
218
- }
219
- }
220
- catch {
221
- return { text: `任务 ${taskId} 已被平台${current.state === 'CANCELED' ? '取消' : '判定失败'},无需再完成,请继续处理队列下一项。` }
222
- }
223
- }
224
- const artifacts: A2AArtifact[] = []
225
- if (deliverable || summary) {
226
- artifacts.push({
227
- artifactId: randomUUID(),
228
- name: 'deliverable',
229
- parts: [{ text: deliverable ?? summary }],
230
- })
231
- }
232
- await ws.completeTask(taskId, artifacts)
233
- ctx.state.currentTaskId = null
234
- // 完成即衔接:报告队列余量与下一项(状态同步 + 驱动继续处理)
235
- try {
236
- const q = await ws.myQueue()
237
- const next = q.queued[0]
238
- if (next) {
239
- return {
240
- text: `任务 ${taskId} 已完成(状态已同步为 COMPLETED)。你的队列还有 ${q.queued.length} 项待处理,下一项:「${next.title}」(即将自动开始;收到任务指派消息后按工作流执行,完成后同样调用 complete_task)。`,
241
- }
242
- }
243
- return { text: `任务 ${taskId} 已完成(状态已同步为 COMPLETED),队列为空。保持待命:新任务/实时消息会自动到达你的信箱。` }
244
- }
245
- catch {
246
- return { text: `任务 ${taskId} 已完成(状态已同步为 COMPLETED)。` }
247
- }
248
- }
249
-
250
- case 'dispatch_task': {
251
- const assigneeId = args.assignee_id as string
252
- const title = args.title as string
253
- const description = args.description as string | undefined
254
- const parentTaskId = args.parent_task_id as string | undefined
255
- const routeReason = args.route_reason as string | undefined
256
- // 重复派发守卫(真实场景实测:lead 模型可能对同一目标重复派发,引发协调风暴):
257
- // 同父任务下已存在同标题非终态子任务 → 不再创建,直接指路既有任务
258
- const dup = await ws.listTasks()
259
- const existing = dup.find(t =>
260
- t.parentId === parentTaskId
261
- && t.title === title
262
- && t.state !== 'COMPLETED' && t.state !== 'FAILED' && t.state !== 'CANCELED')
263
- if (existing) {
264
- return {
265
- text: `未创建:同父任务下已存在同标题进行中子任务 ${existing.id}("${existing.title}",state=${existing.state},assignee=${existing.assigneeId})。请勿重复派发;若需跟进请对该任务 notify 或 reassign。`,
266
- }
267
- }
268
- const task = await ws.dispatchTask({ assigneeId, title, description, parentTaskId, routeReason })
269
- return { text: `子任务 ${task.id} 已创建并指派 → ${assigneeId}(父任务 ${parentTaskId ?? '无'},标题: ${title}${routeReason ? `,路由理由: ${routeReason}` : ''})` }
270
- }
271
-
272
- case 'send_message_to_agent': {
273
- const toAgentId = args.to_agent_id as string
274
- const message = args.message as string
275
- // 回执自动实时:回复(in_reply_to)默认提升为 immediate——
276
- // 接收方正等待该结果,realtime 路由会把回复直接注入其运行中的会话
277
- let priority = (args.priority as string | undefined) ?? 'task'
278
- const metadata: Record<string, unknown> = {}
279
- if (args.require_reply === true) metadata['x-aw-require-reply'] = 'true'
280
- let inReplyTo = args.in_reply_to as string | undefined
281
- // 自动关联兜底:LLM 省略 in_reply_to 时,按待回执上下文盖章
282
- const replyCtx = ctx.state.replyContext
283
- ctx.state.replyContext = null
284
- if (!inReplyTo && replyCtx && replyCtx.fromId === toAgentId) {
285
- inReplyTo = replyCtx.messageId
286
- }
287
- if (inReplyTo) {
288
- metadata['x-aw-in-reply-to'] = inReplyTo
289
- if (priority === 'task') priority = 'immediate'
290
- }
291
- metadata['x-aw-msg-priority'] = priority
292
- const sent = await ws.sendMessage({ toAgentId, parts: [{ text: message }], metadata })
293
- const triggerNote = inReplyTo
294
- ? `(回复 ${inReplyTo.slice(0, 8)}…,已实时推送给对方)`
295
- : metadata['x-aw-require-reply'] === 'true' ? '(已要求对方回复)' : ''
296
- return { text: `消息 ${sent.messageId.slice(0, 8)}… 已发送给 ${toAgentId}(priority=${priority})${triggerNote}` }
297
- }
298
-
299
- case 'send_cross_channel_message': {
300
- const toChannelId = args.to_channel_id as string
301
- const message = args.message as string
302
- if (!toChannelId || !message) return { text: '缺少 to_channel_id 或 message', isError: true }
303
- try {
304
- const r = await ws.sendCrossChannelMessage({
305
- toChannelId,
306
- parts: [{ text: message }],
307
- requireReply: args.require_reply === true,
308
- inReplyTo: args.in_reply_to as string | undefined,
309
- })
310
- const note = args.require_reply === true ? '(已要求对方 Leader 回复)' : ''
311
- return { text: `跨 Channel 消息 ${r.messageId.slice(0, 8)}… 已送达 channel「${r.toChannelName}」的 Leader(${r.toLeadAgentId.slice(0, 8)}…)${note}。对方将按你的信息需求处理;其回复会经 mailbox 到达你这里。` }
312
- }
313
- catch (err) {
314
- return { text: `跨 Channel 发送失败: ${err instanceof Error ? err.message : String(err)}(仅 Leader 可跨 Channel 通信)`, isError: true }
315
- }
316
- }
317
-
318
- case 'list_other_teams': {
319
- const teams = await ws.listOtherTeams()
320
- if (teams.length === 0) return { text: '当前没有其他团队(或均未启用)。' }
321
- const text = teams.map((t) => {
322
- const active = t.activeTasks.length > 0
323
- ? t.activeTasks.map(x => `「${x.title}」(${x.state})`).join('、')
324
- : '无进行中任务'
325
- const done = t.recentCompleted.length > 0
326
- ? t.recentCompleted.map(x => `「${x.title}」`).join('、')
327
- : '无'
328
- const mem = t.sharedMemories > 0 ? `共享记忆 ${t.sharedMemories} 条(可检索)` : '暂无共享记忆'
329
- return `- ${t.name}${t.description ? `(${t.description})` : ''} · lead=${t.leadName ?? '?'} · ${mem}\n 进行中: ${active}\n 近期完成: ${done}\n channel_id: ${t.channelId}`
330
- }).join('\n')
331
- return { text: `其他团队概览:\n${text}\n(需要协作时用 send_cross_channel_message 向对应团队 Leader 发信;查具体知识用 search_other_teams_memory)` }
332
- }
333
-
334
- case 'search_other_teams_memory': {
335
- const query = (args.query as string | undefined) ?? ''
336
- if (!query) return { text: '缺少 query', isError: true }
337
- const rows = await ws.searchOtherTeamsMemory({ query, limit: Number(args.limit ?? 5) })
338
- if (rows.length === 0) return { text: `其他团队的共享记忆中没有命中「${query}」的内容。` }
339
- const text = rows.map(r =>
340
- `- [${r.channelName}] 「${r.title}」(${r.createdAt.slice(0, 10)}): ${r.content}`).join('\n')
341
- return { text: `其他团队共享记忆命中 ${rows.length} 条:\n${text}` }
342
- }
343
-
344
- case 'refuse_task': {
345
- const taskId = args.task_id as string
346
- const reason = args.reason as string
347
- if (!taskId || !reason) return { text: '缺少 task_id 或 reason', isError: true }
348
- const r = await ws.refuseTask(taskId, reason)
349
- const notified = r.notifiedTo ? `拒绝回执已送达 ${r.notifiedTo.slice(0, 8)}` : '无回执对象(创建者已不在 channel)'
350
- return {
351
- text: `任务 ${taskId.slice(0, 8)}("${r.task.title}") 已拒绝(state=${r.task.state});${notified}。调度器将改派他人,请勿再处理该任务。`,
352
- }
353
- }
354
-
355
- case 'poll_messages': {
356
- const limit = (args.limit as number | undefined) ?? 10
357
- const waitSec = Math.min(180, Math.max(0, Number(args.wait_seconds ?? 0) || 0))
358
- const msgs = await ws.waitMailbox(limit, waitSec * 1000)
359
- if (msgs.length === 0) {
360
- return {
361
- text: waitSec > 0
362
- ? `等待 ${waitSec}s 后收件箱仍为空。若此前已有"[实时消息 from X]"注入你的会话,那就是回复本身(无需再轮询);否则可继续处理其他工作,对方回复会以新回合送达。`
363
- : '收件箱为空(无未消费消息)。等待回复请用 wait_seconds 参数阻塞等待,不要反复空轮询。',
364
- }
365
- }
366
- // 读即取:协作消息(非任务投递)取出即确认;任务指派(assign)不确认
367
- const ackIds = msgs
368
- .filter(m => !m.metadata?.['x-aw-task-kind'])
369
- .map(m => m.messageId)
370
- if (ackIds.length > 0) await ws.ackMailbox(ackIds)
371
- const trigger = msgs.find(m =>
372
- m.metadata?.['x-aw-require-reply'] === 'true'
373
- && typeof m.metadata?.['x-aw-from-agent'] === 'string')
374
- if (trigger) {
375
- ctx.state.replyContext = {
376
- fromId: String(trigger.metadata!['x-aw-from-agent']),
377
- messageId: trigger.messageId,
378
- }
379
- }
380
- const text = msgs.map((m, i) => {
381
- const from = m.metadata?.['x-aw-from-agent'] ?? '?'
382
- const reply = m.metadata?.['x-aw-in-reply-to']
383
- ? ` (回复 ${String(m.metadata['x-aw-in-reply-to']).slice(0, 8)}…)`
384
- : ''
385
- const needReply = m.metadata?.['x-aw-require-reply'] === 'true'
386
- ? ` [需回复:用 send_message_to_agent 回 ${from},in_reply_to=${m.messageId}]`
387
- : ''
388
- const body = m.parts.map(p => 'text' in p ? p.text : '').join(' ')
389
- return ` [${i + 1}/${msgs.length}] [from ${from}]${needReply}${reply} ${body.slice(0, 2000)}`
390
- }).join('\n')
391
- return {
392
- text:
393
- `未消费消息(${msgs.length},已读即取):\n${text}`
394
- + (msgs.length > 1
395
- ? '\n(收到多条:请按编号逐条处理并逐条回复,不要只回应最后一条或合并敷衍)'
396
- : ''),
397
- }
398
- }
399
-
400
- case 'read_channel_mail': {
401
- const limit = (args.limit as number | undefined) ?? 50
402
- const agentId = args.agent_id as string | undefined
403
- const mails = await ws.listMail({ limit, agentId })
404
- if (mails.length === 0) return { text: 'Channel 无邮件记录(或该成员无往来)' }
405
- const text = mails.map((m) => {
406
- const from = m.fromAgentId ?? '(系统)'
407
- const to = m.toAgentId ?? '(广播)'
408
- const body = partsToText(m.parts).trim().slice(0, 2000)
409
- const reply = m.metadata?.['x-aw-in-reply-to']
410
- ? ` [回复 ${String(m.metadata['x-aw-in-reply-to']).slice(0, 8)}…]`
411
- : ''
412
- const label = m.metadata?.['x-aw-task-kind'] === 'assign'
413
- ? '[任务指派]'
414
- : m.metadata?.['x-aw-msg-priority'] === 'immediate' ? '[实时]' : '[协作]'
415
- const state = m.state === 'pending' ? '未读' : m.state === 'consuming' ? '处理中' : '已读'
416
- return ` ${m.createdAt.slice(11, 19)} ${label} ${from} → ${to} (${state})${reply}: ${body || '(空)'}`
417
- }).join('\n')
418
- return { text: `Channel 邮件(${mails.length},倒序;可传 agent_id 查看指定成员信箱):\n${text}` }
419
- }
420
-
421
- case 'broadcast_message': {
422
- const message = args.message as string
423
- const priority = (args.priority as string | undefined) ?? 'task'
424
- const agents = await ws.listAgents()
425
- const others = agents.filter(a => a.id !== identity.agentId)
426
- for (const agent of others) {
427
- await ws.sendMessage({
428
- toAgentId: agent.id,
429
- parts: [{ text: message }],
430
- metadata: { 'x-aw-msg-priority': priority },
431
- })
432
- }
433
- return { text: `已广播给 ${others.length} 个 agent(priority=${priority})` }
434
- }
435
-
436
- case 'list_channel_tasks': {
437
- const tasks = await ws.listTasks()
438
- const text = tasks.map(t =>
439
- ` ${t.id} [${t.state}] "${t.title}" assignee=${t.assigneeId} progress=${t.progress}%`,
440
- ).join('\n')
441
- return { text: `Channel 任务(${tasks.length}):\n${text || '(空)'}` }
442
- }
443
-
444
- case 'get_my_task_queue': {
445
- const queue = await ws.myQueue()
446
- const fmt = (t: WorkspaceTask): string =>
447
- ` ${t.id} [${t.state}] "${t.title}" progress=${t.progress}%`
448
- return {
449
- text: [
450
- `我的任务队列(${identity.role}):`,
451
- `执行中: ${queue.current ? `${queue.current.id} "${queue.current.title}" (${queue.current.progress}%)` : '(无)'}`,
452
- `待执行(${queue.queued.length},FIFO):`,
453
- ...queue.queued.map(fmt),
454
- `已完成(${queue.completed.length}):`,
455
- ...queue.completed.map(fmt),
456
- ].join('\n'),
457
- }
458
- }
459
-
460
- case 'get_queue_overview': {
461
- const overview = await ws.queueOverview()
462
- const lines = overview.map(s =>
463
- ` ${s.agentId} (${s.name}, role=${s.role}, state=${s.state}, current=${s.currentTaskId ?? '-'}${s.currentTaskTitle ? `「${s.currentTaskTitle}」` : ''}, progress=${s.currentTaskProgress ?? '-'}%, queued=${s.queuedCount}, completed=${s.completedCount})`,
464
- )
465
- return { text: `团队队列总览(${overview.length}):\n${lines.join('\n') || '(空)'}` }
466
- }
467
-
468
- case 'reassign_task': {
469
- const taskId = args.task_id as string
470
- const toAgentId = args.to_agent_id as string
471
- const task = await ws.reassignTask(taskId, toAgentId)
472
- return { text: `任务 ${taskId}("${task.title}")已调配 → ${toAgentId}(state=${task.state})` }
473
- }
474
-
475
- case 'update_task': {
476
- const taskId = args.task_id as string
477
- const title = args.title as string | undefined
478
- const description = args.description as string | undefined
479
- const task = await ws.updateTask(taskId, { title, description })
480
- return { text: `任务 ${taskId} 已更新: "${task.title}"` }
481
- }
482
-
483
- case 'cancel_task': {
484
- const taskId = args.task_id as string
485
- // 守卫:goal/loop/pipeline 的 mode 父任务是用户的作业主任务,Agent 不得经工具取消
486
- const target = await ws.getTask(taskId).catch(() => null)
487
- if (target && extractTaskMode(target)) {
488
- return {
489
- text: `拒绝:任务 ${taskId} 是 mode 父任务(${target.title}),不能用 cancel_task 取消。若目标未达成 → dispatch_task 派发子任务补齐差距;若确认无法达成 → complete_task 并在交付中说明未达成原因。终止整个作业请由用户操作。`,
490
- }
491
- }
492
- await ws.cancelTask(taskId)
493
- return { text: `任务 ${taskId} 已取消并移出 assignee 队列` }
494
- }
495
-
496
- case 'list_team_agents': {
497
- const agents = await ws.listAgents()
498
- const text = agents.map(a =>
499
- ` ${a.id} (${a.name}, role=${a.role}, harness=${a.harness})`,
500
- ).join('\n')
501
- return { text: `团队成员(${agents.length}):\n${text || '(空)'}` }
502
- }
503
-
504
- case 'get_task_details': {
505
- const taskId = args.task_id as string
506
- const task: WorkspaceTask = await ws.getTask(taskId)
507
- const artifactText = task.artifacts.map(a =>
508
- ` artifact ${a.artifactId}: ${a.parts.map(p => 'text' in p ? p.text.slice(0, 100) : '').join('; ')}`,
509
- ).join('\n')
510
- return {
511
- text: `任务 ${task.id}\n 状态: ${task.state}\n 标题: ${task.title}\n 描述: ${task.description ?? '-'}\n 指派: ${task.assigneeId}\n 进度: ${task.progress}%\n 成果:\n${artifactText || ' (无)'}`,
512
- }
513
- }
514
-
515
- case 'search_memory': {
516
- const query = args.query as string
517
- const scope = (args.scope as 'auto' | 'private' | 'shared' | undefined) ?? 'auto'
518
- const snippets = await ws.recallMemory({ query, scope, limit: args.limit as number | undefined })
519
- if (snippets.length === 0) {
520
- return { text: `记忆检索无命中(query="${query}", scope=${scope})。可尝试更换关键词或放宽 scope。` }
521
- }
522
- const lines = snippets.map(s =>
523
- ` [${s.source}·${s.kind}·score=${s.score}] ${s.title}\n ${s.content}`,
524
- )
525
- return { text: `记忆检索结果(${snippets.length} 条, scope=${scope}):\n${lines.join('\n')}` }
526
- }
527
-
528
- case 'save_memory': {
529
- const title = args.title as string
530
- const content = args.content as string
531
- const scope = args.scope as 'private' | 'shared'
532
- const saved = await ws.saveMemory({
533
- title,
534
- content,
535
- importance: args.importance as number | undefined,
536
- scope,
537
- dedupKey: args.dedup_key as string | undefined,
538
- })
539
- const where = scope === 'shared' ? 'Channel 公共记忆(全员可检索)' : '本人私有记忆'
540
- return { text: `已沉淀到${where}: "${title}"(dedupKey=${saved.dedupKey})` }
541
- }
542
-
543
- case 'create_team_agent': {
544
- const name = args.name as string
545
- const harness = args.harness as string | undefined
546
- const systemPrompt = args.system_prompt as string | undefined
547
- const reason = args.reason as string | undefined
548
- const agent: AgentInfo = await ws.createTeamMember({
549
- name,
550
- harness,
551
- config: systemPrompt ? { systemPromptPrefix: systemPrompt } : undefined,
552
- reason,
553
- })
554
- return {
555
- text: [
556
- `团队成员已创建并加入 channel:`,
557
- ` id: ${agent.id}`,
558
- ` name: ${agent.name}(role=worker, harness=${agent.harness})`,
559
- `新成员当前空闲,可立即 dispatch_task 指派任务;list_team_agents 可随时查看团队名册。`,
560
- ].join('\n'),
561
- }
562
- }
563
-
564
- case 'update_team_agent': {
565
- const agentId = args.agent_id as string
566
- const name = args.name as string | undefined
567
- const systemPrompt = args.system_prompt as string | undefined
568
- const enabled = args.enabled as boolean | undefined
569
- const reason = args.reason as string | undefined
570
- const agent = await ws.updateTeamMember(agentId, {
571
- name,
572
- config: systemPrompt !== undefined ? { systemPromptPrefix: systemPrompt } : undefined,
573
- enabled: enabled === undefined ? undefined : (enabled ? 1 : 0),
574
- reason,
575
- })
576
- return {
577
- text: `团队成员 ${agentId} 已更新:name="${agent.name}"${enabled !== undefined ? `, enabled=${enabled ? 1 : 0}` : ''};运行时将按新配置重载(下次任务生效)。`,
578
- }
579
- }
580
-
581
- case 'remove_team_agent': {
582
- const agentId = args.agent_id as string
583
- const reason = args.reason as string | undefined
584
- const result = await ws.removeTeamMember(agentId, reason)
585
- const recycleNote = result.recycledTasks.length > 0
586
- ? `其 ${result.recycledTasks.length} 个在途任务已回收(排队任务重派给剩余最短队列成员;执行中任务转 FAILED 待调度重试)。`
587
- : `该成员无在途任务。`
588
- return { text: `团队成员 ${agentId} 已移除。${recycleNote}` }
589
- }
590
-
591
- case 'my_industrial_nodes': {
592
- return toolMyIndustrialNodes(identity.agentId)
593
- }
594
- case 'dcw_control': {
595
- return toolDcwControl(identity.agentId, args as { node_id?: string, value?: number | string, hypothesis?: string, task_id?: string })
596
- }
597
- case 'dcw_read': {
598
- return toolDcwRead(identity.agentId, args as { node_id?: string })
599
- }
600
- case 'daq_query': {
601
- return toolDaqQuery(identity.agentId, args as Parameters<typeof toolDaqQuery>[1])
602
- }
603
- case 'daq_frames': {
604
- return toolDaqFrames(identity.agentId, args as Parameters<typeof toolDaqFrames>[1])
605
- }
606
- case 'dcw_judge': {
607
- return toolDcwJudge(identity.agentId, args as { record_id?: string, verdict?: string, reason?: string })
608
- }
609
- case 'dcw_rollback': {
610
- return toolDcwRollback(identity.agentId, args as { record_id?: string, node_id?: string, to?: string })
611
- }
612
- case 'dcw_journal': {
613
- return toolDcwJournal(identity.agentId, args as { node_id?: string, recipe_id?: string, limit?: number | string })
614
- }
615
- case 'ops_log': {
616
- return toolOpsLog(identity.agentId, args as Parameters<typeof toolOpsLog>[1])
617
- }
618
- case 'recipe_log': {
619
- return toolRecipeLog(identity.agentId, args as Parameters<typeof toolRecipeLog>[1])
620
- }
621
- case 'line_context': {
622
- return toolLineContext(identity.agentId, args as { line_id?: string })
623
- }
624
- case 'recipe_versions': {
625
- return toolRecipeVersions(identity.agentId, args as { recipe_id?: string, limit?: number | string })
626
- }
627
- case 'recipe_update': {
628
- return toolRecipeUpdate(identity.agentId, args as Parameters<typeof toolRecipeUpdate>[1])
629
- }
630
- case 'recipe_rollback': {
631
- return toolRecipeRollback(identity.agentId, args as Parameters<typeof toolRecipeRollback>[1])
632
- }
633
- }
634
- return { text: `未知工具: ${req.toolName}`, isError: true }
635
- }
636
- catch (err) {
637
- return {
638
- text: `工具执行异常(${req.toolName}): ${err instanceof Error ? err.message : String(err)}`,
639
- isError: true,
640
- }
641
- }
642
- }
1
+ /**
2
+ * HostToolBridge —— workspace 工具桥(harness 无关)。
3
+ *
4
+ * 把 omp-agent 时代的 host tool 定义/角色装配/分发逻辑抽为共享模块:
5
+ * - omp:保持原生 host_tool_call → dispatch(经本桥);
6
+ * - codex/dsh/opencode:经 stdio MCP 桥(server/harness/aw-mcp-bridge.mjs)
7
+ * → HTTP agent-tools 面 → impl.dispatchHostTool → 同一本桥;
8
+ * - REST 直调(agent-tools/invoke)同样落本桥。
9
+ * 从而"工具注册一套逻辑、注入与分发全引擎一致"。
10
+ *
11
+ * 分发语义与原 omp 实现逐字节对齐:插件工具优先 → workspace 协作/任务/记忆面 →
12
+ * 工业工具族 → 插件兜底 → 未知工具报错。
13
+ */
14
+ import { randomUUID } from 'node:crypto'
15
+ import type {
16
+ AgentInfo,
17
+ AgentWorkspace,
18
+ } from './agent-interface'
19
+ import type { A2AArtifact, Part } from '../types/a2a'
20
+ import type { WorkspaceTask } from '../types/task'
21
+ import type { RpcHostToolDefinition } from './adapters/omp-rpc-client'
22
+ import { loadHostToolDefs } from '../prompts/loader'
23
+ import { daqRuntimeSettings } from '../settings'
24
+ import { toolDaqFrames, toolDaqQuery, toolDcwControl, toolDcwJudge, toolDcwJournal, toolDcwRead, toolDcwRollback, toolLineContext, toolMyIndustrialNodes, toolOpsLog, toolRecipeLog, toolRecipeRollback, toolRecipeUpdate, toolRecipeVersions } from './industrial-tools'
25
+ import { listPluginTools, pluginOfTool } from './plugin-tools'
26
+ import { getChannelPluginsRepo } from '../db/channel-plugins.repo'
27
+ import { extractTaskMode } from '../runtime/execution-mode'
28
+
29
+ /** host tool 定义(外置 .AgentWorkShop/prompts/host-tools.json;加载器缓存) */
30
+ export const HOST_TOOLS: RpcHostToolDefinition[] = loadHostToolDefs()
31
+
32
+ /** 仅 lead 可见的工具名(dispatch/调度/团队管理面;worker 注册时剔除,压缩工具上下文) */
33
+ export const LEAD_ONLY_TOOL_NAMES = new Set([
34
+ 'dispatch_task',
35
+ 'get_queue_overview',
36
+ 'read_channel_mail',
37
+ 'reassign_task',
38
+ 'update_task',
39
+ 'create_team_agent',
40
+ 'update_team_agent',
41
+ 'remove_team_agent',
42
+ ])
43
+
44
+ /** 占位符动态注入:工具描述里的运行时配置值(每次装配实时计算,配置热重载后 Agent 拿到新值) */
45
+ function applyDescriptionPlaceholders(tools: RpcHostToolDefinition[]): RpcHostToolDefinition[] {
46
+ let q: { defaultBucketMs: number, minBucketMs: number } | null = null
47
+ try {
48
+ q = daqRuntimeSettings().query
49
+ }
50
+ catch { /* 设置系统不可用(单测) → 保留原始描述 */ }
51
+ if (!q) return tools
52
+ const map: Record<string, string> = {
53
+ queryDefaultBucketMs: String(q.defaultBucketMs),
54
+ queryMinBucketMs: String(q.minBucketMs),
55
+ }
56
+ return tools.map(t => ({
57
+ ...t,
58
+ description: t.description.replace(/\{(queryDefaultBucketMs|queryMinBucketMs)\}/g, (_, k) => map[k] ?? _),
59
+ parameters: JSON.parse(JSON.stringify(t.parameters ?? {}).replace(/\{(queryDefaultBucketMs|queryMinBucketMs)\}/g, (_m: string, k: string) => map[k] ?? _m)),
60
+ }))
61
+ }
62
+
63
+ /**
64
+ * 按角色装配 host tools:lead = 全量;worker = 剔除 lead 专属(执行面 + 通信面 + 记忆面);
65
+ * 尾部合并插件注册工具(roles 过滤,缺省双角色可用;channelId 给定且该团队有显式
66
+ * 插件开关时,关闭的插件其工具不注入 —— 防不需要插件的 channel 上下文被污染)。
67
+ * 全 harness 共用:omp 经 set_host_tools 下发;其余引擎经 MCP 桥 tools/list 拉取。
68
+ */
69
+ export function hostToolsForRole(role: 'lead' | 'worker', channelId?: string): RpcHostToolDefinition[] {
70
+ const base = role === 'lead'
71
+ ? HOST_TOOLS
72
+ : HOST_TOOLS.filter(t => !LEAD_ONLY_TOOL_NAMES.has(t.name))
73
+ const out = [...base]
74
+ // 团队级插件开关:显式配置过的 channel 按行过滤;未配置(无行)= 全启用,向后兼容
75
+ const channelOff = channelId
76
+ ? getChannelPluginsRepo().explicitFor(channelId)
77
+ : null
78
+ for (const [name, tool] of listPluginTools()) {
79
+ if (out.some(t => t.name === name)) continue
80
+ if (tool.roles && !tool.roles.includes(role)) continue
81
+ const owner = pluginOfTool(name)
82
+ if (channelOff && owner && channelOff.get(owner) === false) continue
83
+ out.push({ name, label: tool.label ?? name, description: tool.description, parameters: tool.parameters ?? {} })
84
+ }
85
+ return applyDescriptionPlaceholders(out)
86
+ }
87
+
88
+ /** 工具调用请求(与引擎协议解耦的规范化形状) */
89
+ export interface HostToolCall {
90
+ toolName: string
91
+ arguments: Record<string, unknown>
92
+ }
93
+
94
+ export interface HostToolResult {
95
+ text: string
96
+ isError?: boolean
97
+ }
98
+
99
+ /** 会话态(impl 持有,桥读写):当前执行任务 + 待回执上下文 */
100
+ export interface HostToolSessionState {
101
+ currentTaskId: string | null
102
+ replyContext: { fromId: string, messageId: string } | null
103
+ }
104
+
105
+ export function createSessionState(): HostToolSessionState {
106
+ return { currentTaskId: null, replyContext: null }
107
+ }
108
+
109
+ /** 桥上下文:impl 注入身份与 workspace 取值器 */
110
+ export interface HostToolBridgeContext {
111
+ identity: { agentId: string, channelId: string, role: 'lead' | 'worker', name: string }
112
+ state: HostToolSessionState
113
+ getWorkspace(): AgentWorkspace | null
114
+ }
115
+
116
+ /** 从消息 parts 提取纯文本(多 harness 共用) */
117
+ export function partsToText(parts: Part[]): string {
118
+ return parts
119
+ .map((p) => {
120
+ if ('text' in p) return p.text
121
+ if ('data' in p) return JSON.stringify(p.data)
122
+ if ('url' in p) return p.url
123
+ if ('raw' in p) return p.raw
124
+ return ''
125
+ })
126
+ .join('\n')
127
+ }
128
+
129
+ /**
130
+ * 分发一次 host tool 调用(全引擎唯一入口):
131
+ * 插件工具优先 → workspace 面 → 工业工具族 → 插件兜底 → 未知工具。
132
+ */
133
+ export async function dispatchHostTool(ctx: HostToolBridgeContext, req: HostToolCall): Promise<HostToolResult> {
134
+ const identity = ctx.identity
135
+ // 插件工具分发(ctx.omp.registerTool 注册的自定义工具;不依赖 workspace,优先于内置面)
136
+ const pluginTool = listPluginTools().get(req.toolName)
137
+ if (pluginTool) {
138
+ // 团队级插件开关:显式关闭的插件,其工具在该团队拒绝执行(与注入过滤同源)
139
+ const channelOff = getChannelPluginsRepo().explicitFor(identity.channelId)
140
+ const owner = pluginOfTool(req.toolName)
141
+ if (channelOff && owner && channelOff.get(owner) === false) {
142
+ return {
143
+ text: `该团队未启用插件「${owner}」,${req.toolName} 不可用。可在团队设置→插件中开启。`,
144
+ isError: true,
145
+ }
146
+ }
147
+ try {
148
+ return await pluginTool.handler(req.arguments ?? {}, identity)
149
+ }
150
+ catch (err) {
151
+ return {
152
+ text: `工具执行异常(${req.toolName}): ${err instanceof Error ? err.message : String(err)}`,
153
+ isError: true,
154
+ }
155
+ }
156
+ }
157
+ const args = req.arguments ?? {}
158
+
159
+ // 工业工具族不依赖 workspace(只按 agentId 查绑定与节点),先于 workspace 门控执行 ——
160
+ // 否则 worker 首回合前的 REST/MCP 直调(my_industrial_nodes 等)会被误拒
161
+ switch (req.toolName) {
162
+ case 'my_industrial_nodes':
163
+ return toolMyIndustrialNodes(identity.agentId)
164
+ case 'dcw_control':
165
+ return toolDcwControl(identity.agentId, args as { node_id?: string, value?: number | string, hypothesis?: string, task_id?: string })
166
+ case 'dcw_read':
167
+ return toolDcwRead(identity.agentId, args as { node_id?: string })
168
+ case 'daq_query':
169
+ return toolDaqQuery(identity.agentId, args as Parameters<typeof toolDaqQuery>[1])
170
+ case 'daq_frames':
171
+ return toolDaqFrames(identity.agentId, args as Parameters<typeof toolDaqFrames>[1])
172
+ case 'dcw_judge':
173
+ return toolDcwJudge(identity.agentId, args as { record_id?: string, verdict?: string, reason?: string })
174
+ case 'dcw_rollback':
175
+ return toolDcwRollback(identity.agentId, args as { record_id?: string, node_id?: string, to?: string })
176
+ case 'dcw_journal':
177
+ return toolDcwJournal(identity.agentId, args as { node_id?: string, recipe_id?: string, limit?: number | string })
178
+ case 'ops_log':
179
+ return toolOpsLog(identity.agentId, args as Parameters<typeof toolOpsLog>[1])
180
+ case 'recipe_log':
181
+ return toolRecipeLog(identity.agentId, args as Parameters<typeof toolRecipeLog>[1])
182
+ case 'line_context':
183
+ return toolLineContext(identity.agentId, args as { line_id?: string })
184
+ case 'recipe_versions':
185
+ return toolRecipeVersions(identity.agentId, args as { recipe_id?: string, limit?: number | string })
186
+ case 'recipe_update':
187
+ return toolRecipeUpdate(identity.agentId, args as Parameters<typeof toolRecipeUpdate>[1])
188
+ case 'recipe_rollback':
189
+ return toolRecipeRollback(identity.agentId, args as Parameters<typeof toolRecipeRollback>[1])
190
+ }
191
+
192
+ const ws = ctx.getWorkspace()
193
+ if (!ws) {
194
+ return { text: 'workspace 未就绪', isError: true }
195
+ }
196
+
197
+ try {
198
+ switch (req.toolName) {
199
+ case 'report_progress': {
200
+ const progress = args.progress as number
201
+ const message = args.message as string | undefined
202
+ const taskId = ctx.state.currentTaskId
203
+ if (!taskId) return { text: '无当前任务上下文', isError: true }
204
+ await ws.reportTask({ taskId, progress, message })
205
+ return { text: `进度已上报: ${progress}%${message ? ` (${message})` : ''}` }
206
+ }
207
+
208
+ case 'complete_task': {
209
+ const summary = args.summary as string
210
+ const deliverable = args.deliverable as string | undefined
211
+ const taskId = (args.task_id as string | undefined) ?? ctx.state.currentTaskId
212
+ if (!taskId) return { text: '无任务 ID', isError: true }
213
+ // 父任务保护:有未完成子任务时拒绝完成(lead 须等 worker 交付)
214
+ const allTasks = await ws.listTasks()
215
+ const incompleteChildren = allTasks.filter(t => t.parentId === taskId && t.state !== 'COMPLETED' && t.state !== 'CANCELED')
216
+ if (incompleteChildren.length > 0) {
217
+ return {
218
+ text: `任务 ${taskId} 有 ${incompleteChildren.length} 个未完成子任务,不能完成父任务。请等待子任务完成。`,
219
+ isError: true,
220
+ }
221
+ }
222
+ // 终态幂等:任务已被平台收口(看门狗取消/调度器完成)时不撞状态机 ——
223
+ // 给 Agent 明确的"无需再完成,继续下一项"信号,杜绝重复重试烧 token
224
+ const current = await ws.getTask(taskId)
225
+ if (current && current.state !== 'SUBMITTED' && current.state !== 'ASSIGNED' && current.state !== 'WORKING' && current.state !== 'WAITING') {
226
+ if (current.state === 'COMPLETED') {
227
+ return { text: `任务 ${taskId} 已是完成状态(可能已被平台收口),无需重复完成。` }
228
+ }
229
+ try {
230
+ const q = await ws.myQueue()
231
+ const next = q.queued[0]
232
+ return {
233
+ text: `任务 ${taskId} 已被平台${current.state === 'CANCELED' ? '取消(如停滞回收/上级作废)' : '判定失败'},不能再标记完成 —— 这不是你的错误,也无需重试。${next ? `队列还有 ${q.queued.length} 项,下一项「${next.title}」即将开始,请继续处理。` : '队列为空,保持待命。'}`,
234
+ isError: false,
235
+ }
236
+ }
237
+ catch {
238
+ return { text: `任务 ${taskId} 已被平台${current.state === 'CANCELED' ? '取消' : '判定失败'},无需再完成,请继续处理队列下一项。` }
239
+ }
240
+ }
241
+ const artifacts: A2AArtifact[] = []
242
+ if (deliverable || summary) {
243
+ artifacts.push({
244
+ artifactId: randomUUID(),
245
+ name: 'deliverable',
246
+ parts: [{ text: deliverable ?? summary }],
247
+ })
248
+ }
249
+ await ws.completeTask(taskId, artifacts)
250
+ ctx.state.currentTaskId = null
251
+ // 完成即衔接:报告队列余量与下一项(状态同步 + 驱动继续处理)
252
+ try {
253
+ const q = await ws.myQueue()
254
+ const next = q.queued[0]
255
+ if (next) {
256
+ return {
257
+ text: `任务 ${taskId} 已完成(状态已同步为 COMPLETED)。你的队列还有 ${q.queued.length} 项待处理,下一项:「${next.title}」(即将自动开始;收到任务指派消息后按工作流执行,完成后同样调用 complete_task)。`,
258
+ }
259
+ }
260
+ return { text: `任务 ${taskId} 已完成(状态已同步为 COMPLETED),队列为空。保持待命:新任务/实时消息会自动到达你的信箱。` }
261
+ }
262
+ catch {
263
+ return { text: `任务 ${taskId} 已完成(状态已同步为 COMPLETED)。` }
264
+ }
265
+ }
266
+
267
+ case 'dispatch_task': {
268
+ const assigneeId = args.assignee_id as string
269
+ const title = args.title as string
270
+ const description = args.description as string | undefined
271
+ const parentTaskId = args.parent_task_id as string | undefined
272
+ const routeReason = args.route_reason as string | undefined
273
+ // 重复派发守卫(真实场景实测:lead 模型可能对同一目标重复派发,引发协调风暴):
274
+ // 同父任务下已存在同标题非终态子任务 → 不再创建,直接指路既有任务
275
+ const dup = await ws.listTasks()
276
+ const existing = dup.find(t =>
277
+ t.parentId === parentTaskId
278
+ && t.title === title
279
+ && t.state !== 'COMPLETED' && t.state !== 'FAILED' && t.state !== 'CANCELED')
280
+ if (existing) {
281
+ return {
282
+ text: `未创建:同父任务下已存在同标题进行中子任务 ${existing.id}("${existing.title}",state=${existing.state},assignee=${existing.assigneeId})。请勿重复派发;若需跟进请对该任务 notify 或 reassign。`,
283
+ }
284
+ }
285
+ const task = await ws.dispatchTask({ assigneeId, title, description, parentTaskId, routeReason })
286
+ return { text: `子任务 ${task.id} 已创建并指派 → ${assigneeId}(父任务 ${parentTaskId ?? '无'},标题: ${title}${routeReason ? `,路由理由: ${routeReason}` : ''})` }
287
+ }
288
+
289
+ case 'send_message_to_agent': {
290
+ const toAgentId = args.to_agent_id as string
291
+ const message = args.message as string
292
+ // 回执自动实时:回复(in_reply_to)默认提升为 immediate——
293
+ // 接收方正等待该结果,realtime 路由会把回复直接注入其运行中的会话
294
+ let priority = (args.priority as string | undefined) ?? 'task'
295
+ const metadata: Record<string, unknown> = {}
296
+ if (args.require_reply === true) metadata['x-aw-require-reply'] = 'true'
297
+ let inReplyTo = args.in_reply_to as string | undefined
298
+ // 自动关联兜底:LLM 省略 in_reply_to 时,按待回执上下文盖章
299
+ const replyCtx = ctx.state.replyContext
300
+ ctx.state.replyContext = null
301
+ if (!inReplyTo && replyCtx && replyCtx.fromId === toAgentId) {
302
+ inReplyTo = replyCtx.messageId
303
+ }
304
+ if (inReplyTo) {
305
+ metadata['x-aw-in-reply-to'] = inReplyTo
306
+ if (priority === 'task') priority = 'immediate'
307
+ }
308
+ metadata['x-aw-msg-priority'] = priority
309
+ const sent = await ws.sendMessage({ toAgentId, parts: [{ text: message }], metadata })
310
+ const triggerNote = inReplyTo
311
+ ? `(回复 ${inReplyTo.slice(0, 8)}…,已实时推送给对方)`
312
+ : metadata['x-aw-require-reply'] === 'true' ? '(已要求对方回复)' : ''
313
+ return { text: `消息 ${sent.messageId.slice(0, 8)}… 已发送给 ${toAgentId}(priority=${priority})${triggerNote}` }
314
+ }
315
+
316
+ case 'send_cross_channel_message': {
317
+ const toChannelId = args.to_channel_id as string
318
+ const message = args.message as string
319
+ if (!toChannelId || !message) return { text: '缺少 to_channel_id 或 message', isError: true }
320
+ try {
321
+ const r = await ws.sendCrossChannelMessage({
322
+ toChannelId,
323
+ parts: [{ text: message }],
324
+ requireReply: args.require_reply === true,
325
+ inReplyTo: args.in_reply_to as string | undefined,
326
+ })
327
+ const note = args.require_reply === true ? '(已要求对方 Leader 回复)' : ''
328
+ return { text: `跨 Channel 消息 ${r.messageId.slice(0, 8)}… 已送达 channel「${r.toChannelName}」的 Leader(${r.toLeadAgentId.slice(0, 8)}…)${note}。对方将按你的信息需求处理;其回复会经 mailbox 到达你这里。` }
329
+ }
330
+ catch (err) {
331
+ return { text: `跨 Channel 发送失败: ${err instanceof Error ? err.message : String(err)}(仅 Leader 可跨 Channel 通信)`, isError: true }
332
+ }
333
+ }
334
+
335
+ case 'list_other_teams': {
336
+ const teams = await ws.listOtherTeams()
337
+ if (teams.length === 0) return { text: '当前没有其他团队(或均未启用)。' }
338
+ const text = teams.map((t) => {
339
+ const active = t.activeTasks.length > 0
340
+ ? t.activeTasks.map(x => `「${x.title}」(${x.state})`).join('、')
341
+ : '无进行中任务'
342
+ const done = t.recentCompleted.length > 0
343
+ ? t.recentCompleted.map(x => `「${x.title}」`).join('、')
344
+ : '无'
345
+ const mem = t.sharedMemories > 0 ? `共享记忆 ${t.sharedMemories} 条(可检索)` : '暂无共享记忆'
346
+ return `- ${t.name}${t.description ? `(${t.description})` : ''} · lead=${t.leadName ?? '?'} · ${mem}\n 进行中: ${active}\n 近期完成: ${done}\n channel_id: ${t.channelId}`
347
+ }).join('\n')
348
+ return { text: `其他团队概览:\n${text}\n(需要协作时用 send_cross_channel_message 向对应团队 Leader 发信;查具体知识用 search_other_teams_memory)` }
349
+ }
350
+
351
+ case 'search_other_teams_memory': {
352
+ const query = (args.query as string | undefined) ?? ''
353
+ if (!query) return { text: '缺少 query', isError: true }
354
+ const rows = await ws.searchOtherTeamsMemory({ query, limit: Number(args.limit ?? 5) })
355
+ if (rows.length === 0) return { text: `其他团队的共享记忆中没有命中「${query}」的内容。` }
356
+ const text = rows.map(r =>
357
+ `- [${r.channelName}] 「${r.title}」(${r.createdAt.slice(0, 10)}): ${r.content}`).join('\n')
358
+ return { text: `其他团队共享记忆命中 ${rows.length} 条:\n${text}` }
359
+ }
360
+
361
+ case 'refuse_task': {
362
+ const taskId = args.task_id as string
363
+ const reason = args.reason as string
364
+ if (!taskId || !reason) return { text: '缺少 task_id 或 reason', isError: true }
365
+ const r = await ws.refuseTask(taskId, reason)
366
+ const notified = r.notifiedTo ? `拒绝回执已送达 ${r.notifiedTo.slice(0, 8)}` : '无回执对象(创建者已不在 channel)'
367
+ return {
368
+ text: `任务 ${taskId.slice(0, 8)}("${r.task.title}") 已拒绝(state=${r.task.state});${notified}。调度器将改派他人,请勿再处理该任务。`,
369
+ }
370
+ }
371
+
372
+ case 'poll_messages': {
373
+ const limit = (args.limit as number | undefined) ?? 10
374
+ const waitSec = Math.min(180, Math.max(0, Number(args.wait_seconds ?? 0) || 0))
375
+ const msgs = await ws.waitMailbox(limit, waitSec * 1000)
376
+ if (msgs.length === 0) {
377
+ return {
378
+ text: waitSec > 0
379
+ ? `等待 ${waitSec}s 后收件箱仍为空。若此前已有"[实时消息 from X]"注入你的会话,那就是回复本身(无需再轮询);否则可继续处理其他工作,对方回复会以新回合送达。`
380
+ : '收件箱为空(无未消费消息)。等待回复请用 wait_seconds 参数阻塞等待,不要反复空轮询。',
381
+ }
382
+ }
383
+ // 读即取:协作消息(非任务投递)取出即确认;任务指派(assign)不确认
384
+ const ackIds = msgs
385
+ .filter(m => !m.metadata?.['x-aw-task-kind'])
386
+ .map(m => m.messageId)
387
+ if (ackIds.length > 0) await ws.ackMailbox(ackIds)
388
+ const trigger = msgs.find(m =>
389
+ m.metadata?.['x-aw-require-reply'] === 'true'
390
+ && typeof m.metadata?.['x-aw-from-agent'] === 'string')
391
+ if (trigger) {
392
+ ctx.state.replyContext = {
393
+ fromId: String(trigger.metadata!['x-aw-from-agent']),
394
+ messageId: trigger.messageId,
395
+ }
396
+ }
397
+ const text = msgs.map((m, i) => {
398
+ const from = m.metadata?.['x-aw-from-agent'] ?? '?'
399
+ const reply = m.metadata?.['x-aw-in-reply-to']
400
+ ? ` (回复 ${String(m.metadata['x-aw-in-reply-to']).slice(0, 8)}…)`
401
+ : ''
402
+ const needReply = m.metadata?.['x-aw-require-reply'] === 'true'
403
+ ? ` [需回复:用 send_message_to_agent 回 ${from},in_reply_to=${m.messageId}]`
404
+ : ''
405
+ const body = m.parts.map(p => 'text' in p ? p.text : '').join(' ')
406
+ return ` [${i + 1}/${msgs.length}] [from ${from}]${needReply}${reply} ${body.slice(0, 2000)}`
407
+ }).join('\n')
408
+ return {
409
+ text:
410
+ `未消费消息(${msgs.length},已读即取):\n${text}`
411
+ + (msgs.length > 1
412
+ ? '\n(收到多条:请按编号逐条处理并逐条回复,不要只回应最后一条或合并敷衍)'
413
+ : ''),
414
+ }
415
+ }
416
+
417
+ case 'read_channel_mail': {
418
+ const limit = (args.limit as number | undefined) ?? 50
419
+ const agentId = args.agent_id as string | undefined
420
+ const mails = await ws.listMail({ limit, agentId })
421
+ if (mails.length === 0) return { text: 'Channel 无邮件记录(或该成员无往来)' }
422
+ const text = mails.map((m) => {
423
+ const from = m.fromAgentId ?? '(系统)'
424
+ const to = m.toAgentId ?? '(广播)'
425
+ const body = partsToText(m.parts).trim().slice(0, 2000)
426
+ const reply = m.metadata?.['x-aw-in-reply-to']
427
+ ? ` [回复 ${String(m.metadata['x-aw-in-reply-to']).slice(0, 8)}…]`
428
+ : ''
429
+ const label = m.metadata?.['x-aw-task-kind'] === 'assign'
430
+ ? '[任务指派]'
431
+ : m.metadata?.['x-aw-msg-priority'] === 'immediate' ? '[实时]' : '[协作]'
432
+ const state = m.state === 'pending' ? '未读' : m.state === 'consuming' ? '处理中' : '已读'
433
+ return ` ${m.createdAt.slice(11, 19)} ${label} ${from} → ${to} (${state})${reply}: ${body || '(空)'}`
434
+ }).join('\n')
435
+ return { text: `Channel 邮件(${mails.length},倒序;可传 agent_id 查看指定成员信箱):\n${text}` }
436
+ }
437
+
438
+ case 'broadcast_message': {
439
+ const message = args.message as string
440
+ const priority = (args.priority as string | undefined) ?? 'task'
441
+ const agents = await ws.listAgents()
442
+ const others = agents.filter(a => a.id !== identity.agentId)
443
+ for (const agent of others) {
444
+ await ws.sendMessage({
445
+ toAgentId: agent.id,
446
+ parts: [{ text: message }],
447
+ metadata: { 'x-aw-msg-priority': priority },
448
+ })
449
+ }
450
+ return { text: `已广播给 ${others.length} 个 agent(priority=${priority})` }
451
+ }
452
+
453
+ case 'list_channel_tasks': {
454
+ const tasks = await ws.listTasks()
455
+ const text = tasks.map(t =>
456
+ ` ${t.id} [${t.state}] "${t.title}" assignee=${t.assigneeId} progress=${t.progress}%`,
457
+ ).join('\n')
458
+ return { text: `Channel 任务(${tasks.length}):\n${text || '(空)'}` }
459
+ }
460
+
461
+ case 'get_my_task_queue': {
462
+ const queue = await ws.myQueue()
463
+ const fmt = (t: WorkspaceTask): string =>
464
+ ` ${t.id} [${t.state}] "${t.title}" progress=${t.progress}%`
465
+ return {
466
+ text: [
467
+ `我的任务队列(${identity.role}):`,
468
+ `执行中: ${queue.current ? `${queue.current.id} "${queue.current.title}" (${queue.current.progress}%)` : '(无)'}`,
469
+ `待执行(${queue.queued.length},FIFO):`,
470
+ ...queue.queued.map(fmt),
471
+ `已完成(${queue.completed.length}):`,
472
+ ...queue.completed.map(fmt),
473
+ ].join('\n'),
474
+ }
475
+ }
476
+
477
+ case 'get_queue_overview': {
478
+ const overview = await ws.queueOverview()
479
+ const lines = overview.map(s =>
480
+ ` ${s.agentId} (${s.name}, role=${s.role}, state=${s.state}, current=${s.currentTaskId ?? '-'}${s.currentTaskTitle ? `「${s.currentTaskTitle}」` : ''}, progress=${s.currentTaskProgress ?? '-'}%, queued=${s.queuedCount}, completed=${s.completedCount})`,
481
+ )
482
+ return { text: `团队队列总览(${overview.length}):\n${lines.join('\n') || '(空)'}` }
483
+ }
484
+
485
+ case 'reassign_task': {
486
+ const taskId = args.task_id as string
487
+ const toAgentId = args.to_agent_id as string
488
+ const task = await ws.reassignTask(taskId, toAgentId)
489
+ return { text: `任务 ${taskId}("${task.title}")已调配 → ${toAgentId}(state=${task.state})` }
490
+ }
491
+
492
+ case 'update_task': {
493
+ const taskId = args.task_id as string
494
+ const title = args.title as string | undefined
495
+ const description = args.description as string | undefined
496
+ const task = await ws.updateTask(taskId, { title, description })
497
+ return { text: `任务 ${taskId} 已更新: "${task.title}"` }
498
+ }
499
+
500
+ case 'cancel_task': {
501
+ const taskId = args.task_id as string
502
+ // 守卫:goal/loop/pipeline 的 mode 父任务是用户的作业主任务,Agent 不得经工具取消
503
+ const target = await ws.getTask(taskId).catch(() => null)
504
+ if (target && extractTaskMode(target)) {
505
+ return {
506
+ text: `拒绝:任务 ${taskId} 是 mode 父任务(${target.title}),不能用 cancel_task 取消。若目标未达成 → dispatch_task 派发子任务补齐差距;若确认无法达成 → complete_task 并在交付中说明未达成原因。终止整个作业请由用户操作。`,
507
+ }
508
+ }
509
+ await ws.cancelTask(taskId)
510
+ return { text: `任务 ${taskId} 已取消并移出 assignee 队列` }
511
+ }
512
+
513
+ case 'list_team_agents': {
514
+ const agents = await ws.listAgents()
515
+ const text = agents.map(a =>
516
+ ` ${a.id} (${a.name}, role=${a.role}, harness=${a.harness})`,
517
+ ).join('\n')
518
+ return { text: `团队成员(${agents.length}):\n${text || '(空)'}` }
519
+ }
520
+
521
+ case 'get_task_details': {
522
+ const taskId = args.task_id as string
523
+ const task: WorkspaceTask = await ws.getTask(taskId)
524
+ const artifactText = task.artifacts.map(a =>
525
+ ` artifact ${a.artifactId}: ${a.parts.map(p => 'text' in p ? p.text.slice(0, 100) : '').join('; ')}`,
526
+ ).join('\n')
527
+ return {
528
+ text: `任务 ${task.id}\n 状态: ${task.state}\n 标题: ${task.title}\n 描述: ${task.description ?? '-'}\n 指派: ${task.assigneeId}\n 进度: ${task.progress}%\n 成果:\n${artifactText || ' (无)'}`,
529
+ }
530
+ }
531
+
532
+ case 'search_memory': {
533
+ const query = args.query as string
534
+ const scope = (args.scope as 'auto' | 'private' | 'shared' | undefined) ?? 'auto'
535
+ const snippets = await ws.recallMemory({ query, scope, limit: args.limit as number | undefined })
536
+ if (snippets.length === 0) {
537
+ return { text: `记忆检索无命中(query="${query}", scope=${scope})。可尝试更换关键词或放宽 scope。` }
538
+ }
539
+ const lines = snippets.map(s =>
540
+ ` [${s.source}·${s.kind}·score=${s.score}] ${s.title}\n ${s.content}`,
541
+ )
542
+ return { text: `记忆检索结果(${snippets.length} 条, scope=${scope}):\n${lines.join('\n')}` }
543
+ }
544
+
545
+ case 'save_memory': {
546
+ const title = args.title as string
547
+ const content = args.content as string
548
+ const scope = args.scope as 'private' | 'shared'
549
+ const saved = await ws.saveMemory({
550
+ title,
551
+ content,
552
+ importance: args.importance as number | undefined,
553
+ scope,
554
+ dedupKey: args.dedup_key as string | undefined,
555
+ })
556
+ const where = scope === 'shared' ? 'Channel 公共记忆(全员可检索)' : '本人私有记忆'
557
+ return { text: `已沉淀到${where}: "${title}"(dedupKey=${saved.dedupKey})` }
558
+ }
559
+
560
+ case 'create_team_agent': {
561
+ const name = args.name as string
562
+ const harness = args.harness as string | undefined
563
+ const systemPrompt = args.system_prompt as string | undefined
564
+ const reason = args.reason as string | undefined
565
+ const agent: AgentInfo = await ws.createTeamMember({
566
+ name,
567
+ harness,
568
+ config: systemPrompt ? { systemPromptPrefix: systemPrompt } : undefined,
569
+ reason,
570
+ })
571
+ return {
572
+ text: [
573
+ `团队成员已创建并加入 channel:`,
574
+ ` id: ${agent.id}`,
575
+ ` name: ${agent.name}(role=worker, harness=${agent.harness})`,
576
+ `新成员当前空闲,可立即 dispatch_task 指派任务;list_team_agents 可随时查看团队名册。`,
577
+ ].join('\n'),
578
+ }
579
+ }
580
+
581
+ case 'update_team_agent': {
582
+ const agentId = args.agent_id as string
583
+ const name = args.name as string | undefined
584
+ const systemPrompt = args.system_prompt as string | undefined
585
+ const enabled = args.enabled as boolean | undefined
586
+ const reason = args.reason as string | undefined
587
+ const agent = await ws.updateTeamMember(agentId, {
588
+ name,
589
+ config: systemPrompt !== undefined ? { systemPromptPrefix: systemPrompt } : undefined,
590
+ enabled: enabled === undefined ? undefined : (enabled ? 1 : 0),
591
+ reason,
592
+ })
593
+ return {
594
+ text: `团队成员 ${agentId} 已更新:name="${agent.name}"${enabled !== undefined ? `, enabled=${enabled ? 1 : 0}` : ''};运行时将按新配置重载(下次任务生效)。`,
595
+ }
596
+ }
597
+
598
+ case 'remove_team_agent': {
599
+ const agentId = args.agent_id as string
600
+ const reason = args.reason as string | undefined
601
+ const result = await ws.removeTeamMember(agentId, reason)
602
+ const recycleNote = result.recycledTasks.length > 0
603
+ ? `其 ${result.recycledTasks.length} 个在途任务已回收(排队任务重派给剩余最短队列成员;执行中任务转 FAILED 待调度重试)。`
604
+ : `该成员无在途任务。`
605
+ return { text: `团队成员 ${agentId} 已移除。${recycleNote}` }
606
+ }
607
+
608
+ case 'my_industrial_nodes': {
609
+ return toolMyIndustrialNodes(identity.agentId)
610
+ }
611
+ case 'dcw_control': {
612
+ return toolDcwControl(identity.agentId, args as { node_id?: string, value?: number | string, hypothesis?: string, task_id?: string })
613
+ }
614
+ case 'dcw_read': {
615
+ return toolDcwRead(identity.agentId, args as { node_id?: string })
616
+ }
617
+ case 'daq_query': {
618
+ return toolDaqQuery(identity.agentId, args as Parameters<typeof toolDaqQuery>[1])
619
+ }
620
+ case 'daq_frames': {
621
+ return toolDaqFrames(identity.agentId, args as Parameters<typeof toolDaqFrames>[1])
622
+ }
623
+ case 'dcw_judge': {
624
+ return toolDcwJudge(identity.agentId, args as { record_id?: string, verdict?: string, reason?: string })
625
+ }
626
+ case 'dcw_rollback': {
627
+ return toolDcwRollback(identity.agentId, args as { record_id?: string, node_id?: string, to?: string })
628
+ }
629
+ case 'dcw_journal': {
630
+ return toolDcwJournal(identity.agentId, args as { node_id?: string, recipe_id?: string, limit?: number | string })
631
+ }
632
+ case 'ops_log': {
633
+ return toolOpsLog(identity.agentId, args as Parameters<typeof toolOpsLog>[1])
634
+ }
635
+ case 'recipe_log': {
636
+ return toolRecipeLog(identity.agentId, args as Parameters<typeof toolRecipeLog>[1])
637
+ }
638
+ case 'line_context': {
639
+ return toolLineContext(identity.agentId, args as { line_id?: string })
640
+ }
641
+ case 'recipe_versions': {
642
+ return toolRecipeVersions(identity.agentId, args as { recipe_id?: string, limit?: number | string })
643
+ }
644
+ case 'recipe_update': {
645
+ return toolRecipeUpdate(identity.agentId, args as Parameters<typeof toolRecipeUpdate>[1])
646
+ }
647
+ case 'recipe_rollback': {
648
+ return toolRecipeRollback(identity.agentId, args as Parameters<typeof toolRecipeRollback>[1])
649
+ }
650
+ }
651
+ return { text: `未知工具: ${req.toolName}`, isError: true }
652
+ }
653
+ catch (err) {
654
+ return {
655
+ text: `工具执行异常(${req.toolName}): ${err instanceof Error ? err.message : String(err)}`,
656
+ isError: true,
657
+ }
658
+ }
659
+ }