agentworkshop 0.7.41 → 0.7.44

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 (265) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/B8uy9iRW.js +1 -0
  3. package/.output/public/_nuxt/BGTYYQH-.js +1 -0
  4. package/.output/public/_nuxt/BUmHEJ77.js +2 -0
  5. package/.output/public/_nuxt/BY7YrV_D.js +1 -0
  6. package/.output/public/_nuxt/Bqcgz95s.js +18 -0
  7. package/.output/public/_nuxt/C7UlqPeD.js +1 -0
  8. package/.output/public/_nuxt/CTmDn-YP.js +1 -0
  9. package/.output/public/_nuxt/Cs8pVeVG.js +1 -0
  10. package/.output/public/_nuxt/DKySmNPI.js +14 -0
  11. package/.output/public/_nuxt/Di0NY_ht.js +1 -0
  12. package/.output/public/_nuxt/Du9v6qGB.js +1 -0
  13. package/.output/public/_nuxt/{DGbd-UiG.js → EHlEMRii.js} +1 -1
  14. package/.output/public/_nuxt/Eke1fs-U.js +1 -0
  15. package/.output/public/_nuxt/{DeJykBFf.js → H0BcqZtT.js} +1 -1
  16. package/.output/public/_nuxt/{C4oW1HsE.js → HSYEkkce.js} +1 -1
  17. package/.output/public/_nuxt/{D_OTvQza.js → J9oU00D1.js} +1 -1
  18. package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +1 -0
  19. package/.output/public/_nuxt/SSyAPaHc.js +1 -0
  20. package/.output/public/_nuxt/TownView.li4EMQxK.css +1 -0
  21. package/.output/public/_nuxt/VdLk4jLE2.js +1 -0
  22. package/.output/public/_nuxt/_id_.BhsXoEuk.css +1 -0
  23. package/.output/public/_nuxt/_id_.DQyj-WKN.css +1 -0
  24. package/.output/public/_nuxt/{_wsId_.B4WiqkIS.css → _wsId_.CTx-BlfF.css} +1 -1
  25. package/.output/public/_nuxt/aShsv_rG.js +19 -0
  26. package/.output/public/_nuxt/{yF0aYMF1.js → aesgXKSr.js} +3 -3
  27. package/.output/public/_nuxt/agents.B21nUEUX.css +1 -0
  28. package/.output/public/_nuxt/aml.ekTjxFkf.css +1 -0
  29. package/.output/public/_nuxt/builds/latest.json +1 -1
  30. package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +1 -0
  31. package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
  32. package/.output/public/_nuxt/daq.BdVmoIfe.css +1 -0
  33. package/.output/public/_nuxt/dcw.DCgyzpWV.css +1 -0
  34. package/.output/public/_nuxt/{default.J32SgqFY.css → default.Bh3L2HU8.css} +1 -1
  35. package/.output/public/_nuxt/lHAxdSsj.js +1 -0
  36. package/.output/public/_nuxt/pages.j9rzRocr.css +1 -0
  37. package/.output/public/_nuxt/plugins.DhiZbHU8.css +1 -0
  38. package/.output/public/_nuxt/settings.BRx9rIDg.css +1 -0
  39. package/.output/public/_nuxt/teams.BLlOPB_Z.css +1 -0
  40. package/.output/public/_nuxt/tokens.De8xzszn.css +1 -0
  41. package/.output/public/_nuxt/workshop.D1jFNkrn.css +1 -0
  42. package/.output/server/chunks/_/nitro.mjs +748 -733
  43. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs → OmpTerminalPanel-CVvxpsF7.mjs} +15 -15
  44. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs.map → OmpTerminalPanel-CVvxpsF7.mjs.map} +1 -1
  45. package/.output/server/chunks/build/{TownView--otsnr_7.mjs → TownView-CJ6IsDCc.mjs} +226 -225
  46. package/.output/server/chunks/build/{TownView--otsnr_7.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
  47. package/.output/server/chunks/build/{_id_-Brv-JSvr.mjs → _id_-CVD_H0ir.mjs} +143 -142
  48. package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
  49. package/.output/server/chunks/build/{_id_-D9pl1OZy.mjs → _id_-CtxMb3qp.mjs} +41 -41
  50. package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
  51. package/.output/server/chunks/build/{_wsId_-BASfDf0s.mjs → _wsId_-DpY3T-md.mjs} +514 -444
  52. package/.output/server/chunks/build/{_wsId_-BASfDf0s.mjs.map → _wsId_-DpY3T-md.mjs.map} +1 -1
  53. package/.output/server/chunks/build/{agents-CLQqJF2y.mjs → agents-DsCkuXDV.mjs} +49 -46
  54. package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +1 -0
  55. package/.output/server/chunks/build/{aml-_7FdFL2p.mjs → aml-CT0xKGKI.mjs} +122 -105
  56. package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +1 -0
  57. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
  58. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
  59. package/.output/server/chunks/build/{channel-templates-p6SZNjlM.mjs → channel-templates-Dla9FbYa.mjs} +49 -37
  60. package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +1 -0
  61. package/.output/server/chunks/build/{daq-DkMvD8sg.mjs → daq-DWWSKGAV.mjs} +89 -88
  62. package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
  63. package/.output/server/chunks/build/{dcw-C1jgVJkO.mjs → dcw-CT8k17_E.mjs} +44 -43
  64. package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +1 -0
  65. package/.output/server/chunks/build/{default-BE4r0FZN.mjs → default-D3525UFX.mjs} +16 -15
  66. package/.output/server/chunks/build/{default-BE4r0FZN.mjs.map → default-D3525UFX.mjs.map} +1 -1
  67. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs → monitor-Dh2Kvaq5.mjs} +2 -2
  68. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs.map → monitor-Dh2Kvaq5.mjs.map} +1 -1
  69. package/.output/server/chunks/build/{pages-B0bUWnC6.mjs → pages-BHcSBM2D.mjs} +24 -18
  70. package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +1 -0
  71. package/.output/server/chunks/build/{plugins-lGlUr1lj.mjs → plugins-BNgIi61W.mjs} +23 -17
  72. package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +1 -0
  73. package/.output/server/chunks/build/{settings-Ddd1qx2Z.mjs → settings-ipYBDWdn.mjs} +45 -44
  74. package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +1 -0
  75. package/.output/server/chunks/build/{teams-MEDLZdMn.mjs → teams-5lx-CwFa.mjs} +47 -44
  76. package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +1 -0
  77. package/.output/server/chunks/build/{tokens-C7fTM5C2.mjs → tokens-q-IDT8Io.mjs} +73 -53
  78. package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +1 -0
  79. package/.output/server/chunks/build/{town-CoQdblbZ.mjs → town-QJWB5wng.mjs} +3 -2
  80. package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
  81. package/.output/server/chunks/build/{workshop-BL5bhfs0.mjs → workshop-BIlCj_ZP.mjs} +45 -45
  82. package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +1 -0
  83. package/.output/server/chunks/routes/renderer.mjs +1 -1
  84. package/.output/server/chunks/virtual/entry.mjs +637 -86
  85. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  86. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  87. package/.output/server/package.json +1 -1
  88. package/README-zh.md +13 -3
  89. package/README.md +13 -3
  90. package/app/components/AppHeader.vue +961 -959
  91. package/app/components/workshop/A2aRpcDebugger.vue +237 -237
  92. package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
  93. package/app/components/workshop/AgentLanesView.vue +998 -998
  94. package/app/components/workshop/ArtifactCard.vue +120 -120
  95. package/app/components/workshop/AssetLibrary.vue +452 -452
  96. package/app/components/workshop/ChannelSessionList.vue +812 -804
  97. package/app/components/workshop/CommandPalette.vue +269 -269
  98. package/app/components/workshop/Composer.vue +827 -827
  99. package/app/components/workshop/DeviceTwinPanel.vue +643 -643
  100. package/app/components/workshop/EventBlock.vue +602 -602
  101. package/app/components/workshop/FileSelectorModal.vue +188 -188
  102. package/app/components/workshop/InspectorPanel.vue +301 -301
  103. package/app/components/workshop/MemoryPanel.vue +364 -364
  104. package/app/components/workshop/MultiChannelView.vue +189 -189
  105. package/app/components/workshop/TaskBoardView.vue +686 -686
  106. package/app/components/workshop/TaskInspectorDrawer.vue +390 -390
  107. package/app/components/workshop/TranscriptTimeline.vue +601 -601
  108. package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
  109. package/app/components/workshop/blocks/ClusterError.vue +85 -85
  110. package/app/components/workshop/blocks/ClusterLife.vue +87 -87
  111. package/app/components/workshop/blocks/ClusterMember.vue +95 -93
  112. package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
  113. package/app/components/workshop/blocks/ClusterOther.vue +67 -67
  114. package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
  115. package/app/components/workshop/blocks/ClusterTool.vue +158 -158
  116. package/app/components/workshop/terminal/OmpTerminalPanel.vue +1011 -1011
  117. package/app/components/workshop/town/TownView.vue +56 -56
  118. package/app/middleware/locale-cookie.global.ts +22 -0
  119. package/app/pages/aml/index.vue +24 -2
  120. package/app/pages/daq/[id].vue +11 -11
  121. package/app/pages/daq/index.vue +2316 -2316
  122. package/app/pages/dcw/[id].vue +18 -19
  123. package/app/pages/dcw/index.vue +3 -3
  124. package/app/pages/index.vue +987 -981
  125. package/app/pages/plugins/index.vue +317 -311
  126. package/app/pages/settings.vue +1564 -1562
  127. package/app/pages/tokens.vue +15 -15
  128. package/app/pages/workshop/agents.vue +612 -609
  129. package/app/pages/workshop/channel-templates.vue +18 -11
  130. package/app/pages/workshop/index.vue +668 -668
  131. package/app/pages/workshop/teams.vue +743 -740
  132. package/app/pages/workshop/w/[wsId].vue +704 -704
  133. package/app/plugins/locale-restore.client.ts +16 -1
  134. package/app/utils/builtin-catalog.ts +104 -0
  135. package/config.yml +170 -170
  136. package/docs/full-test-plan.md +2 -3
  137. package/i18n/locales/en.ts +293 -35
  138. package/i18n/locales/zh-CN.ts +265 -7
  139. package/package.json +2 -2
  140. package/scripts/_dbg-perms-e2e.mjs +132 -109
  141. package/scripts/_demo-agentteam-capture.mjs +278 -0
  142. package/scripts/_e2e-ui-functional.mjs +371 -0
  143. package/scripts/_i18n-en-cjk-scan.mjs +28 -0
  144. package/scripts/_i18n-en-verify.mjs +107 -0
  145. package/scripts/_i18n-extra-checks.mjs +53 -0
  146. package/scripts/_i18n-scan-hardcoded.mjs +180 -0
  147. package/scripts/_i18n-verify-keys.mjs +50 -0
  148. package/scripts/_loop-biax-campaign.mjs +15 -9
  149. package/scripts/_sweep-button-contrast.mjs +87 -0
  150. package/scripts/ui/record-demo.mjs +557 -0
  151. package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
  152. package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
  153. package/server/services/workshop/daq/drivers.ts +11 -0
  154. package/.output/public/_nuxt/88GOowgO2.js +0 -1
  155. package/.output/public/_nuxt/BN16aRdQ.js +0 -1
  156. package/.output/public/_nuxt/BWVhZntO.js +0 -1
  157. package/.output/public/_nuxt/BXxH0UQE.js +0 -1
  158. package/.output/public/_nuxt/BsROmY89.js +0 -1
  159. package/.output/public/_nuxt/C0ZrS1KL.js +0 -19
  160. package/.output/public/_nuxt/C4s_niy-.js +0 -14
  161. package/.output/public/_nuxt/D6mbFA-a.js +0 -1
  162. package/.output/public/_nuxt/DS8qj8EL.js +0 -1
  163. package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
  164. package/.output/public/_nuxt/Dap5PmTL.js +0 -2
  165. package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
  166. package/.output/public/_nuxt/DvLxILQP.js +0 -1
  167. package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
  168. package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
  169. package/.output/public/_nuxt/_id_.D9ymtHB5.css +0 -1
  170. package/.output/public/_nuxt/_id_.jf-_8E_c.css +0 -1
  171. package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
  172. package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
  173. package/.output/public/_nuxt/builds/meta/78a295bb-2072-48a4-8b46-6d545fdae743.json +0 -1
  174. package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
  175. package/.output/public/_nuxt/daq.DjSgY94l.css +0 -1
  176. package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
  177. package/.output/public/_nuxt/eqq3wWQF.js +0 -18
  178. package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
  179. package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
  180. package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
  181. package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
  182. package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
  183. package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
  184. package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
  185. package/.output/server/chunks/build/_id_-Brv-JSvr.mjs.map +0 -1
  186. package/.output/server/chunks/build/_id_-D9pl1OZy.mjs.map +0 -1
  187. package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
  188. package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
  189. package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
  190. package/.output/server/chunks/build/daq-DkMvD8sg.mjs.map +0 -1
  191. package/.output/server/chunks/build/dcw-C1jgVJkO.mjs.map +0 -1
  192. package/.output/server/chunks/build/pages-B0bUWnC6.mjs.map +0 -1
  193. package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
  194. package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
  195. package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
  196. package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
  197. package/.output/server/chunks/build/town-CoQdblbZ.mjs.map +0 -1
  198. package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
  199. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
  200. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
  201. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
  202. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
  203. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
  204. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
  205. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
  206. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
  207. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
  208. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
  209. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
  210. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
  211. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
  212. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
  213. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
  214. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
  215. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
  216. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
  217. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
  218. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
  219. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
  220. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
  221. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
  222. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
  223. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
  224. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
  225. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
  226. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
  227. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
  228. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
  229. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
  230. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
  231. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
  232. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
  233. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
  234. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
  235. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
  236. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
  237. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
  238. package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
  239. package/scripts/_dbg-audit-all-pages.mjs +0 -43
  240. package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
  241. package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
  242. package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
  243. package/scripts/_dbg-live-line-e2e.mjs +0 -279
  244. package/scripts/_dbg-minimal-omp.mjs +0 -77
  245. package/scripts/_dbg-mode-nav.mjs +0 -173
  246. package/scripts/_dbg-real-scenario-timeline.log +0 -55
  247. package/scripts/_dbg-team-repro.mjs +0 -76
  248. package/scripts/_dbg-town-agents-probe.mjs +0 -60
  249. package/scripts/_dbg-visual-acceptance.mjs +0 -63
  250. package/scripts/_townview_style.css +0 -1185
  251. package/scripts/_townview_template.html +0 -1215
  252. package/server/data/agent-node-bindings.json +0 -554
  253. package/server/data/daq-templates.json +0 -16
  254. package/server/data/daqs.json +0 -7464
  255. package/server/data/dcw-lines.json +0 -1787
  256. package/server/data/dcw-products.json +0 -1626
  257. package/server/data/dcw-recipes.json +0 -4672
  258. package/server/data/dcw-rollback.json +0 -20754
  259. package/server/data/dcw-runs.json +0 -6354
  260. package/server/data/dcw-templates.json +0 -146
  261. package/server/data/dcw-writes.json +0 -15650
  262. package/server/data/dcws.json +0 -9033
  263. package/server/data/device-twins.json +0 -133
  264. package/server/data/line-runs.json +0 -322
  265. package/server/data/scene-layouts.json +0 -277
@@ -1,827 +1,827 @@
1
- <script setup lang="ts">
2
- /**
3
- * Composer(底部输入区,open-tag composer 声部 + HITL 人类控制面):
4
- * - 浮起输入卡(inset hairline + 柔和投影)+ 底部工具行 + 墨色药丸发送;
5
- * - 统一 @ 寻址(任务/消息两模式共用):@ 某成员 → 任务直发该成员(HITL 调度)/
6
- * 消息发往该成员;无 @ 缺省 lead(任务走 lead 调度,消息发 lead);
7
- * - 任务模式:POST /channels/:id/tasks(title+description+mode goal/loop/pipeline+assigneeId);
8
- * - 消息模式:POST /channels/:id/messages(toAgentId/priority/requireReply),
9
- * immediate = 实时注入(HITL steer:目标 busy 时注入运行中的回合);
10
- * - @提及自动补全(open-tag mention-menu 移植):输入 "@" 触发成员菜单,
11
- * ↑↓/Enter 选中 → 回填 @名字 并锁定目标 agent(Esc 关闭);
12
- * - 人类发送者 = 登录用户名(fromLabel):时间线以"用户章"渲染我方消息。
13
- */
14
- import { message } from 'ant-design-vue'
15
- import { useWorkshopApi } from '@/app/composables/workshop/useWorkshopApi'
16
- import { useComposerBus } from '@/app/composables/workshop/useComposerBus'
17
- import { useEntitiesStore } from '@/app/stores/workshop/entities'
18
- import { useUserStore } from '@/app/stores/workshop/user'
19
- import { agentHueColor } from '@/app/composables/workshop/useEventBlocks'
20
-
21
- const { t } = useI18n()
22
-
23
- const props = defineProps<{ channelId: string }>()
24
- const emit = defineEmits<{ (e: 'submitted'): void }>()
25
- const api = useWorkshopApi()
26
- const entities = useEntitiesStore()
27
- const userStore = useUserStore()
28
-
29
- const mode = ref<'task' | 'message'>('task')
30
- const input = ref('')
31
-
32
- // ===== 引用总线:块工具条「引用到输入框」→ 以 `> ` 前缀注入并聚焦 =====
33
- const { quoteText } = useComposerBus()
34
- const taEl = ref<HTMLTextAreaElement | null>(null)
35
- watch(quoteText, (t) => {
36
- if (!t) return
37
- const quoted = t.trim().split('\n').map(l => `> ${l}`).join('\n')
38
- input.value = input.value ? `${input.value}\n\n${quoted}\n\n` : `${quoted}\n\n`
39
- quoteText.value = null
40
- nextTick(() => taEl.value?.focus())
41
- })
42
- const taskMode = ref<'goal' | 'loop' | 'pipeline'>('goal')
43
- /** loop 间隔秒(留空由下方校验拦截) */
44
- const loopIntervalSeconds = ref<number | null>(60)
45
- /** 留空 = 不限次数 */
46
- const loopMaxIterations = ref<number | null>(null)
47
- /** antd InputNumber 模型用 undefined 表示空值;内部状态统一用 null 便于校验 */
48
- const loopIntervalModel = computed<number | undefined>({
49
- get: () => loopIntervalSeconds.value ?? undefined,
50
- set: (v) => { loopIntervalSeconds.value = v ?? null },
51
- })
52
- const loopMaxIterationsModel = computed<number | undefined>({
53
- get: () => loopMaxIterations.value ?? undefined,
54
- set: (v) => { loopMaxIterations.value = v ?? null },
55
- })
56
- const sendLoading = ref(false)
57
-
58
- const agents = computed(() => entities.agents[props.channelId] ?? [])
59
- const workersAndLead = computed(() => agents.value)
60
- /** 统一 HITL 目标(任务/消息共用):缺省 lead;@ 提及或下拉选择切换 */
61
- const leadAgentId = computed(() =>
62
- agents.value.find(a => a.role === 'lead')?.agentId ?? agents.value[0]?.agentId ?? '')
63
- const toAgentId = ref<string>('')
64
- watch([agents, leadAgentId], ([list]) => {
65
- if (list.length === 0) return
66
- // 目标未设置,或频道切换后原目标不在本频道 → 回落 lead
67
- if (!toAgentId.value || !list.some(a => a.agentId === toAgentId.value)) {
68
- toAgentId.value = leadAgentId.value || (list[0]?.agentId ?? '')
69
- }
70
- }, { immediate: true })
71
- const priority = ref<'task' | 'immediate'>('immediate')
72
- const requireReply = ref(false)
73
-
74
- /** 发送成功后目标回落缺省 lead(下一条不会静默发给上一位成员) */
75
- const resetTarget = (): void => {
76
- toAgentId.value = leadAgentId.value
77
- }
78
-
79
- // ===== @提及自动补全(任务/消息双模式通用) =====
80
- const mentionOpen = ref(false)
81
- const mentionQuery = ref('')
82
- const mentionHi = ref(0)
83
- /** 光标前未闭合的 "@词"(无空格断开才算进行中) */
84
- const detectMention = (): void => {
85
- const el = document.activeElement as HTMLTextAreaElement | null
86
- const text = el?.value ?? input.value
87
- const caret = el?.selectionStart ?? text.length
88
- const upto = text.slice(0, caret)
89
- const m = /(^|\s)@([^\s@]*)$/.exec(upto)
90
- if (!m) {
91
- mentionOpen.value = false
92
- return
93
- }
94
- mentionQuery.value = m[2] ?? ''
95
- mentionOpen.value = mentionCandidates.value.length > 0
96
- mentionHi.value = 0
97
- }
98
- const mentionCandidates = computed(() => {
99
- const q = mentionQuery.value.toLowerCase()
100
- return workersAndLead.value.filter(a => !q || a.name.toLowerCase().includes(q)).slice(0, 6)
101
- })
102
- const pickMention = (idx: number): void => {
103
- const a = mentionCandidates.value[idx]
104
- if (!a) return
105
- const el = document.activeElement as HTMLTextAreaElement | null
106
- const text = el?.value ?? input.value
107
- const caret = el?.selectionStart ?? text.length
108
- const upto = text.slice(0, caret)
109
- const m = /(^|\s)@([^\s@]*)$/.exec(upto)
110
- const cut = m ? caret - (m[2]?.length ?? 0) : caret
111
- const next = `${text.slice(0, cut)}${a.name} ${text.slice(caret)}`
112
- input.value = next
113
- toAgentId.value = a.agentId
114
- mentionOpen.value = false
115
- nextTick(() => {
116
- const pos = cut + a.name.length + 1
117
- el?.setSelectionRange(pos, pos)
118
- el?.focus()
119
- })
120
- }
121
- const onMentionKeydown = (ev: KeyboardEvent): boolean => {
122
- if (!mentionOpen.value) return false
123
- if (ev.key === 'ArrowDown') {
124
- ev.preventDefault()
125
- mentionHi.value = Math.min(mentionHi.value + 1, mentionCandidates.value.length - 1)
126
- return true
127
- }
128
- if (ev.key === 'ArrowUp') {
129
- ev.preventDefault()
130
- mentionHi.value = Math.max(mentionHi.value - 1, 0)
131
- return true
132
- }
133
- if (ev.key === 'Enter' || ev.key === 'Tab') {
134
- ev.preventDefault()
135
- pickMention(mentionHi.value)
136
- return true
137
- }
138
- if (ev.key === 'Escape') {
139
- mentionOpen.value = false
140
- return true
141
- }
142
- return false
143
- }
144
-
145
- /** 任务模式:首行=标题,其余=描述(与聊天输入习惯兼容) */
146
- const parseTitleDesc = (): { title: string, description?: string } => {
147
- const [first, ...rest] = input.value.trim().split('\n')
148
- return { title: (first ?? '').slice(0, 120), description: rest.join('\n').trim() || undefined }
149
- }
150
-
151
- const send = async (): Promise<void> => {
152
- const text = input.value.trim()
153
- if (!text) return
154
- sendLoading.value = true
155
- try {
156
- if (mode.value === 'task') {
157
- const { title, description } = parseTitleDesc()
158
- if (!title) {
159
- message.warning(t('composer.kqdhdlq014'))
160
- return
161
- }
162
- // loop 参数(先校验后使用;校验通过后下方 modeConfig 才可能引用)
163
- const intervalSeconds = loopIntervalSeconds.value
164
- const maxIterations = loopMaxIterations.value
165
- if (taskMode.value === 'loop') {
166
- if (intervalSeconds === null || !Number.isFinite(intervalSeconds) || intervalSeconds < 1 || intervalSeconds > 86400) {
167
- message.warning('loop 间隔需设置为 1 到 86400 秒')
168
- return
169
- }
170
- if (maxIterations !== null && (!Number.isInteger(maxIterations) || maxIterations < 1 || maxIterations > 10000)) {
171
- message.warning(t('composer.k1l36qf9015'))
172
- return
173
- }
174
- }
175
- await api.submitTask(props.channelId, {
176
- title,
177
- description,
178
- mode: taskMode.value,
179
- ...(taskMode.value === 'loop'
180
- ? {
181
- modeConfig: {
182
- intervalMs: Math.round(intervalSeconds! * 1000),
183
- ...(maxIterations !== null
184
- ? { maxIterations: Math.floor(maxIterations) }
185
- : {}),
186
- },
187
- }
188
- : {}),
189
- // HITL:@ 指定成员 → 任务直发(人类此刻即调度者);缺省 lead 自动调度
190
- assigneeId: toAgentId.value || undefined,
191
- // 人类发送者登录名:assign 消息以此盖章,时间线渲染"用户章"
192
- fromLabel: userStore.user?.name ?? undefined,
193
- // 任务全文随 assign 消息进时间线(人类提交内容在聊天界面可见)
194
- parts: [{ text: input.value.trim() }],
195
- })
196
- const routedTo = targetName.value || 'lead'
197
- message.success(t('composer.k1d2idgj035', { p0: routedTo, p1: taskMode.value }))
198
- }
199
- else {
200
- if (!toAgentId.value) {
201
- message.warning(t('composer.k1bw65is016'))
202
- return
203
- }
204
- await api.injectMessage(props.channelId, {
205
- toAgentId: toAgentId.value,
206
- text,
207
- priority: priority.value,
208
- requireReply: requireReply.value,
209
- // 人类发送者名:时间线 a2a.message 以"@你 → @目标"聊天行呈现
210
- fromLabel: userStore.user?.name ?? undefined,
211
- })
212
- message.success(t('composer.k1wk4bf036', { p0: targetName.value, p1: priority.value === 'immediate' ? t('composer.k1cxjc4m034') : t('composer.k40g8m009') }))
213
- }
214
- input.value = ''
215
- resetTarget()
216
- emit('submitted')
217
- }
218
- catch (e) {
219
- message.error(apiErrorMessage(e))
220
- }
221
- finally {
222
- sendLoading.value = false
223
- }
224
- }
225
-
226
- const onKeydown = (ev: KeyboardEvent): void => {
227
- // IME 组合中不拦截按键(中文输入法选词确认的 Enter/方向键不做快捷处理)
228
- if (ev.isComposing) return
229
- if (onMentionKeydown(ev)) return
230
- if (ev.key === 'Enter' && (ev.ctrlKey || ev.metaKey)) {
231
- ev.preventDefault()
232
- void send()
233
- }
234
- }
235
-
236
- // ===== 输入框自适应高度(open-tag composer auto-resize) =====
237
- watch(input, async () => {
238
- await nextTick()
239
- const el = taEl.value
240
- if (!el) return
241
- el.style.height = 'auto'
242
- el.style.height = `${Math.min(el.scrollHeight, 160)}px`
243
- })
244
-
245
- /** 当前目标成员(状态行 chip 与提及回显) */
246
- const targetAgent = computed(() => workersAndLead.value.find(a => a.agentId === toAgentId.value))
247
- const targetName = computed(() => targetAgent.value?.name ?? '')
248
- /** 目标是否为缺省路由(lead)——chip 提示"@ 可切换" */
249
- const isDefaultLead = computed(() => !!leadAgentId.value && toAgentId.value === leadAgentId.value)
250
-
251
- /**
252
- * 可达性提示(open-tag reach hint 移植 + HITL 送达语义):直接回答
253
- * "我发的东西对方以何种方式收到"。任务模式 = 排队进信箱;消息模式即时 =
254
- * busy 时实时注入运行中的回合(HITL steer)。
255
- */
256
- type Reach = { tone: 'ok' | 'info' | 'warn', text: string, title: string }
257
- const reachHint = computed<Reach | null>(() => {
258
- if (!targetAgent.value) return null
259
- const st = targetAgent.value.state
260
- if (mode.value === 'task') {
261
- if (st === 'busy') {
262
- return {
263
- tone: 'info',
264
- text: t('composer.k1ww0ftm017'),
265
- title: t('composer.k9eq10w018'),
266
- }
267
- }
268
- if (st === 'stopped') {
269
- return {
270
- tone: 'warn',
271
- text: t('composer.kbhz1rp019'),
272
- title: t('composer.kophjf5020'),
273
- }
274
- }
275
- return { tone: 'ok', text: t('composer.k4qgtiw021'), title: t('composer.kfmjwas022') }
276
- }
277
- if (st === 'busy') {
278
- return {
279
- tone: 'info',
280
- text: priority.value === 'immediate' ? t('composer.kwg68t0023') : t('composer.k7w1etd024'),
281
- title: t('composer.k9m6p7u025'),
282
- }
283
- }
284
- if (st === 'stopped') {
285
- return {
286
- tone: 'warn',
287
- text: t('composer.kj1ngpo026'),
288
- title: t('composer.kmf7lcc027'),
289
- }
290
- }
291
- return { tone: 'ok', text: t('composer.k1tet5df028'), title: t('composer.kn600in029') }
292
- })
293
-
294
- const placeholder = computed(() =>
295
- mode.value === 'task'
296
- ? t('composer.k2h5jwc030')
297
- : t('composer.k15mwzt7031'),
298
- )
299
- </script>
300
-
301
- <template>
302
- <div class="composer">
303
- <div class="composer-box">
304
- <!-- @提及菜单(输入卡上方) -->
305
- <div
306
- v-if="mentionOpen"
307
- class="mention-menu"
308
- >
309
- <div class="mention-title">
310
- {{ $t('composer.k1bxvc46004') }}
311
- </div>
312
- <button
313
- v-for="(a, i) in mentionCandidates"
314
- :key="a.agentId"
315
- type="button"
316
- class="mention-opt"
317
- :class="{ sel: i === mentionHi }"
318
- @mousedown.prevent="pickMention(i)"
319
- @mouseenter="mentionHi = i"
320
- >
321
- <span
322
- class="aw-avatar is-agent mention-ava"
323
- :style="{ '--av': agentHueColor(a.agentId) }"
324
- >{{ a.name.charAt(0).toUpperCase() }}</span>
325
- <span class="mention-name">@{{ a.name }}</span>
326
- <span class="mention-role">{{ a.role }}</span>
327
- <span
328
- class="mention-state"
329
- :class="a.state"
330
- :title="a.state"
331
- />
332
- </button>
333
- </div>
334
-
335
- <div class="composer-status-chip">
336
- <template v-if="mode === 'task'">
337
- <span class="chip-key">{{ $t('composer.k3wcox005') }}</span>
338
- <span>{{ taskMode }}</span>
339
- <span v-if="taskMode === 'loop'">
340
- {{ $t('composer.k49kr1011') }} {{ loopIntervalSeconds ?? '-' }}s
341
- </span>
342
- <span
343
- class="chip-target"
344
- :title="isDefaultLead ? '未 @ 指定:任务自动路由 lead 调度;@ 某成员可直发' : $t('composer.kl3604i033', { p0: targetName })"
345
- >→ {{ targetName ? `@${targetName}` : 'lead' }}{{ isDefaultLead ? $t('composer.k2z7yuw012') : $t('composer.k2z0fsx032') }}</span>
346
- <!-- HITL 送达语义提示 -->
347
- <span
348
- v-if="reachHint"
349
- class="reach-chip"
350
- :data-tone="reachHint.tone"
351
- :title="reachHint.title"
352
- >
353
- <span
354
- class="reach-dot"
355
- aria-hidden="true"
356
- />{{ reachHint.text }}
357
- </span>
358
- <span class="chip-hint">{{ $t('composer.k17u6q77006') }}</span>
359
- </template>
360
- <template v-else>
361
- <span class="chip-key">{{ $t('composer.k41ykc007') }}</span>
362
- <span v-if="targetName">@{{ targetName }}{{ isDefaultLead ? $t('composer.k2z7yuw012') : '' }}</span>
363
- <span>{{ priority === 'immediate' ? $t('composer.k1bosqfv013') : $t('composer.k40g8m009') }}</span>
364
- <!-- 可达性提示(open-tag reach hint):对方能否收到、将以何种方式送达 -->
365
- <span
366
- v-if="reachHint"
367
- class="reach-chip"
368
- :data-tone="reachHint.tone"
369
- :title="reachHint.title"
370
- >
371
- <span
372
- class="reach-dot"
373
- aria-hidden="true"
374
- />{{ reachHint.text }}
375
- </span>
376
- <span class="chip-hint">输入 @ 提及成员 · ⌘/Ctrl+Enter 发送</span>
377
- </template>
378
- </div>
379
-
380
- <textarea
381
- ref="taEl"
382
- v-model="input"
383
- class="composer-input"
384
- rows="1"
385
- :placeholder="placeholder"
386
- @input="detectMention"
387
- @keydown="onKeydown"
388
- @blur="mentionOpen = false"
389
- />
390
-
391
- <div class="composer-bar">
392
- <div class="cb-left">
393
- <div class="aw-seg">
394
- <button
395
- type="button"
396
- :class="{ on: mode === 'task' }"
397
- @click="mode = 'task'"
398
- >
399
- {{ $t('composer.k3wcox005') }}
400
- </button>
401
- <button
402
- type="button"
403
- :class="{ on: mode === 'message' }"
404
- @click="mode = 'message'"
405
- >
406
- {{ $t('composer.k41ykc007') }}
407
- </button>
408
- </div>
409
-
410
- <!-- HITL 目标选择(任务/消息共用):缺省 lead;@ 提及或下拉切换直发对象 -->
411
- <a-select
412
- v-model:value="toAgentId"
413
- size="small"
414
- class="target"
415
- :options="workersAndLead.map(a => ({ value: a.agentId, label: `@ ${a.name}${a.role === 'lead' ? ' · lead' : ''}` }))"
416
- />
417
-
418
- <template v-if="mode === 'task'">
419
- <div class="aw-seg">
420
- <button
421
- v-for="m in ['goal', 'loop', 'pipeline'] as const"
422
- :key="m"
423
- type="button"
424
- :class="{ on: taskMode === m }"
425
- @click="taskMode = m"
426
- >
427
- {{ m }}
428
- </button>
429
- </div>
430
- <template v-if="taskMode === 'loop'">
431
- <a-input-number
432
- v-model:value="loopIntervalModel"
433
- size="small"
434
- :min="1"
435
- :max="86400"
436
- :step="1"
437
- :precision="0"
438
- addon-after="秒"
439
- class="loop-number"
440
- />
441
- <a-input-number
442
- v-model:value="loopMaxIterationsModel"
443
- size="small"
444
- :min="1"
445
- :max="10000"
446
- :step="1"
447
- :precision="0"
448
- :placeholder="$t('composer.k1b38y2b001')"
449
- class="loop-number iterations"
450
- />
451
- </template>
452
- </template>
453
- <template v-else>
454
- <div class="aw-seg">
455
- <button
456
- type="button"
457
- :class="{ on: priority === 'immediate' }"
458
- :title="$t('composer.k1jqqvhe002')"
459
- @click="priority = 'immediate'"
460
- >
461
- {{ $t('composer.k3x9n2008') }}
462
- </button>
463
- <button
464
- type="button"
465
- :class="{ on: priority === 'task' }"
466
- @click="priority = 'task'"
467
- >
468
- {{ $t('composer.k40g8m009') }}
469
- </button>
470
- </div>
471
- <button
472
- type="button"
473
- class="chip-toggle"
474
- :class="{ on: requireReply }"
475
- :title="$t('composer.k195594v003')"
476
- @click="requireReply = !requireReply"
477
- >
478
- <span class="i-tabler-mail-forward" />
479
- {{ $t('composer.k3xv7u010') }}
480
- </button>
481
- </template>
482
- </div>
483
-
484
- <div class="cb-right">
485
- <button
486
- type="button"
487
- class="send-btn im"
488
- :disabled="sendLoading || !input.trim()"
489
- title="发送(⌘/Ctrl+Enter)"
490
- @click="send"
491
- >
492
- <span class="i-tabler-send im-nudge-up" />
493
- </button>
494
- </div>
495
- </div>
496
- </div>
497
- </div>
498
- </template>
499
-
500
- <style scoped>
501
- .composer {
502
- position: relative;
503
- padding: 10px 16px 12px;
504
- overflow: hidden;
505
- background: var(--paper-raised);
506
- border-top: 1px solid var(--line);
507
- }
508
-
509
- /* 输入卡(Slack 声部):纯白面 + 发丝线 + 12px 圆角;聚焦时墨色内缘加深。
510
- * 去掉了光斑/水印装饰层 — 输入区是作业面,不是品牌海报。 */
511
- .composer-box {
512
- position: relative;
513
- max-width: 900px;
514
- padding: 8px 12px 8px;
515
- margin: 0 auto;
516
- background: var(--paper-raised);
517
- border: 1px solid var(--line);
518
- border-radius: var(--radius-panel);
519
- box-shadow: inset 0 1px 0 color-mix(in srgb, white 30%, transparent), var(--bubble-shadow);
520
- transition: border-color var(--transition-slow), box-shadow var(--transition-slow);
521
- }
522
- .composer-box:focus-within {
523
- border-color: var(--ink-fainter);
524
- box-shadow: inset 0 0 0 0.5px color-mix(in srgb, var(--ink) 18%, transparent), 0 6px 22px rgb(12 10 9 / 5%);
525
- }
526
-
527
- /* 状态行:轻 chip 说明当前模式参数 */
528
- .composer-status-chip {
529
- display: flex;
530
- gap: 6px;
531
- align-items: center;
532
- width: max-content;
533
- max-width: 100%;
534
- margin: 0 0 4px 2px;
535
- padding: 2px 8px;
536
- font-size: 11.5px;
537
- color: var(--ink-faint);
538
- background: var(--paper-deep);
539
- border-radius: var(--radius-chip);
540
- }
541
- .chip-key {
542
- font-weight: 600;
543
- color: var(--ink-soft);
544
- }
545
- .chip-target {
546
- font-weight: 600;
547
- color: var(--ink);
548
- }
549
- .chip-hint {
550
- margin-left: auto;
551
- padding-left: 8px;
552
- color: var(--ink-faint);
553
- }
554
-
555
- /* 可达性提示 chip:ok 绿 / info 蓝 / warn 琥珀 —— 状态不只靠颜色(附文字) */
556
- .reach-chip {
557
- display: inline-flex;
558
- gap: 5px;
559
- align-items: center;
560
- padding: 0 6px;
561
- font-size: 10.5px;
562
- border-radius: var(--radius-chip);
563
- }
564
- .reach-chip .reach-dot {
565
- width: 5px;
566
- height: 5px;
567
- border-radius: 50%;
568
- background: currentColor;
569
- }
570
- .reach-chip[data-tone='ok'] { color: var(--tone-success-dot); background: color-mix(in srgb, var(--tone-success-dot) 10%, transparent); }
571
- .reach-chip[data-tone='info'] { color: var(--tone-info-dot); background: color-mix(in srgb, var(--tone-info-dot) 10%, transparent); }
572
- .reach-chip[data-tone='warn'] { color: var(--tone-warning-dot); background: color-mix(in srgb, var(--tone-warning-dot) 13%, transparent); }
573
-
574
- .composer-input {
575
- display: block;
576
- width: 100%;
577
- max-height: 160px;
578
- min-height: 26px;
579
- padding: 3px 2px;
580
- overflow-y: auto;
581
- font-family: var(--font-body);
582
- font-size: 13.5px;
583
- line-height: 1.5;
584
- color: var(--ink);
585
- resize: none;
586
- background: transparent;
587
- border: 0;
588
- outline: none;
589
- }
590
- .composer-input::placeholder { color: var(--ink-faint); }
591
-
592
- .composer-bar {
593
- display: flex;
594
- align-items: center;
595
- justify-content: space-between;
596
- gap: 12px;
597
- min-height: 30px;
598
- margin-top: 6px;
599
- }
600
- .cb-left {
601
- display: flex;
602
- flex-wrap: wrap;
603
- gap: 6px;
604
- align-items: center;
605
- }
606
- .cb-right {
607
- display: flex;
608
- flex: none;
609
- gap: 8px;
610
- align-items: center;
611
- }
612
-
613
- /* 回执切换 chip */
614
- .chip-toggle {
615
- display: inline-flex;
616
- gap: 5px;
617
- align-items: center;
618
- padding: 3px 10px;
619
- font-family: var(--font-body);
620
- font-size: 12px;
621
- color: var(--ink-faint);
622
- cursor: pointer;
623
- background: var(--paper-raised);
624
- border: 1px solid var(--line-strong);
625
- border-radius: var(--radius-pill);
626
- transition: color var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast);
627
- }
628
- .chip-toggle:hover { color: var(--ink); }
629
- .chip-toggle.on {
630
- font-weight: 600;
631
- color: var(--ink);
632
- background: var(--paper-deep);
633
- border-color: var(--ink);
634
- }
635
-
636
- /* 发送:墨色药丸圆钮 */
637
- .send-btn {
638
- display: flex;
639
- align-items: center;
640
- justify-content: center;
641
- width: 32px;
642
- height: 32px;
643
- padding: 0;
644
- font-size: 15px;
645
- color: var(--on-accent);
646
- cursor: pointer;
647
- background: var(--accent);
648
- border: 0;
649
- border-radius: var(--radius-pill);
650
- transition: background var(--transition-fast), transform var(--transition-fast), opacity var(--transition-fast);
651
- }
652
- .send-btn:hover:not(:disabled) { background: var(--accent-strong); }
653
- .send-btn:active:not(:disabled) { transform: scale(0.96); }
654
- .send-btn:disabled { opacity: 0.3; cursor: default; }
655
-
656
- .loop-number { width: 104px; }
657
- .loop-number.iterations { width: 104px; }
658
- .target { width: 138px; }
659
-
660
- /* @提及菜单(open-tag mention-menu) */
661
- .mention-menu {
662
- position: absolute;
663
- right: 12px;
664
- bottom: 100%;
665
- left: 12px;
666
- z-index: 20;
667
- max-height: 264px;
668
- margin-bottom: 8px;
669
- overflow: auto;
670
- background: var(--paper-raised);
671
- border: 1px solid var(--line-strong);
672
- border-radius: var(--radius-panel);
673
- box-shadow: var(--shadow-float);
674
- }
675
- .mention-opt {
676
- display: flex;
677
- gap: 9px;
678
- align-items: center;
679
- width: 100%;
680
- padding: 7px 12px;
681
- font-family: var(--font-body);
682
- font-size: 13px;
683
- color: var(--ink-soft);
684
- text-align: left;
685
- cursor: pointer;
686
- background: transparent;
687
- border: 0;
688
- }
689
- .mention-opt:hover,
690
- .mention-opt.sel { background: var(--paper-deep); }
691
- .mention-ava { width: 20px; height: 20px; font-size: 10px; }
692
- .mention-name {
693
- flex: 1;
694
- min-width: 0;
695
- overflow: hidden;
696
- text-overflow: ellipsis;
697
- white-space: nowrap;
698
- color: var(--ink);
699
- }
700
- .mention-role {
701
- flex: none;
702
- font-size: 10.5px;
703
- color: var(--ink-faint);
704
- text-transform: uppercase;
705
- letter-spacing: 0.05em;
706
- }
707
-
708
- .mention-title {
709
- padding: 5px 12px 4px;
710
- font-size: 10px;
711
- font-weight: 600;
712
- letter-spacing: 0.06em;
713
- text-transform: uppercase;
714
- color: var(--ink-faint);
715
- }
716
-
717
- /* 成员状态点:busy = 暖橙脉冲,idle = 静灰 */
718
- .mention-state {
719
- flex: none;
720
- width: 7px;
721
- height: 7px;
722
- background: var(--ink-fainter);
723
- border-radius: 50%;
724
- opacity: 0.7;
725
- }
726
-
727
- .mention-state.busy {
728
- background: var(--tone-live-dot);
729
- opacity: 1;
730
- }
731
-
732
- /* ── 窄屏(≤1023):输入区是手持设备的"主操作面",键盘弹出时它必须还在 ──
733
- 桌面工具栏是「一行左簇右钮」;390px 下这行会折成一列竖排字,
734
- 所以窄屏改为「模式行 → 目标行 → 发送行」三段堆叠,发送占满一行且 ≥44px。 */
735
- @media (max-width: 1023.98px) {
736
- .composer {
737
- padding: 8px 10px calc(10px + env(safe-area-inset-bottom));
738
- }
739
-
740
- .composer-box {
741
- padding: 8px 10px;
742
- }
743
-
744
- /* 状态 chip:桌面宽度自适应内容,窄屏必须允许折行,否则顶破输入卡 */
745
- .composer-status-chip {
746
- width: auto;
747
- flex-wrap: wrap;
748
- row-gap: 2px;
749
- font-size: 11.5px;
750
- line-height: 1.45;
751
- }
752
-
753
- .chip-hint {
754
- flex: 1 1 100%;
755
- padding-left: 0;
756
- margin-left: 0;
757
- }
758
-
759
- .reach-chip,
760
- .mention-role,
761
- .mention-title {
762
- font-size: 11.5px;
763
- }
764
-
765
- /* 16px 是 iOS 不缩放输入框的下限(小于它聚焦时整页被放大) */
766
- .composer-input {
767
- min-height: 42px;
768
- max-height: 30dvh;
769
- font-size: 16px;
770
- }
771
-
772
- .composer-bar {
773
- flex-wrap: wrap;
774
- gap: 8px;
775
- margin-top: 8px;
776
- }
777
-
778
- .cb-left {
779
- flex: 1 1 100%;
780
- row-gap: 8px;
781
- }
782
-
783
- .cb-right {
784
- flex: 1 1 100%;
785
- }
786
-
787
- /* 主操作:整行墨色药丸(触摸目标 44px) */
788
- .send-btn {
789
- width: 100%;
790
- height: 44px;
791
- font-size: 18px;
792
- }
793
-
794
- .aw-seg button {
795
- min-height: 40px;
796
- padding: 0 12px;
797
- }
798
-
799
- .chip-toggle {
800
- min-height: 40px;
801
- }
802
-
803
- .target {
804
- flex: 1 1 130px;
805
- width: auto;
806
- }
807
-
808
- .composer-bar :deep(.ant-select-selector) {
809
- min-height: 40px;
810
- align-items: center;
811
- }
812
-
813
- .loop-number,
814
- .loop-number.iterations {
815
- flex: 1 1 96px;
816
- width: auto;
817
- }
818
-
819
- .composer-bar :deep(.ant-input-number) {
820
- min-height: 40px;
821
- }
822
-
823
- .mention-opt {
824
- min-height: 44px;
825
- }
826
- }
827
- </style>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Composer(底部输入区,open-tag composer 声部 + HITL 人类控制面):
4
+ * - 浮起输入卡(inset hairline + 柔和投影)+ 底部工具行 + 墨色药丸发送;
5
+ * - 统一 @ 寻址(任务/消息两模式共用):@ 某成员 → 任务直发该成员(HITL 调度)/
6
+ * 消息发往该成员;无 @ 缺省 lead(任务走 lead 调度,消息发 lead);
7
+ * - 任务模式:POST /channels/:id/tasks(title+description+mode goal/loop/pipeline+assigneeId);
8
+ * - 消息模式:POST /channels/:id/messages(toAgentId/priority/requireReply),
9
+ * immediate = 实时注入(HITL steer:目标 busy 时注入运行中的回合);
10
+ * - @提及自动补全(open-tag mention-menu 移植):输入 "@" 触发成员菜单,
11
+ * ↑↓/Enter 选中 → 回填 @名字 并锁定目标 agent(Esc 关闭);
12
+ * - 人类发送者 = 登录用户名(fromLabel):时间线以"用户章"渲染我方消息。
13
+ */
14
+ import { message } from 'ant-design-vue'
15
+ import { useWorkshopApi } from '@/app/composables/workshop/useWorkshopApi'
16
+ import { useComposerBus } from '@/app/composables/workshop/useComposerBus'
17
+ import { useEntitiesStore } from '@/app/stores/workshop/entities'
18
+ import { useUserStore } from '@/app/stores/workshop/user'
19
+ import { agentHueColor } from '@/app/composables/workshop/useEventBlocks'
20
+
21
+ const { t } = useI18n()
22
+
23
+ const props = defineProps<{ channelId: string }>()
24
+ const emit = defineEmits<{ (e: 'submitted'): void }>()
25
+ const api = useWorkshopApi()
26
+ const entities = useEntitiesStore()
27
+ const userStore = useUserStore()
28
+
29
+ const mode = ref<'task' | 'message'>('task')
30
+ const input = ref('')
31
+
32
+ // ===== 引用总线:块工具条「引用到输入框」→ 以 `> ` 前缀注入并聚焦 =====
33
+ const { quoteText } = useComposerBus()
34
+ const taEl = ref<HTMLTextAreaElement | null>(null)
35
+ watch(quoteText, (t) => {
36
+ if (!t) return
37
+ const quoted = t.trim().split('\n').map(l => `> ${l}`).join('\n')
38
+ input.value = input.value ? `${input.value}\n\n${quoted}\n\n` : `${quoted}\n\n`
39
+ quoteText.value = null
40
+ nextTick(() => taEl.value?.focus())
41
+ })
42
+ const taskMode = ref<'goal' | 'loop' | 'pipeline'>('goal')
43
+ /** loop 间隔秒(留空由下方校验拦截) */
44
+ const loopIntervalSeconds = ref<number | null>(60)
45
+ /** 留空 = 不限次数 */
46
+ const loopMaxIterations = ref<number | null>(null)
47
+ /** antd InputNumber 模型用 undefined 表示空值;内部状态统一用 null 便于校验 */
48
+ const loopIntervalModel = computed<number | undefined>({
49
+ get: () => loopIntervalSeconds.value ?? undefined,
50
+ set: (v) => { loopIntervalSeconds.value = v ?? null },
51
+ })
52
+ const loopMaxIterationsModel = computed<number | undefined>({
53
+ get: () => loopMaxIterations.value ?? undefined,
54
+ set: (v) => { loopMaxIterations.value = v ?? null },
55
+ })
56
+ const sendLoading = ref(false)
57
+
58
+ const agents = computed(() => entities.agents[props.channelId] ?? [])
59
+ const workersAndLead = computed(() => agents.value)
60
+ /** 统一 HITL 目标(任务/消息共用):缺省 lead;@ 提及或下拉选择切换 */
61
+ const leadAgentId = computed(() =>
62
+ agents.value.find(a => a.role === 'lead')?.agentId ?? agents.value[0]?.agentId ?? '')
63
+ const toAgentId = ref<string>('')
64
+ watch([agents, leadAgentId], ([list]) => {
65
+ if (list.length === 0) return
66
+ // 目标未设置,或频道切换后原目标不在本频道 → 回落 lead
67
+ if (!toAgentId.value || !list.some(a => a.agentId === toAgentId.value)) {
68
+ toAgentId.value = leadAgentId.value || (list[0]?.agentId ?? '')
69
+ }
70
+ }, { immediate: true })
71
+ const priority = ref<'task' | 'immediate'>('immediate')
72
+ const requireReply = ref(false)
73
+
74
+ /** 发送成功后目标回落缺省 lead(下一条不会静默发给上一位成员) */
75
+ const resetTarget = (): void => {
76
+ toAgentId.value = leadAgentId.value
77
+ }
78
+
79
+ // ===== @提及自动补全(任务/消息双模式通用) =====
80
+ const mentionOpen = ref(false)
81
+ const mentionQuery = ref('')
82
+ const mentionHi = ref(0)
83
+ /** 光标前未闭合的 "@词"(无空格断开才算进行中) */
84
+ const detectMention = (): void => {
85
+ const el = document.activeElement as HTMLTextAreaElement | null
86
+ const text = el?.value ?? input.value
87
+ const caret = el?.selectionStart ?? text.length
88
+ const upto = text.slice(0, caret)
89
+ const m = /(^|\s)@([^\s@]*)$/.exec(upto)
90
+ if (!m) {
91
+ mentionOpen.value = false
92
+ return
93
+ }
94
+ mentionQuery.value = m[2] ?? ''
95
+ mentionOpen.value = mentionCandidates.value.length > 0
96
+ mentionHi.value = 0
97
+ }
98
+ const mentionCandidates = computed(() => {
99
+ const q = mentionQuery.value.toLowerCase()
100
+ return workersAndLead.value.filter(a => !q || a.name.toLowerCase().includes(q)).slice(0, 6)
101
+ })
102
+ const pickMention = (idx: number): void => {
103
+ const a = mentionCandidates.value[idx]
104
+ if (!a) return
105
+ const el = document.activeElement as HTMLTextAreaElement | null
106
+ const text = el?.value ?? input.value
107
+ const caret = el?.selectionStart ?? text.length
108
+ const upto = text.slice(0, caret)
109
+ const m = /(^|\s)@([^\s@]*)$/.exec(upto)
110
+ const cut = m ? caret - (m[2]?.length ?? 0) : caret
111
+ const next = `${text.slice(0, cut)}${a.name} ${text.slice(caret)}`
112
+ input.value = next
113
+ toAgentId.value = a.agentId
114
+ mentionOpen.value = false
115
+ nextTick(() => {
116
+ const pos = cut + a.name.length + 1
117
+ el?.setSelectionRange(pos, pos)
118
+ el?.focus()
119
+ })
120
+ }
121
+ const onMentionKeydown = (ev: KeyboardEvent): boolean => {
122
+ if (!mentionOpen.value) return false
123
+ if (ev.key === 'ArrowDown') {
124
+ ev.preventDefault()
125
+ mentionHi.value = Math.min(mentionHi.value + 1, mentionCandidates.value.length - 1)
126
+ return true
127
+ }
128
+ if (ev.key === 'ArrowUp') {
129
+ ev.preventDefault()
130
+ mentionHi.value = Math.max(mentionHi.value - 1, 0)
131
+ return true
132
+ }
133
+ if (ev.key === 'Enter' || ev.key === 'Tab') {
134
+ ev.preventDefault()
135
+ pickMention(mentionHi.value)
136
+ return true
137
+ }
138
+ if (ev.key === 'Escape') {
139
+ mentionOpen.value = false
140
+ return true
141
+ }
142
+ return false
143
+ }
144
+
145
+ /** 任务模式:首行=标题,其余=描述(与聊天输入习惯兼容) */
146
+ const parseTitleDesc = (): { title: string, description?: string } => {
147
+ const [first, ...rest] = input.value.trim().split('\n')
148
+ return { title: (first ?? '').slice(0, 120), description: rest.join('\n').trim() || undefined }
149
+ }
150
+
151
+ const send = async (): Promise<void> => {
152
+ const text = input.value.trim()
153
+ if (!text) return
154
+ sendLoading.value = true
155
+ try {
156
+ if (mode.value === 'task') {
157
+ const { title, description } = parseTitleDesc()
158
+ if (!title) {
159
+ message.warning(t('composer.kqdhdlq014'))
160
+ return
161
+ }
162
+ // loop 参数(先校验后使用;校验通过后下方 modeConfig 才可能引用)
163
+ const intervalSeconds = loopIntervalSeconds.value
164
+ const maxIterations = loopMaxIterations.value
165
+ if (taskMode.value === 'loop') {
166
+ if (intervalSeconds === null || !Number.isFinite(intervalSeconds) || intervalSeconds < 1 || intervalSeconds > 86400) {
167
+ message.warning(t('composer.loopIntervalRange'))
168
+ return
169
+ }
170
+ if (maxIterations !== null && (!Number.isInteger(maxIterations) || maxIterations < 1 || maxIterations > 10000)) {
171
+ message.warning(t('composer.k1l36qf9015'))
172
+ return
173
+ }
174
+ }
175
+ await api.submitTask(props.channelId, {
176
+ title,
177
+ description,
178
+ mode: taskMode.value,
179
+ ...(taskMode.value === 'loop'
180
+ ? {
181
+ modeConfig: {
182
+ intervalMs: Math.round(intervalSeconds! * 1000),
183
+ ...(maxIterations !== null
184
+ ? { maxIterations: Math.floor(maxIterations) }
185
+ : {}),
186
+ },
187
+ }
188
+ : {}),
189
+ // HITL:@ 指定成员 → 任务直发(人类此刻即调度者);缺省 lead 自动调度
190
+ assigneeId: toAgentId.value || undefined,
191
+ // 人类发送者登录名:assign 消息以此盖章,时间线渲染"用户章"
192
+ fromLabel: userStore.user?.name ?? undefined,
193
+ // 任务全文随 assign 消息进时间线(人类提交内容在聊天界面可见)
194
+ parts: [{ text: input.value.trim() }],
195
+ })
196
+ const routedTo = targetName.value || 'lead'
197
+ message.success(t('composer.k1d2idgj035', { p0: routedTo, p1: taskMode.value }))
198
+ }
199
+ else {
200
+ if (!toAgentId.value) {
201
+ message.warning(t('composer.k1bw65is016'))
202
+ return
203
+ }
204
+ await api.injectMessage(props.channelId, {
205
+ toAgentId: toAgentId.value,
206
+ text,
207
+ priority: priority.value,
208
+ requireReply: requireReply.value,
209
+ // 人类发送者名:时间线 a2a.message 以"@你 → @目标"聊天行呈现
210
+ fromLabel: userStore.user?.name ?? undefined,
211
+ })
212
+ message.success(t('composer.k1wk4bf036', { p0: targetName.value, p1: priority.value === 'immediate' ? t('composer.k1cxjc4m034') : t('composer.k40g8m009') }))
213
+ }
214
+ input.value = ''
215
+ resetTarget()
216
+ emit('submitted')
217
+ }
218
+ catch (e) {
219
+ message.error(apiErrorMessage(e))
220
+ }
221
+ finally {
222
+ sendLoading.value = false
223
+ }
224
+ }
225
+
226
+ const onKeydown = (ev: KeyboardEvent): void => {
227
+ // IME 组合中不拦截按键(中文输入法选词确认的 Enter/方向键不做快捷处理)
228
+ if (ev.isComposing) return
229
+ if (onMentionKeydown(ev)) return
230
+ if (ev.key === 'Enter' && (ev.ctrlKey || ev.metaKey)) {
231
+ ev.preventDefault()
232
+ void send()
233
+ }
234
+ }
235
+
236
+ // ===== 输入框自适应高度(open-tag composer auto-resize) =====
237
+ watch(input, async () => {
238
+ await nextTick()
239
+ const el = taEl.value
240
+ if (!el) return
241
+ el.style.height = 'auto'
242
+ el.style.height = `${Math.min(el.scrollHeight, 160)}px`
243
+ })
244
+
245
+ /** 当前目标成员(状态行 chip 与提及回显) */
246
+ const targetAgent = computed(() => workersAndLead.value.find(a => a.agentId === toAgentId.value))
247
+ const targetName = computed(() => targetAgent.value?.name ?? '')
248
+ /** 目标是否为缺省路由(lead)——chip 提示"@ 可切换" */
249
+ const isDefaultLead = computed(() => !!leadAgentId.value && toAgentId.value === leadAgentId.value)
250
+
251
+ /**
252
+ * 可达性提示(open-tag reach hint 移植 + HITL 送达语义):直接回答
253
+ * "我发的东西对方以何种方式收到"。任务模式 = 排队进信箱;消息模式即时 =
254
+ * busy 时实时注入运行中的回合(HITL steer)。
255
+ */
256
+ type Reach = { tone: 'ok' | 'info' | 'warn', text: string, title: string }
257
+ const reachHint = computed<Reach | null>(() => {
258
+ if (!targetAgent.value) return null
259
+ const st = targetAgent.value.state
260
+ if (mode.value === 'task') {
261
+ if (st === 'busy') {
262
+ return {
263
+ tone: 'info',
264
+ text: t('composer.k1ww0ftm017'),
265
+ title: t('composer.k9eq10w018'),
266
+ }
267
+ }
268
+ if (st === 'stopped') {
269
+ return {
270
+ tone: 'warn',
271
+ text: t('composer.kbhz1rp019'),
272
+ title: t('composer.kophjf5020'),
273
+ }
274
+ }
275
+ return { tone: 'ok', text: t('composer.k4qgtiw021'), title: t('composer.kfmjwas022') }
276
+ }
277
+ if (st === 'busy') {
278
+ return {
279
+ tone: 'info',
280
+ text: priority.value === 'immediate' ? t('composer.kwg68t0023') : t('composer.k7w1etd024'),
281
+ title: t('composer.k9m6p7u025'),
282
+ }
283
+ }
284
+ if (st === 'stopped') {
285
+ return {
286
+ tone: 'warn',
287
+ text: t('composer.kj1ngpo026'),
288
+ title: t('composer.kmf7lcc027'),
289
+ }
290
+ }
291
+ return { tone: 'ok', text: t('composer.k1tet5df028'), title: t('composer.kn600in029') }
292
+ })
293
+
294
+ const placeholder = computed(() =>
295
+ mode.value === 'task'
296
+ ? t('composer.k2h5jwc030')
297
+ : t('composer.k15mwzt7031'),
298
+ )
299
+ </script>
300
+
301
+ <template>
302
+ <div class="composer">
303
+ <div class="composer-box">
304
+ <!-- @提及菜单(输入卡上方) -->
305
+ <div
306
+ v-if="mentionOpen"
307
+ class="mention-menu"
308
+ >
309
+ <div class="mention-title">
310
+ {{ $t('composer.k1bxvc46004') }}
311
+ </div>
312
+ <button
313
+ v-for="(a, i) in mentionCandidates"
314
+ :key="a.agentId"
315
+ type="button"
316
+ class="mention-opt"
317
+ :class="{ sel: i === mentionHi }"
318
+ @mousedown.prevent="pickMention(i)"
319
+ @mouseenter="mentionHi = i"
320
+ >
321
+ <span
322
+ class="aw-avatar is-agent mention-ava"
323
+ :style="{ '--av': agentHueColor(a.agentId) }"
324
+ >{{ a.name.charAt(0).toUpperCase() }}</span>
325
+ <span class="mention-name">@{{ a.name }}</span>
326
+ <span class="mention-role">{{ a.role }}</span>
327
+ <span
328
+ class="mention-state"
329
+ :class="a.state"
330
+ :title="a.state"
331
+ />
332
+ </button>
333
+ </div>
334
+
335
+ <div class="composer-status-chip">
336
+ <template v-if="mode === 'task'">
337
+ <span class="chip-key">{{ $t('composer.k3wcox005') }}</span>
338
+ <span>{{ taskMode }}</span>
339
+ <span v-if="taskMode === 'loop'">
340
+ {{ $t('composer.k49kr1011') }} {{ loopIntervalSeconds ?? '-' }}s
341
+ </span>
342
+ <span
343
+ class="chip-target"
344
+ :title="isDefaultLead ? $t('composer.defaultLeadTitle') : $t('composer.kl3604i033', { p0: targetName })"
345
+ >→ {{ targetName ? `@${targetName}` : 'lead' }}{{ isDefaultLead ? $t('composer.k2z7yuw012') : $t('composer.k2z0fsx032') }}</span>
346
+ <!-- HITL 送达语义提示 -->
347
+ <span
348
+ v-if="reachHint"
349
+ class="reach-chip"
350
+ :data-tone="reachHint.tone"
351
+ :title="reachHint.title"
352
+ >
353
+ <span
354
+ class="reach-dot"
355
+ aria-hidden="true"
356
+ />{{ reachHint.text }}
357
+ </span>
358
+ <span class="chip-hint">{{ $t('composer.k17u6q77006') }}</span>
359
+ </template>
360
+ <template v-else>
361
+ <span class="chip-key">{{ $t('composer.k41ykc007') }}</span>
362
+ <span v-if="targetName">@{{ targetName }}{{ isDefaultLead ? $t('composer.k2z7yuw012') : '' }}</span>
363
+ <span>{{ priority === 'immediate' ? $t('composer.k1bosqfv013') : $t('composer.k40g8m009') }}</span>
364
+ <!-- 可达性提示(open-tag reach hint):对方能否收到、将以何种方式送达 -->
365
+ <span
366
+ v-if="reachHint"
367
+ class="reach-chip"
368
+ :data-tone="reachHint.tone"
369
+ :title="reachHint.title"
370
+ >
371
+ <span
372
+ class="reach-dot"
373
+ aria-hidden="true"
374
+ />{{ reachHint.text }}
375
+ </span>
376
+ <span class="chip-hint">{{ $t('composer.chipHint') }}</span>
377
+ </template>
378
+ </div>
379
+
380
+ <textarea
381
+ ref="taEl"
382
+ v-model="input"
383
+ class="composer-input"
384
+ rows="1"
385
+ :placeholder="placeholder"
386
+ @input="detectMention"
387
+ @keydown="onKeydown"
388
+ @blur="mentionOpen = false"
389
+ />
390
+
391
+ <div class="composer-bar">
392
+ <div class="cb-left">
393
+ <div class="aw-seg">
394
+ <button
395
+ type="button"
396
+ :class="{ on: mode === 'task' }"
397
+ @click="mode = 'task'"
398
+ >
399
+ {{ $t('composer.k3wcox005') }}
400
+ </button>
401
+ <button
402
+ type="button"
403
+ :class="{ on: mode === 'message' }"
404
+ @click="mode = 'message'"
405
+ >
406
+ {{ $t('composer.k41ykc007') }}
407
+ </button>
408
+ </div>
409
+
410
+ <!-- HITL 目标选择(任务/消息共用):缺省 lead;@ 提及或下拉切换直发对象 -->
411
+ <a-select
412
+ v-model:value="toAgentId"
413
+ size="small"
414
+ class="target"
415
+ :options="workersAndLead.map(a => ({ value: a.agentId, label: `@ ${a.name}${a.role === 'lead' ? ' · lead' : ''}` }))"
416
+ />
417
+
418
+ <template v-if="mode === 'task'">
419
+ <div class="aw-seg">
420
+ <button
421
+ v-for="m in ['goal', 'loop', 'pipeline'] as const"
422
+ :key="m"
423
+ type="button"
424
+ :class="{ on: taskMode === m }"
425
+ @click="taskMode = m"
426
+ >
427
+ {{ m }}
428
+ </button>
429
+ </div>
430
+ <template v-if="taskMode === 'loop'">
431
+ <a-input-number
432
+ v-model:value="loopIntervalModel"
433
+ size="small"
434
+ :min="1"
435
+ :max="86400"
436
+ :step="1"
437
+ :precision="0"
438
+ :addon-after="$t('composer.seconds')"
439
+ class="loop-number"
440
+ />
441
+ <a-input-number
442
+ v-model:value="loopMaxIterationsModel"
443
+ size="small"
444
+ :min="1"
445
+ :max="10000"
446
+ :step="1"
447
+ :precision="0"
448
+ :placeholder="$t('composer.k1b38y2b001')"
449
+ class="loop-number iterations"
450
+ />
451
+ </template>
452
+ </template>
453
+ <template v-else>
454
+ <div class="aw-seg">
455
+ <button
456
+ type="button"
457
+ :class="{ on: priority === 'immediate' }"
458
+ :title="$t('composer.k1jqqvhe002')"
459
+ @click="priority = 'immediate'"
460
+ >
461
+ {{ $t('composer.k3x9n2008') }}
462
+ </button>
463
+ <button
464
+ type="button"
465
+ :class="{ on: priority === 'task' }"
466
+ @click="priority = 'task'"
467
+ >
468
+ {{ $t('composer.k40g8m009') }}
469
+ </button>
470
+ </div>
471
+ <button
472
+ type="button"
473
+ class="chip-toggle"
474
+ :class="{ on: requireReply }"
475
+ :title="$t('composer.k195594v003')"
476
+ @click="requireReply = !requireReply"
477
+ >
478
+ <span class="i-tabler-mail-forward" />
479
+ {{ $t('composer.k3xv7u010') }}
480
+ </button>
481
+ </template>
482
+ </div>
483
+
484
+ <div class="cb-right">
485
+ <button
486
+ type="button"
487
+ class="send-btn im"
488
+ :disabled="sendLoading || !input.trim()"
489
+ :title="$t('composer.sendTitle')"
490
+ @click="send"
491
+ >
492
+ <span class="i-tabler-send im-nudge-up" />
493
+ </button>
494
+ </div>
495
+ </div>
496
+ </div>
497
+ </div>
498
+ </template>
499
+
500
+ <style scoped>
501
+ .composer {
502
+ position: relative;
503
+ padding: 10px 16px 12px;
504
+ overflow: hidden;
505
+ background: var(--paper-raised);
506
+ border-top: 1px solid var(--line);
507
+ }
508
+
509
+ /* 输入卡(Slack 声部):纯白面 + 发丝线 + 12px 圆角;聚焦时墨色内缘加深。
510
+ * 去掉了光斑/水印装饰层 — 输入区是作业面,不是品牌海报。 */
511
+ .composer-box {
512
+ position: relative;
513
+ max-width: 900px;
514
+ padding: 8px 12px 8px;
515
+ margin: 0 auto;
516
+ background: var(--paper-raised);
517
+ border: 1px solid var(--line);
518
+ border-radius: var(--radius-panel);
519
+ box-shadow: inset 0 1px 0 color-mix(in srgb, white 30%, transparent), var(--bubble-shadow);
520
+ transition: border-color var(--transition-slow), box-shadow var(--transition-slow);
521
+ }
522
+ .composer-box:focus-within {
523
+ border-color: var(--ink-fainter);
524
+ box-shadow: inset 0 0 0 0.5px color-mix(in srgb, var(--ink) 18%, transparent), 0 6px 22px rgb(12 10 9 / 5%);
525
+ }
526
+
527
+ /* 状态行:轻 chip 说明当前模式参数 */
528
+ .composer-status-chip {
529
+ display: flex;
530
+ gap: 6px;
531
+ align-items: center;
532
+ width: max-content;
533
+ max-width: 100%;
534
+ margin: 0 0 4px 2px;
535
+ padding: 2px 8px;
536
+ font-size: 11.5px;
537
+ color: var(--ink-faint);
538
+ background: var(--paper-deep);
539
+ border-radius: var(--radius-chip);
540
+ }
541
+ .chip-key {
542
+ font-weight: 600;
543
+ color: var(--ink-soft);
544
+ }
545
+ .chip-target {
546
+ font-weight: 600;
547
+ color: var(--ink);
548
+ }
549
+ .chip-hint {
550
+ margin-left: auto;
551
+ padding-left: 8px;
552
+ color: var(--ink-faint);
553
+ }
554
+
555
+ /* 可达性提示 chip:ok 绿 / info 蓝 / warn 琥珀 —— 状态不只靠颜色(附文字) */
556
+ .reach-chip {
557
+ display: inline-flex;
558
+ gap: 5px;
559
+ align-items: center;
560
+ padding: 0 6px;
561
+ font-size: 10.5px;
562
+ border-radius: var(--radius-chip);
563
+ }
564
+ .reach-chip .reach-dot {
565
+ width: 5px;
566
+ height: 5px;
567
+ border-radius: 50%;
568
+ background: currentColor;
569
+ }
570
+ .reach-chip[data-tone='ok'] { color: var(--tone-success-dot); background: color-mix(in srgb, var(--tone-success-dot) 10%, transparent); }
571
+ .reach-chip[data-tone='info'] { color: var(--tone-info-dot); background: color-mix(in srgb, var(--tone-info-dot) 10%, transparent); }
572
+ .reach-chip[data-tone='warn'] { color: var(--tone-warning-dot); background: color-mix(in srgb, var(--tone-warning-dot) 13%, transparent); }
573
+
574
+ .composer-input {
575
+ display: block;
576
+ width: 100%;
577
+ max-height: 160px;
578
+ min-height: 26px;
579
+ padding: 3px 2px;
580
+ overflow-y: auto;
581
+ font-family: var(--font-body);
582
+ font-size: 13.5px;
583
+ line-height: 1.5;
584
+ color: var(--ink);
585
+ resize: none;
586
+ background: transparent;
587
+ border: 0;
588
+ outline: none;
589
+ }
590
+ .composer-input::placeholder { color: var(--ink-faint); }
591
+
592
+ .composer-bar {
593
+ display: flex;
594
+ align-items: center;
595
+ justify-content: space-between;
596
+ gap: 12px;
597
+ min-height: 30px;
598
+ margin-top: 6px;
599
+ }
600
+ .cb-left {
601
+ display: flex;
602
+ flex-wrap: wrap;
603
+ gap: 6px;
604
+ align-items: center;
605
+ }
606
+ .cb-right {
607
+ display: flex;
608
+ flex: none;
609
+ gap: 8px;
610
+ align-items: center;
611
+ }
612
+
613
+ /* 回执切换 chip */
614
+ .chip-toggle {
615
+ display: inline-flex;
616
+ gap: 5px;
617
+ align-items: center;
618
+ padding: 3px 10px;
619
+ font-family: var(--font-body);
620
+ font-size: 12px;
621
+ color: var(--ink-faint);
622
+ cursor: pointer;
623
+ background: var(--paper-raised);
624
+ border: 1px solid var(--line-strong);
625
+ border-radius: var(--radius-pill);
626
+ transition: color var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast);
627
+ }
628
+ .chip-toggle:hover { color: var(--ink); }
629
+ .chip-toggle.on {
630
+ font-weight: 600;
631
+ color: var(--ink);
632
+ background: var(--paper-deep);
633
+ border-color: var(--ink);
634
+ }
635
+
636
+ /* 发送:墨色药丸圆钮 */
637
+ .send-btn {
638
+ display: flex;
639
+ align-items: center;
640
+ justify-content: center;
641
+ width: 32px;
642
+ height: 32px;
643
+ padding: 0;
644
+ font-size: 15px;
645
+ color: var(--on-accent);
646
+ cursor: pointer;
647
+ background: var(--accent);
648
+ border: 0;
649
+ border-radius: var(--radius-pill);
650
+ transition: background var(--transition-fast), transform var(--transition-fast), opacity var(--transition-fast);
651
+ }
652
+ .send-btn:hover:not(:disabled) { background: var(--accent-strong); }
653
+ .send-btn:active:not(:disabled) { transform: scale(0.96); }
654
+ .send-btn:disabled { opacity: 0.3; cursor: default; }
655
+
656
+ .loop-number { width: 104px; }
657
+ .loop-number.iterations { width: 104px; }
658
+ .target { width: 138px; }
659
+
660
+ /* @提及菜单(open-tag mention-menu) */
661
+ .mention-menu {
662
+ position: absolute;
663
+ right: 12px;
664
+ bottom: 100%;
665
+ left: 12px;
666
+ z-index: 20;
667
+ max-height: 264px;
668
+ margin-bottom: 8px;
669
+ overflow: auto;
670
+ background: var(--paper-raised);
671
+ border: 1px solid var(--line-strong);
672
+ border-radius: var(--radius-panel);
673
+ box-shadow: var(--shadow-float);
674
+ }
675
+ .mention-opt {
676
+ display: flex;
677
+ gap: 9px;
678
+ align-items: center;
679
+ width: 100%;
680
+ padding: 7px 12px;
681
+ font-family: var(--font-body);
682
+ font-size: 13px;
683
+ color: var(--ink-soft);
684
+ text-align: left;
685
+ cursor: pointer;
686
+ background: transparent;
687
+ border: 0;
688
+ }
689
+ .mention-opt:hover,
690
+ .mention-opt.sel { background: var(--paper-deep); }
691
+ .mention-ava { width: 20px; height: 20px; font-size: 10px; }
692
+ .mention-name {
693
+ flex: 1;
694
+ min-width: 0;
695
+ overflow: hidden;
696
+ text-overflow: ellipsis;
697
+ white-space: nowrap;
698
+ color: var(--ink);
699
+ }
700
+ .mention-role {
701
+ flex: none;
702
+ font-size: 10.5px;
703
+ color: var(--ink-faint);
704
+ text-transform: uppercase;
705
+ letter-spacing: 0.05em;
706
+ }
707
+
708
+ .mention-title {
709
+ padding: 5px 12px 4px;
710
+ font-size: 10px;
711
+ font-weight: 600;
712
+ letter-spacing: 0.06em;
713
+ text-transform: uppercase;
714
+ color: var(--ink-faint);
715
+ }
716
+
717
+ /* 成员状态点:busy = 暖橙脉冲,idle = 静灰 */
718
+ .mention-state {
719
+ flex: none;
720
+ width: 7px;
721
+ height: 7px;
722
+ background: var(--ink-fainter);
723
+ border-radius: 50%;
724
+ opacity: 0.7;
725
+ }
726
+
727
+ .mention-state.busy {
728
+ background: var(--tone-live-dot);
729
+ opacity: 1;
730
+ }
731
+
732
+ /* ── 窄屏(≤1023):输入区是手持设备的"主操作面",键盘弹出时它必须还在 ──
733
+ 桌面工具栏是「一行左簇右钮」;390px 下这行会折成一列竖排字,
734
+ 所以窄屏改为「模式行 → 目标行 → 发送行」三段堆叠,发送占满一行且 ≥44px。 */
735
+ @media (max-width: 1023.98px) {
736
+ .composer {
737
+ padding: 8px 10px calc(10px + env(safe-area-inset-bottom));
738
+ }
739
+
740
+ .composer-box {
741
+ padding: 8px 10px;
742
+ }
743
+
744
+ /* 状态 chip:桌面宽度自适应内容,窄屏必须允许折行,否则顶破输入卡 */
745
+ .composer-status-chip {
746
+ width: auto;
747
+ flex-wrap: wrap;
748
+ row-gap: 2px;
749
+ font-size: 11.5px;
750
+ line-height: 1.45;
751
+ }
752
+
753
+ .chip-hint {
754
+ flex: 1 1 100%;
755
+ padding-left: 0;
756
+ margin-left: 0;
757
+ }
758
+
759
+ .reach-chip,
760
+ .mention-role,
761
+ .mention-title {
762
+ font-size: 11.5px;
763
+ }
764
+
765
+ /* 16px 是 iOS 不缩放输入框的下限(小于它聚焦时整页被放大) */
766
+ .composer-input {
767
+ min-height: 42px;
768
+ max-height: 30dvh;
769
+ font-size: 16px;
770
+ }
771
+
772
+ .composer-bar {
773
+ flex-wrap: wrap;
774
+ gap: 8px;
775
+ margin-top: 8px;
776
+ }
777
+
778
+ .cb-left {
779
+ flex: 1 1 100%;
780
+ row-gap: 8px;
781
+ }
782
+
783
+ .cb-right {
784
+ flex: 1 1 100%;
785
+ }
786
+
787
+ /* 主操作:整行墨色药丸(触摸目标 44px) */
788
+ .send-btn {
789
+ width: 100%;
790
+ height: 44px;
791
+ font-size: 18px;
792
+ }
793
+
794
+ .aw-seg button {
795
+ min-height: 40px;
796
+ padding: 0 12px;
797
+ }
798
+
799
+ .chip-toggle {
800
+ min-height: 40px;
801
+ }
802
+
803
+ .target {
804
+ flex: 1 1 130px;
805
+ width: auto;
806
+ }
807
+
808
+ .composer-bar :deep(.ant-select-selector) {
809
+ min-height: 40px;
810
+ align-items: center;
811
+ }
812
+
813
+ .loop-number,
814
+ .loop-number.iterations {
815
+ flex: 1 1 96px;
816
+ width: auto;
817
+ }
818
+
819
+ .composer-bar :deep(.ant-input-number) {
820
+ min-height: 40px;
821
+ }
822
+
823
+ .mention-opt {
824
+ min-height: 44px;
825
+ }
826
+ }
827
+ </style>