agentworkshop 0.7.40 → 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 (327) 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/{DcsCcxp4.js → D98NH8kG.js} +1 -1
  11. package/.output/public/_nuxt/DKySmNPI.js +14 -0
  12. package/.output/public/_nuxt/Di0NY_ht.js +1 -0
  13. package/.output/public/_nuxt/Du9v6qGB.js +1 -0
  14. package/.output/public/_nuxt/{C_iYD-EB.js → EHlEMRii.js} +1 -1
  15. package/.output/public/_nuxt/Eke1fs-U.js +1 -0
  16. package/.output/public/_nuxt/{DeJykBFf.js → H0BcqZtT.js} +1 -1
  17. package/.output/public/_nuxt/{C4oW1HsE.js → HSYEkkce.js} +1 -1
  18. package/.output/public/_nuxt/{DZshyNLa.js → IREZ1eC2.js} +1 -1
  19. package/.output/public/_nuxt/{D_OTvQza.js → J9oU00D1.js} +1 -1
  20. package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +1 -0
  21. package/.output/public/_nuxt/SSyAPaHc.js +1 -0
  22. package/.output/public/_nuxt/TownView.li4EMQxK.css +1 -0
  23. package/.output/public/_nuxt/VdLk4jLE2.js +1 -0
  24. package/.output/public/_nuxt/_id_.BhsXoEuk.css +1 -0
  25. package/.output/public/_nuxt/_id_.DQyj-WKN.css +1 -0
  26. package/.output/public/_nuxt/{_wsId_.B4WiqkIS.css → _wsId_.CTx-BlfF.css} +1 -1
  27. package/.output/public/_nuxt/aShsv_rG.js +19 -0
  28. package/.output/public/_nuxt/{yF0aYMF1.js → aesgXKSr.js} +3 -3
  29. package/.output/public/_nuxt/agents.B21nUEUX.css +1 -0
  30. package/.output/public/_nuxt/aml.ekTjxFkf.css +1 -0
  31. package/.output/public/_nuxt/builds/latest.json +1 -1
  32. package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +1 -0
  33. package/.output/public/_nuxt/ca4TJ3Op.js +1 -0
  34. package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
  35. package/.output/public/_nuxt/daq.BdVmoIfe.css +1 -0
  36. package/.output/public/_nuxt/dcw.DCgyzpWV.css +1 -0
  37. package/.output/public/_nuxt/{default.J32SgqFY.css → default.Bh3L2HU8.css} +1 -1
  38. package/.output/public/_nuxt/lHAxdSsj.js +1 -0
  39. package/.output/public/_nuxt/pages.j9rzRocr.css +1 -0
  40. package/.output/public/_nuxt/plugins.DhiZbHU8.css +1 -0
  41. package/.output/public/_nuxt/settings.BRx9rIDg.css +1 -0
  42. package/.output/public/_nuxt/teams.BLlOPB_Z.css +1 -0
  43. package/.output/public/_nuxt/tokens.De8xzszn.css +1 -0
  44. package/.output/public/_nuxt/workshop.D1jFNkrn.css +1 -0
  45. package/.output/server/chunks/_/nitro.mjs +1058 -830
  46. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  47. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs → OmpTerminalPanel-CVvxpsF7.mjs} +15 -15
  48. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs.map → OmpTerminalPanel-CVvxpsF7.mjs.map} +1 -1
  49. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs → TownView-CJ6IsDCc.mjs} +229 -228
  50. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
  51. package/.output/server/chunks/build/{_id_-BU1wfOID.mjs → _id_-CVD_H0ir.mjs} +149 -147
  52. package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
  53. package/.output/server/chunks/build/{_id_-BYsEua57.mjs → _id_-CtxMb3qp.mjs} +47 -46
  54. package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
  55. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs → _wsId_-DpY3T-md.mjs} +516 -446
  56. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs.map → _wsId_-DpY3T-md.mjs.map} +1 -1
  57. package/.output/server/chunks/build/{agents-CLQqJF2y.mjs → agents-DsCkuXDV.mjs} +49 -46
  58. package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +1 -0
  59. package/.output/server/chunks/build/{aml-_7FdFL2p.mjs → aml-CT0xKGKI.mjs} +122 -105
  60. package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +1 -0
  61. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
  62. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
  63. package/.output/server/chunks/build/{channel-templates-p6SZNjlM.mjs → channel-templates-Dla9FbYa.mjs} +49 -37
  64. package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +1 -0
  65. package/.output/server/chunks/build/{daq-cgyjKmJu.mjs → daq-DWWSKGAV.mjs} +95 -93
  66. package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
  67. package/.output/server/chunks/build/{dcw-CoTxBlAZ.mjs → dcw-CT8k17_E.mjs} +46 -45
  68. package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +1 -0
  69. package/.output/server/chunks/build/{default-BE4r0FZN.mjs → default-D3525UFX.mjs} +16 -15
  70. package/.output/server/chunks/build/{default-BE4r0FZN.mjs.map → default-D3525UFX.mjs.map} +1 -1
  71. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs → logs--dByNyhw.mjs} +2 -2
  72. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs.map → logs--dByNyhw.mjs.map} +1 -1
  73. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs → monitor-Dh2Kvaq5.mjs} +2 -2
  74. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs.map → monitor-Dh2Kvaq5.mjs.map} +1 -1
  75. package/.output/server/chunks/build/{pages-DFxjn9Xa.mjs → pages-BHcSBM2D.mjs} +26 -20
  76. package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +1 -0
  77. package/.output/server/chunks/build/{plugins-lGlUr1lj.mjs → plugins-BNgIi61W.mjs} +23 -17
  78. package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +1 -0
  79. package/.output/server/chunks/build/{settings-Ddd1qx2Z.mjs → settings-ipYBDWdn.mjs} +45 -44
  80. package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +1 -0
  81. package/.output/server/chunks/build/{teams-MEDLZdMn.mjs → teams-5lx-CwFa.mjs} +47 -44
  82. package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +1 -0
  83. package/.output/server/chunks/build/{tokens-C7fTM5C2.mjs → tokens-q-IDT8Io.mjs} +73 -53
  84. package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +1 -0
  85. package/.output/server/chunks/build/{town-qMPql9Se.mjs → town-QJWB5wng.mjs} +5 -4
  86. package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
  87. package/.output/server/chunks/build/{useDaqStream-CIBaiGAl.mjs → useDaqStream-DfCFpb3p.mjs} +9 -3
  88. package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +1 -0
  89. package/.output/server/chunks/build/{useDcwStream-CBbiYnWe.mjs → useDcwStream-UPiX-3iO.mjs} +11 -2
  90. package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +1 -0
  91. package/.output/server/chunks/build/{workshop-BL5bhfs0.mjs → workshop-BIlCj_ZP.mjs} +45 -45
  92. package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +1 -0
  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.post.mjs +3 -3
  95. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
  96. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  97. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  98. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
  99. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
  100. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  101. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  102. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  103. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  104. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  105. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  106. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  107. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +3 -1
  108. package/.output/server/chunks/routes/api/workshop/index5.get.mjs.map +1 -1
  109. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +3 -1
  110. package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
  111. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  112. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  113. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  116. package/.output/server/chunks/routes/renderer.mjs +2 -2
  117. package/.output/server/chunks/virtual/entry.mjs +639 -88
  118. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  119. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  120. package/.output/server/package.json +1 -1
  121. package/README-zh.md +16 -6
  122. package/README.md +17 -7
  123. package/app/components/AppHeader.vue +961 -959
  124. package/app/components/workshop/A2aRpcDebugger.vue +237 -237
  125. package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
  126. package/app/components/workshop/AgentLanesView.vue +998 -998
  127. package/app/components/workshop/ArtifactCard.vue +120 -120
  128. package/app/components/workshop/AssetLibrary.vue +452 -452
  129. package/app/components/workshop/ChannelSessionList.vue +812 -804
  130. package/app/components/workshop/CommandPalette.vue +269 -269
  131. package/app/components/workshop/Composer.vue +827 -827
  132. package/app/components/workshop/DeviceTwinPanel.vue +643 -643
  133. package/app/components/workshop/EventBlock.vue +602 -602
  134. package/app/components/workshop/FileSelectorModal.vue +188 -188
  135. package/app/components/workshop/InspectorPanel.vue +301 -301
  136. package/app/components/workshop/MemoryPanel.vue +364 -364
  137. package/app/components/workshop/MultiChannelView.vue +189 -189
  138. package/app/components/workshop/TaskBoardView.vue +686 -686
  139. package/app/components/workshop/TaskInspectorDrawer.vue +390 -390
  140. package/app/components/workshop/TranscriptTimeline.vue +601 -601
  141. package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
  142. package/app/components/workshop/blocks/ClusterError.vue +85 -85
  143. package/app/components/workshop/blocks/ClusterLife.vue +87 -87
  144. package/app/components/workshop/blocks/ClusterMember.vue +95 -93
  145. package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
  146. package/app/components/workshop/blocks/ClusterOther.vue +67 -67
  147. package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
  148. package/app/components/workshop/blocks/ClusterTool.vue +158 -158
  149. package/app/components/workshop/terminal/OmpTerminalPanel.vue +1011 -1011
  150. package/app/components/workshop/town/TownView.vue +56 -56
  151. package/app/composables/workshop/useDaqStream.ts +7 -4
  152. package/app/composables/workshop/useDcwStream.ts +8 -2
  153. package/app/middleware/locale-cookie.global.ts +22 -0
  154. package/app/pages/aml/index.vue +24 -2
  155. package/app/pages/daq/[id].vue +20 -16
  156. package/app/pages/daq/index.vue +2310 -2307
  157. package/app/pages/dcw/[id].vue +26 -23
  158. package/app/pages/dcw/index.vue +3 -3
  159. package/app/pages/index.vue +987 -981
  160. package/app/pages/plugins/index.vue +317 -311
  161. package/app/pages/settings.vue +1564 -1562
  162. package/app/pages/tokens.vue +15 -15
  163. package/app/pages/workshop/agents.vue +612 -609
  164. package/app/pages/workshop/channel-templates.vue +18 -11
  165. package/app/pages/workshop/index.vue +668 -668
  166. package/app/pages/workshop/teams.vue +743 -740
  167. package/app/pages/workshop/w/[wsId].vue +704 -704
  168. package/app/plugins/locale-restore.client.ts +16 -1
  169. package/app/utils/builtin-catalog.ts +104 -0
  170. package/config.yml +170 -170
  171. package/docs/cli.en.md +2 -2
  172. package/docs/cli.md +2 -2
  173. package/docs/full-test-plan.md +2 -3
  174. package/docs/plugins.en.md +33 -1
  175. package/docs/plugins.md +29 -1
  176. package/docs/sdk.en.md +1 -1
  177. package/docs/sdk.md +1 -1
  178. package/i18n/locales/en.ts +293 -35
  179. package/i18n/locales/zh-CN.ts +265 -7
  180. package/package.json +4 -3
  181. package/scripts/_dbg-perms-e2e.mjs +132 -109
  182. package/scripts/_demo-agentteam-capture.mjs +278 -0
  183. package/scripts/_e2e-ui-functional.mjs +371 -0
  184. package/scripts/_i18n-en-cjk-scan.mjs +28 -0
  185. package/scripts/_i18n-en-verify.mjs +107 -0
  186. package/scripts/_i18n-extra-checks.mjs +53 -0
  187. package/scripts/_i18n-scan-hardcoded.mjs +180 -0
  188. package/scripts/_i18n-verify-keys.mjs +50 -0
  189. package/scripts/_loop-biax-campaign.mjs +130 -0
  190. package/scripts/_sweep-button-contrast.mjs +87 -0
  191. package/scripts/test-serial-bridge.mjs +418 -0
  192. package/scripts/ui/e2e-interactive.mjs +328 -0
  193. package/scripts/ui/record-demo.mjs +557 -0
  194. package/scripts/ui/seed-industrial-demo.mjs +171 -0
  195. package/server/api/workshop/daq/index.get.ts +3 -1
  196. package/server/api/workshop/dcw/index.get.ts +3 -0
  197. package/server/plugins/aw-plugins.ts +9 -7
  198. package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
  199. package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
  200. package/server/plugins-builtin/serial-bridge/client.mjs +210 -0
  201. package/server/plugins-builtin/serial-bridge/drivers.mjs +409 -0
  202. package/server/plugins-builtin/serial-bridge/i18n.json +62 -0
  203. package/server/plugins-builtin/serial-bridge/index.mjs +247 -0
  204. package/server/plugins-builtin/serial-bridge/lib.mjs +208 -0
  205. package/server/plugins-builtin/serial-bridge/rtu.mjs +168 -0
  206. package/server/services/workshop/daq/daq-controller.ts +7 -2
  207. package/server/services/workshop/daq/drivers.ts +89 -3
  208. package/server/services/workshop/dcw/dcw-controller.ts +5 -1
  209. package/server/services/workshop/dcw/drivers.ts +85 -2
  210. package/server/services/workshop/dcw/plugin-bridge.ts +45 -0
  211. package/server/services/workshop/plugins/host.mjs +30 -0
  212. package/shared/daq-protocol.ts +15 -0
  213. package/.output/public/_nuxt/88GOowgO2.js +0 -1
  214. package/.output/public/_nuxt/BOt2SP4z.js +0 -1
  215. package/.output/public/_nuxt/BP-NyKVG.js +0 -18
  216. package/.output/public/_nuxt/BXxH0UQE.js +0 -1
  217. package/.output/public/_nuxt/BsROmY89.js +0 -1
  218. package/.output/public/_nuxt/ClILjvWH.js +0 -1
  219. package/.output/public/_nuxt/CzOCSGLe.js +0 -1
  220. package/.output/public/_nuxt/D8qvkfyY.js +0 -1
  221. package/.output/public/_nuxt/DCn7QZ5N.js +0 -2
  222. package/.output/public/_nuxt/DS8qj8EL.js +0 -1
  223. package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
  224. package/.output/public/_nuxt/Dihnimlk.js +0 -19
  225. package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
  226. package/.output/public/_nuxt/Dr9v6C0f.js +0 -14
  227. package/.output/public/_nuxt/Du9ZJtnj.js +0 -1
  228. package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
  229. package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
  230. package/.output/public/_nuxt/_id_.CM4bIgKY.css +0 -1
  231. package/.output/public/_nuxt/_id_.bNV69Dtf.css +0 -1
  232. package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
  233. package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
  234. package/.output/public/_nuxt/builds/meta/f4707884-7cf4-4a31-80d2-e3d72b73745f.json +0 -1
  235. package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
  236. package/.output/public/_nuxt/daq.B0D3dPAc.css +0 -1
  237. package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
  238. package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
  239. package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
  240. package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
  241. package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
  242. package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
  243. package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
  244. package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
  245. package/.output/server/chunks/build/_id_-BU1wfOID.mjs.map +0 -1
  246. package/.output/server/chunks/build/_id_-BYsEua57.mjs.map +0 -1
  247. package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
  248. package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
  249. package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
  250. package/.output/server/chunks/build/daq-cgyjKmJu.mjs.map +0 -1
  251. package/.output/server/chunks/build/dcw-CoTxBlAZ.mjs.map +0 -1
  252. package/.output/server/chunks/build/pages-DFxjn9Xa.mjs.map +0 -1
  253. package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
  254. package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
  255. package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
  256. package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
  257. package/.output/server/chunks/build/town-qMPql9Se.mjs.map +0 -1
  258. package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs.map +0 -1
  259. package/.output/server/chunks/build/useDcwStream-CBbiYnWe.mjs.map +0 -1
  260. package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
  261. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
  262. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
  263. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
  264. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
  265. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
  266. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
  267. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
  268. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
  269. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
  270. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
  271. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
  272. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
  273. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
  274. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
  275. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
  276. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
  277. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
  278. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
  279. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
  280. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
  281. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
  282. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
  283. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
  284. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
  285. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
  286. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
  287. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
  288. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
  289. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
  290. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
  291. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
  292. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
  293. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
  294. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
  295. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
  296. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
  297. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
  298. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
  299. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
  300. package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
  301. package/scripts/_dbg-audit-all-pages.mjs +0 -43
  302. package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
  303. package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
  304. package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
  305. package/scripts/_dbg-live-line-e2e.mjs +0 -279
  306. package/scripts/_dbg-minimal-omp.mjs +0 -77
  307. package/scripts/_dbg-mode-nav.mjs +0 -173
  308. package/scripts/_dbg-real-scenario-timeline.log +0 -55
  309. package/scripts/_dbg-team-repro.mjs +0 -76
  310. package/scripts/_dbg-town-agents-probe.mjs +0 -60
  311. package/scripts/_dbg-visual-acceptance.mjs +0 -63
  312. package/scripts/_townview_style.css +0 -1185
  313. package/scripts/_townview_template.html +0 -1215
  314. package/server/data/agent-node-bindings.json +0 -554
  315. package/server/data/daq-templates.json +0 -16
  316. package/server/data/daqs.json +0 -7464
  317. package/server/data/dcw-lines.json +0 -1787
  318. package/server/data/dcw-products.json +0 -1626
  319. package/server/data/dcw-recipes.json +0 -4672
  320. package/server/data/dcw-rollback.json +0 -20754
  321. package/server/data/dcw-runs.json +0 -6354
  322. package/server/data/dcw-templates.json +0 -146
  323. package/server/data/dcw-writes.json +0 -15650
  324. package/server/data/dcws.json +0 -9033
  325. package/server/data/device-twins.json +0 -133
  326. package/server/data/line-runs.json +0 -322
  327. package/server/data/scene-layouts.json +0 -277
@@ -1,609 +1,612 @@
1
- <script setup lang="ts">
2
- /**
3
- * Agent 模板库:用户级隔离的模板 CRUD + 实例去向(克隆到了哪些 channel)。
4
- * v10 可见性:private 仅本人;public 全员可读可用(仅属主可改删);内置(锁)任何人不可改删。
5
- * admin:全量视图(含他人私有),附创建者;可改删任意非内置模板。
6
- */
7
- import { message } from 'ant-design-vue'
8
- import { useWorkshopApi, type AgentTemplateDto, type HarnessMetaDto } from '../../composables/workshop/useWorkshopApi'
9
- import { useUserStore } from '../../stores/workshop/user'
10
-
11
- const { t: tt } = useI18n()
12
-
13
- definePageMeta({ layout: 'default' })
14
-
15
- const api = useWorkshopApi()
16
- const userStore = useUserStore()
17
- const templates = ref<AgentTemplateDto[]>([])
18
- const loading = ref(false)
19
- const load = async (): Promise<void> => {
20
- loading.value = true
21
- try {
22
- const res = await api.listTemplates()
23
- templates.value = (res as unknown as { data?: AgentTemplateDto[] })?.data ?? []
24
- }
25
- finally {
26
- loading.value = false
27
- }
28
- }
29
- // SSR 安全:setup 期 $http(axios)无法在服务端发相对地址请求,拒绝会变成未处理
30
- // rejection 直杀渲染进程;页面数据一律客户端装载
31
- if (import.meta.client) void load()
32
-
33
- // ===== 过滤(全部/我的/公开/内置;admin 另有"他人私有") =====
34
- type Filter = 'all' | 'mine' | 'public' | 'builtin' | 'others'
35
- const filter = ref<Filter>('all')
36
- const filterOptions = computed(() => {
37
- const opts: Array<{ value: Filter, label: string }> = [
38
- { value: 'all', label: tt('chips.chipAll', { n: templates.value.length }) },
39
- { value: 'mine', label: tt('chips.chipMine', { n: templates.value.filter(t => t.ownerUserId === userStore.user?.id).length }) },
40
- { value: 'public', label: tt('chips.chipPublic', { n: templates.value.filter(t => t.visibility === 'public').length }) },
41
- { value: 'builtin', label: tt('chips.chipBuiltin', { n: templates.value.filter(t => t.isBuiltin).length }) },
42
- ]
43
- if (userStore.isAdmin) {
44
- opts.push({ value: 'others', label: tt('chips.chipOthers', { n: templates.value.filter(t => t.ownerUserId !== null && t.ownerUserId !== userStore.user?.id && t.visibility === 'private').length }) })
45
- }
46
- return opts
47
- })
48
- const shown = computed(() => {
49
- const uid = userStore.user?.id
50
- switch (filter.value) {
51
- case 'mine': return templates.value.filter(t => t.ownerUserId === uid)
52
- case 'public': return templates.value.filter(t => t.visibility === 'public')
53
- case 'builtin': return templates.value.filter(t => t.isBuiltin)
54
- case 'others': return templates.value.filter(t => t.ownerUserId !== null && t.ownerUserId !== uid && t.visibility === 'private')
55
- default: return templates.value
56
- }
57
- })
58
-
59
- // 写权限:属主或 admin;内置/他人公开模板只读
60
- const canWrite = (t: AgentTemplateDto): boolean =>
61
- !t.isBuiltin && (t.ownerUserId === userStore.user?.id || userStore.isAdmin)
62
-
63
- const visTag = (t: AgentTemplateDto): { text: string, color: string, icon?: string } => {
64
- if (t.isBuiltin) return { text: tt('agents.k3x23c017'), color: 'default', icon: 'i-tabler-lock' }
65
- if (t.visibility === 'public') return { text: tt('agents.k3wv1t018'), color: 'green' }
66
- return { text: tt('agents.k447jj019'), color: 'default' }
67
- }
68
-
69
- // ===== harness 选项(引擎注册表动态拉取,含环境可用性探测;失败回退静态表) =====
70
- const harnesses = ref<HarnessMetaDto[]>([])
71
- const harnessById = computed(() => new Map(harnesses.value.map(h => [h.id, h])))
72
- const isUnavailable = (id: string): boolean => harnessById.value.get(id)?.available === false
73
- const harnessOptions = computed(() =>
74
- (harnesses.value.length > 0
75
- ? harnesses.value
76
- : [
77
- { id: 'mock', label: 'mock(测试)', description: '', capabilities: { steer: true, supervise: false, hitl: false, terminal: false, contextStats: false, compact: false } },
78
- { id: 'omp', label: 'omp(真实 LLM)', description: '', capabilities: { steer: true, supervise: true, hitl: true, terminal: true, contextStats: true, compact: true } },
79
- { id: 'claude', label: 'claude', description: '', capabilities: { steer: false, supervise: false, hitl: false, terminal: false, contextStats: false, compact: false } },
80
- ] as HarnessMetaDto[]).map((h) => {
81
- const unavailable = h.available === false
82
- return { value: h.id, label: unavailable ? `${h.label}(${tt('agents.notInstalled')})` : h.label, disabled: unavailable }
83
- }),
84
- )
85
- const capBadges = (id: string): string[] => {
86
- const caps = harnesses.value.find(h => h.id === id)?.capabilities
87
- if (!caps) return []
88
- const out: string[] = []
89
- if (caps.steer) out.push('steer')
90
- if (caps.supervise) out.push('lead')
91
- if (caps.hitl) out.push('HITL')
92
- if (caps.compact) out.push('compact')
93
- return out
94
- }
95
- const loadHarnesses = async (): Promise<void> => {
96
- try {
97
- const res = await api.listHarnesses()
98
- const list = (res as unknown as { data?: { harnesses?: HarnessMetaDto[] } })?.data?.harnesses
99
- if (Array.isArray(list) && list.length > 0) harnesses.value = list
100
- }
101
- catch { /* 回退静态表 */ }
102
- }
103
- if (import.meta.client) void loadHarnesses()
104
-
105
- const editOpen = ref(false)
106
- const editing = ref<AgentTemplateDto | null>(null)
107
- const form = reactive({ name: '', harness: 'mock', configJson: '{}', visibility: 'private' as 'private' | 'public' })
108
- const openCreate = (): void => {
109
- editing.value = null
110
- form.name = ''
111
- form.harness = 'mock'
112
- form.configJson = '{}'
113
- form.visibility = 'private'
114
- editOpen.value = true
115
- }
116
- const openEdit = (t: AgentTemplateDto): void => {
117
- editing.value = t
118
- form.name = t.name
119
- form.harness = t.harness
120
- form.configJson = JSON.stringify(t.config ?? {}, null, 2)
121
- form.visibility = t.visibility
122
- editOpen.value = true
123
- }
124
- const save = async (): Promise<void> => {
125
- let config: Record<string, unknown> = {}
126
- try {
127
- config = JSON.parse(form.configJson || '{}')
128
- }
129
- catch {
130
- message.error('config 不是合法 JSON')
131
- return
132
- }
133
- // 前端兜底:引擎未安装禁止保存(与后端 assertHarnessUsable 同判据)
134
- if (isUnavailable(form.harness)) {
135
- message.error(harnessById.value.get(form.harness)?.error ?? tt('agents.notInstalled'))
136
- return
137
- }
138
- try {
139
- if (editing.value) {
140
- await api.updateTemplate(editing.value.id, { name: form.name, harness: form.harness, config, visibility: form.visibility })
141
- message.success(tt('agents.k3n9aij020'))
142
- }
143
- else {
144
- await api.createTemplate({ name: form.name, harness: form.harness, config, visibility: form.visibility })
145
- message.success(tt('agents.k3n5hak021'))
146
- }
147
- editOpen.value = false
148
- void load()
149
- }
150
- catch (e) {
151
- message.error(apiErrorMessage(e))
152
- }
153
- }
154
- const remove = async (t: AgentTemplateDto): Promise<void> => {
155
- await api.deleteTemplate(t.id)
156
- message.success(tt('agents.k3n5sd7022'))
157
- void load()
158
- }
159
-
160
- const toggleEnabled = async (t: AgentTemplateDto): Promise<void> => {
161
- await api.updateTemplate(t.id, { enabled: t.enabled === 1 ? 0 : 1 })
162
- void load()
163
- }
164
-
165
- /** 一键切换可见性(属主/admin;行内 switch) */
166
- const toggleVisibility = async (t: AgentTemplateDto, pub: boolean): Promise<void> => {
167
- try {
168
- await api.updateTemplate(t.id, { visibility: pub ? 'public' : 'private' })
169
- message.success(pub ? tt('agents.kxa6cxx023') : tt('agents.k1xxabaf024'))
170
- void load()
171
- }
172
- catch (e) {
173
- message.error(apiErrorMessage(e))
174
- }
175
- }
176
-
177
- useHead({ title: () => tt('titles.agents') })
178
- </script>
179
-
180
- <template>
181
- <div class="page">
182
- <div class="head">
183
- <div>
184
- <h2>Agent {{ $t('agents.k3pa5h4025') }}</h2>
185
- <p class="sub">
186
- {{ $t('agents.k5nsz7y008') }}
187
- </p>
188
- </div>
189
- <a-space>
190
- <a-button @click="navigateTo('/workshop')">
191
- {{ $t('agents.krpx6qa009') }}
192
- </a-button>
193
- <a-button
194
- type="primary"
195
- @click="openCreate"
196
- >
197
- {{ $t('agents.k1efixrj010') }}
198
- </a-button>
199
- </a-space>
200
- </div>
201
-
202
- <div class="toolbar">
203
- <a-segmented
204
- v-model:value="filter"
205
- size="small"
206
- :options="filterOptions"
207
- />
208
- <span
209
- v-if="userStore.isAdmin"
210
- class="admin-note"
211
- ><span class="i-tabler-shield-check" /> {{ $t('agents.ka1gpdj011') }}</span>
212
- </div>
213
-
214
- <a-table
215
- :data-source="shown"
216
- :loading="loading"
217
- row-key="id"
218
- size="small"
219
- :pagination="false"
220
- >
221
- <a-table-column
222
- :title="$t('agents.k3xhia001')"
223
- data-index="name"
224
- >
225
- <template #default="{ record }">
226
- <span class="tpl-name">
227
- <span class="i-tabler-user-square" />
228
- {{ record.name }}
229
- </span>
230
- </template>
231
- </a-table-column>
232
- <a-table-column
233
- title="harness"
234
- data-index="harness"
235
- :width="120"
236
- >
237
- <template #default="{ record }">
238
- <span
239
- class="h-cell"
240
- :class="{ off: isUnavailable(record.harness) }"
241
- :title="isUnavailable(record.harness) ? (harnessById.get(record.harness)?.error ?? '') : (harnessById.get(record.harness)?.resolvedPath ?? '')"
242
- >
243
- <span class="h-dot" />{{ record.harness }}<span
244
- v-if="isUnavailable(record.harness)"
245
- class="h-miss"
246
- >({{ tt('agents.notInstalled') }})</span>
247
- </span>
248
- </template>
249
- </a-table-column>
250
- <a-table-column
251
- :title="$t('agents.k3lrqn0002')"
252
- :width="120"
253
- >
254
- <template #default="{ record }">
255
- <a-tag
256
- :color="visTag(record).color"
257
- class="vis-tag"
258
- >
259
- <span
260
- v-if="visTag(record).icon"
261
- :class="visTag(record).icon"
262
- />{{ visTag(record).text }}
263
- </a-tag>
264
- <!-- 开关**不带文字**:左边那枚 tag 已经在说"公开/私有"了。
265
- 两处同时显示同一个词,是同一列里把一条信息讲了两遍(实测桌面版每行都这样)。
266
- 开关只负责"可切换"这个动作,语义由 tag + title 承载。 -->
267
- <a-switch
268
- v-if="!record.isBuiltin && canWrite(record)"
269
- :checked="record.visibility === 'public'"
270
- size="small"
271
- :title="record.visibility === 'public' ? '点击转为私有' : '点击公开(全员可读可用)'"
272
- @change="(v: unknown) => toggleVisibility(record, v === true)"
273
- />
274
- </template>
275
- </a-table-column>
276
- <a-table-column
277
- :title="$t('agents.k3l98u7003')"
278
- :width="130"
279
- >
280
- <template #default="{ record }">
281
- <span class="owner">{{ record.ownerName ?? record.ownerUserId?.slice(0, 8) ?? '-' }}</span>
282
- </template>
283
- </a-table-column>
284
- <a-table-column
285
- :title="$t('agents.k3mr526004')"
286
- :width="70"
287
- >
288
- <template #default="{ record }">
289
- {{ record.instances.length }}
290
- </template>
291
- </a-table-column>
292
- <a-table-column
293
- :title="$t('agents.k3xhfg005')"
294
- :width="70"
295
- >
296
- <template #default="{ record }">
297
- <a-switch
298
- :checked="record.enabled === 1"
299
- size="small"
300
- :disabled="!canWrite(record)"
301
- @change="toggleEnabled(record)"
302
- />
303
- </template>
304
- </a-table-column>
305
- <a-table-column
306
- :title="$t('agents.k40aa6006')"
307
- :width="130"
308
- >
309
- <template #default="{ record }">
310
- <a-space size="small">
311
- <a-button
312
- size="small"
313
- type="text"
314
- :disabled="!canWrite(record)"
315
- :title="record.isBuiltin ? '内置模板不可修改' : !canWrite(record) ? '仅属主可修改' : '编辑'"
316
- @click="openEdit(record)"
317
- >
318
- {{ $t('agents.k45eb0012') }}
319
- </a-button>
320
- <a-popconfirm
321
- :title="$t('agents.keetzvf007')"
322
- :disabled="!canWrite(record)"
323
- @confirm="remove(record)"
324
- >
325
- <a-button
326
- size="small"
327
- type="text"
328
- danger
329
- :disabled="!canWrite(record)"
330
- :title="record.isBuiltin ? '内置模板不可删除' : '删除'"
331
- >
332
- {{ $t('agents.k3xakp013') }}
333
- </a-button>
334
- </a-popconfirm>
335
- </a-space>
336
- </template>
337
- </a-table-column>
338
- <template #expandedRowRender="{ record }">
339
- <div
340
- v-for="inst in record.instances"
341
- :key="inst.id"
342
- class="inst"
343
- >
344
- <a-tag :color="inst.role === 'lead' ? 'gold' : 'blue'">
345
- {{ inst.role }}
346
- </a-tag>
347
- <span class="inst-id">{{ inst.id.slice(0, 8) }}</span>
348
- <span class="inst-ch">channel {{ inst.channelId.slice(0, 8) }}</span>
349
- </div>
350
- <div
351
- v-if="record.instances.length === 0"
352
- class="empty"
353
- >
354
- {{ $t('agents.k1d036zs014') }}
355
- </div>
356
- </template>
357
- </a-table>
358
-
359
- <a-modal
360
- v-model:open="editOpen"
361
- :title="editing ? '编辑模板' : '新建模板'"
362
- ok-text="保存"
363
- cancel-text="取消"
364
- @ok="save"
365
- >
366
- <a-form layout="vertical">
367
- <a-form-item :label="$t('agents.k3xhia001')">
368
- <a-input v-model:value="form.name" />
369
- </a-form-item>
370
- <a-form-item label="harness">
371
- <a-select
372
- v-model:value="form.harness"
373
- :options="harnessOptions"
374
- >
375
- <template #option="{ value, label }">
376
- <span
377
- class="h-opt"
378
- :class="{ off: isUnavailable(String(value)) }"
379
- >
380
- <span class="h-opt-dot" /> {{ label }}
381
- </span>
382
- </template>
383
- </a-select>
384
- <div
385
- v-if="harnesses.length > 0"
386
- class="harness-status"
387
- :class="{ off: isUnavailable(form.harness) }"
388
- >
389
- <template v-if="isUnavailable(form.harness)">
390
- <span class="i-tabler-plug-off" /> {{ harnessById.get(form.harness)?.error }}
391
- <a
392
- v-if="harnessById.get(form.harness)?.homepage"
393
- class="h-install"
394
- :href="harnessById.get(form.harness)?.homepage"
395
- target="_blank"
396
- rel="noopener"
397
- >{{ tt('agents.installLink') }}</a>
398
- </template>
399
- <template v-else-if="harnessById.get(form.harness)?.inprocess">
400
- <span class="i-tabler-plug-connected" /> {{ tt('agents.harnessInprocess') }}
401
- </template>
402
- <template v-else-if="harnessById.get(form.harness)?.resolvedPath">
403
- <span class="i-tabler-plug-connected" /> <span class="mono">{{ harnessById.get(form.harness)?.resolvedPath }}</span>
404
- </template>
405
- </div>
406
- <div
407
- v-if="capBadges(form.harness).length"
408
- class="harness-caps"
409
- >
410
- <span
411
- v-for="b in capBadges(form.harness)"
412
- :key="b"
413
- class="harness-cap"
414
- >{{ b }}</span>
415
- </div>
416
- </a-form-item>
417
- <a-form-item :label="$t('agents.k3lrqn0002')">
418
- <a-radio-group v-model:value="form.visibility">
419
- <a-radio value="private">
420
- {{ $t('agents.k17jfge3015') }}
421
- </a-radio>
422
- <a-radio value="public">
423
- {{ $t('agents.k1tt5zyo016') }}
424
- </a-radio>
425
- </a-radio-group>
426
- </a-form-item>
427
- <a-form-item label="config(JSON;mock 可配 delayMs)">
428
- <a-textarea
429
- v-model:value="form.configJson"
430
- :rows="6"
431
- />
432
- </a-form-item>
433
- </a-form>
434
- </a-modal>
435
- </div>
436
- </template>
437
-
438
- <style scoped>
439
- .harness-caps { margin-top: 6px; display: flex; gap: 4px; flex-wrap: wrap; }
440
- .harness-cap {
441
- font-size: 11px;
442
- line-height: 18px;
443
- padding: 0 6px;
444
- border: 1px solid var(--border, #2a3a4a);
445
- border-radius: 3px;
446
- opacity: 0.75;
447
- }
448
- .h-opt { display: inline-flex; align-items: center; gap: 7px; }
449
- .h-opt-dot { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--tone-success-dot, #4a6b57); }
450
- .h-opt.off .h-opt-dot { background: var(--tone-danger-dot, #c25a4e); }
451
- .h-opt.off { opacity: 0.6; }
452
- .h-install { margin-left: 8px; color: var(--accent); text-decoration: none; }
453
- .h-install:hover { text-decoration: underline; }
454
- .harness-status {
455
- display: flex;
456
- gap: 6px;
457
- align-items: center;
458
- margin-top: 6px;
459
- font-size: 11px;
460
- color: var(--ink-faint);
461
- }
462
- .harness-status.off { color: var(--danger, #c25a4e); }
463
- .h-cell {
464
- display: inline-flex;
465
- gap: 6px;
466
- align-items: center;
467
- font-family: var(--font-mono);
468
- font-size: 12px;
469
- }
470
- .h-dot {
471
- width: 6px;
472
- height: 6px;
473
- border-radius: 50%;
474
- background: var(--ok, #4a6b57);
475
- }
476
- .h-cell.off .h-dot { background: var(--danger, #c25a4e); }
477
- .h-cell.off { opacity: 0.75; }
478
- .h-miss { color: var(--danger, #c25a4e); font-family: var(--font-sans); font-size: 11px; }
479
- .page { padding: 4px; }
480
- .head {
481
- display: flex;
482
- align-items: center;
483
- justify-content: space-between;
484
- margin-bottom: 12px;
485
- }
486
- h2 { margin: 0 0 4px; }
487
- .sub { margin: 0; font-size: 12px; opacity: 0.55; }
488
- .toolbar {
489
- display: flex;
490
- gap: 12px;
491
- align-items: center;
492
- margin-bottom: 12px;
493
- }
494
- .admin-note {
495
- display: inline-flex;
496
- gap: 5px;
497
- align-items: center;
498
- font-size: 11px;
499
- color: var(--ink-faint);
500
- }
501
- .tpl-name {
502
- display: inline-flex;
503
- gap: 6px;
504
- align-items: center;
505
- }
506
- .vis-tag {
507
- margin-right: 8px;
508
- }
509
- .owner { font-size: 12px; color: var(--ink-soft); }
510
- .inst {
511
- display: flex;
512
- gap: 8px;
513
- align-items: center;
514
- padding: 2px 0;
515
- font-size: 12px;
516
- }
517
- .inst-id,
518
- .inst-ch { font-family: var(--font-mono); opacity: 0.6; }
519
- .empty { padding: 6px 0; font-size: 12px; opacity: 0.4; }
520
-
521
- /* ══ 窄屏(v9):页头纵向堆叠 / 筛选条换行 / 表格横向卷轴 + 首列可读 ═════════ */
522
- @media (max-width: 900px) {
523
- .head {
524
- flex-direction: column;
525
- align-items: stretch;
526
- gap: 12px;
527
- }
528
-
529
- .head > div { min-width: 0; }
530
- .head h2 { font-size: 21px; line-height: 1.25; }
531
-
532
- .head :deep(.ant-space) {
533
- display: flex;
534
- flex-direction: column;
535
- gap: 8px;
536
- align-items: stretch;
537
- width: 100%;
538
- }
539
-
540
- .head :deep(.ant-space-item) { width: 100%; }
541
- .head :deep(.ant-btn) { width: 100%; min-height: 40px; }
542
-
543
- .toolbar {
544
- flex-wrap: wrap;
545
- gap: 8px;
546
- }
547
-
548
- .toolbar :deep(.ant-segmented) {
549
- flex: 1 1 100%;
550
- min-width: 0;
551
- }
552
-
553
- .admin-note {
554
- flex: 1 1 100%;
555
- min-width: 0;
556
- font-size: 11.5px;
557
- line-height: 1.5;
558
- }
559
-
560
- /* 整表给出可读下限:横向卷轴交给全局 v5 的 .ant-table-content */
561
- .page :deep(.ant-table-content) table { min-width: 828px; }
562
-
563
- /* 有 expandedRowRender 时 antd 会把"展开图标列"放在第一列,而全局 v5 钉住的正是第一列。
564
- auto 布局下没有显式宽度的列会吃掉全部余量 → 展开列白占 ~115px,真正的身份列还会被卷走。
565
- 这里把展开列收成 44px,并让身份列(模板名)紧随其后一起钉住:横扫时始终知道这一行是谁。 */
566
- .page :deep(.ant-table colgroup col:first-child) { width: 44px; }
567
-
568
- .page :deep(.ant-table-thead > tr > th:first-child),
569
- .page :deep(.ant-table-tbody > tr > td:first-child) {
570
- width: 44px;
571
- min-width: 44px;
572
- padding-right: 2px;
573
- padding-left: 6px;
574
- }
575
-
576
- .page :deep(.ant-table-thead > tr > th:nth-child(2)),
577
- .page :deep(.ant-table-tbody > tr > td:nth-child(2)) {
578
- position: sticky;
579
- left: 44px;
580
- z-index: 2;
581
- min-width: 132px;
582
- background: var(--paper-raised);
583
- box-shadow: 1px 0 0 var(--line);
584
- }
585
-
586
- .page :deep(.ant-table-thead > tr > th:nth-child(2)) { z-index: 3; }
587
- }
588
-
589
- @media (max-width: 640px) {
590
- .page { padding: 0; }
591
- .head h2 { font-size: 19px; }
592
- .sub { font-size: 11.5px; line-height: 1.5; }
593
- .tpl-name { min-width: 120px; }
594
- .page :deep(.ant-table) .ant-btn-sm { min-height: 34px; }
595
- }
596
-
597
- /* 触摸命中区:antd 小开关本体只有 28×16(手指点不中),视觉尺寸保持不变,
598
- 用伪元素把命中区外扩到 ~40×40 —— 与全局 v5 给展开图标做的事同一手法。
599
- 行内没有相邻可点元素(可见性列是 tag+switch,启用列只有 switch),不会误伤。 */
600
- @media (max-width: 900px) {
601
- .page :deep(.ant-switch) { position: relative; }
602
-
603
- .page :deep(.ant-switch)::after {
604
- position: absolute;
605
- inset: -12px -6px;
606
- content: '';
607
- }
608
- }
609
- </style>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Agent 模板库:用户级隔离的模板 CRUD + 实例去向(克隆到了哪些 channel)。
4
+ * v10 可见性:private 仅本人;public 全员可读可用(仅属主可改删);内置(锁)任何人不可改删。
5
+ * admin:全量视图(含他人私有),附创建者;可改删任意非内置模板。
6
+ */
7
+ import { message } from 'ant-design-vue'
8
+ import { useWorkshopApi, type AgentTemplateDto, type HarnessMetaDto } from '../../composables/workshop/useWorkshopApi'
9
+ import { useUserStore } from '../../stores/workshop/user'
10
+
11
+ const { t: tt } = useI18n()
12
+
13
+ /** 内置种子模板按稳定 id 翻译(服务端种子名是中文数据);自建/改名回退原名 */
14
+ const tplName = (r: { id: string, name: string }): string => seedName(tt, r)
15
+
16
+ definePageMeta({ layout: 'default' })
17
+
18
+ const api = useWorkshopApi()
19
+ const userStore = useUserStore()
20
+ const templates = ref<AgentTemplateDto[]>([])
21
+ const loading = ref(false)
22
+ const load = async (): Promise<void> => {
23
+ loading.value = true
24
+ try {
25
+ const res = await api.listTemplates()
26
+ templates.value = (res as unknown as { data?: AgentTemplateDto[] })?.data ?? []
27
+ }
28
+ finally {
29
+ loading.value = false
30
+ }
31
+ }
32
+ // SSR 安全:setup 期 $http(axios)无法在服务端发相对地址请求,拒绝会变成未处理
33
+ // rejection 直杀渲染进程;页面数据一律客户端装载
34
+ if (import.meta.client) void load()
35
+
36
+ // ===== 过滤(全部/我的/公开/内置;admin 另有"他人私有") =====
37
+ type Filter = 'all' | 'mine' | 'public' | 'builtin' | 'others'
38
+ const filter = ref<Filter>('all')
39
+ const filterOptions = computed(() => {
40
+ const opts: Array<{ value: Filter, label: string }> = [
41
+ { value: 'all', label: tt('chips.chipAll', { n: templates.value.length }) },
42
+ { value: 'mine', label: tt('chips.chipMine', { n: templates.value.filter(t => t.ownerUserId === userStore.user?.id).length }) },
43
+ { value: 'public', label: tt('chips.chipPublic', { n: templates.value.filter(t => t.visibility === 'public').length }) },
44
+ { value: 'builtin', label: tt('chips.chipBuiltin', { n: templates.value.filter(t => t.isBuiltin).length }) },
45
+ ]
46
+ if (userStore.isAdmin) {
47
+ opts.push({ value: 'others', label: tt('chips.chipOthers', { n: templates.value.filter(t => t.ownerUserId !== null && t.ownerUserId !== userStore.user?.id && t.visibility === 'private').length }) })
48
+ }
49
+ return opts
50
+ })
51
+ const shown = computed(() => {
52
+ const uid = userStore.user?.id
53
+ switch (filter.value) {
54
+ case 'mine': return templates.value.filter(t => t.ownerUserId === uid)
55
+ case 'public': return templates.value.filter(t => t.visibility === 'public')
56
+ case 'builtin': return templates.value.filter(t => t.isBuiltin)
57
+ case 'others': return templates.value.filter(t => t.ownerUserId !== null && t.ownerUserId !== uid && t.visibility === 'private')
58
+ default: return templates.value
59
+ }
60
+ })
61
+
62
+ // 写权限:属主或 admin;内置/他人公开模板只读
63
+ const canWrite = (t: AgentTemplateDto): boolean =>
64
+ !t.isBuiltin && (t.ownerUserId === userStore.user?.id || userStore.isAdmin)
65
+
66
+ const visTag = (t: AgentTemplateDto): { text: string, color: string, icon?: string } => {
67
+ if (t.isBuiltin) return { text: tt('agents.k3x23c017'), color: 'default', icon: 'i-tabler-lock' }
68
+ if (t.visibility === 'public') return { text: tt('agents.k3wv1t018'), color: 'green' }
69
+ return { text: tt('agents.k447jj019'), color: 'default' }
70
+ }
71
+
72
+ // ===== harness 选项(引擎注册表动态拉取,含环境可用性探测;失败回退静态表) =====
73
+ const harnesses = ref<HarnessMetaDto[]>([])
74
+ const harnessById = computed(() => new Map(harnesses.value.map(h => [h.id, h])))
75
+ const isUnavailable = (id: string): boolean => harnessById.value.get(id)?.available === false
76
+ const harnessOptions = computed(() =>
77
+ (harnesses.value.length > 0
78
+ ? harnesses.value
79
+ : [
80
+ { id: 'mock', label: tt('agents.hMock'), description: '', capabilities: { steer: true, supervise: false, hitl: false, terminal: false, contextStats: false, compact: false } },
81
+ { id: 'omp', label: tt('agents.hOmp'), description: '', capabilities: { steer: true, supervise: true, hitl: true, terminal: true, contextStats: true, compact: true } },
82
+ { id: 'claude', label: 'claude', description: '', capabilities: { steer: false, supervise: false, hitl: false, terminal: false, contextStats: false, compact: false } },
83
+ ] as HarnessMetaDto[]).map((h) => {
84
+ const unavailable = h.available === false
85
+ return { value: h.id, label: unavailable ? `${h.label}(${tt('agents.notInstalled')})` : h.label, disabled: unavailable }
86
+ }),
87
+ )
88
+ const capBadges = (id: string): string[] => {
89
+ const caps = harnesses.value.find(h => h.id === id)?.capabilities
90
+ if (!caps) return []
91
+ const out: string[] = []
92
+ if (caps.steer) out.push('steer')
93
+ if (caps.supervise) out.push('lead')
94
+ if (caps.hitl) out.push('HITL')
95
+ if (caps.compact) out.push('compact')
96
+ return out
97
+ }
98
+ const loadHarnesses = async (): Promise<void> => {
99
+ try {
100
+ const res = await api.listHarnesses()
101
+ const list = (res as unknown as { data?: { harnesses?: HarnessMetaDto[] } })?.data?.harnesses
102
+ if (Array.isArray(list) && list.length > 0) harnesses.value = list
103
+ }
104
+ catch { /* 回退静态表 */ }
105
+ }
106
+ if (import.meta.client) void loadHarnesses()
107
+
108
+ const editOpen = ref(false)
109
+ const editing = ref<AgentTemplateDto | null>(null)
110
+ const form = reactive({ name: '', harness: 'mock', configJson: '{}', visibility: 'private' as 'private' | 'public' })
111
+ const openCreate = (): void => {
112
+ editing.value = null
113
+ form.name = ''
114
+ form.harness = 'mock'
115
+ form.configJson = '{}'
116
+ form.visibility = 'private'
117
+ editOpen.value = true
118
+ }
119
+ const openEdit = (t: AgentTemplateDto): void => {
120
+ editing.value = t
121
+ form.name = t.name
122
+ form.harness = t.harness
123
+ form.configJson = JSON.stringify(t.config ?? {}, null, 2)
124
+ form.visibility = t.visibility
125
+ editOpen.value = true
126
+ }
127
+ const save = async (): Promise<void> => {
128
+ let config: Record<string, unknown> = {}
129
+ try {
130
+ config = JSON.parse(form.configJson || '{}')
131
+ }
132
+ catch {
133
+ message.error(tt('agents.badConfigJson'))
134
+ return
135
+ }
136
+ // 前端兜底:引擎未安装禁止保存(与后端 assertHarnessUsable 同判据)
137
+ if (isUnavailable(form.harness)) {
138
+ message.error(harnessById.value.get(form.harness)?.error ?? tt('agents.notInstalled'))
139
+ return
140
+ }
141
+ try {
142
+ if (editing.value) {
143
+ await api.updateTemplate(editing.value.id, { name: form.name, harness: form.harness, config, visibility: form.visibility })
144
+ message.success(tt('agents.k3n9aij020'))
145
+ }
146
+ else {
147
+ await api.createTemplate({ name: form.name, harness: form.harness, config, visibility: form.visibility })
148
+ message.success(tt('agents.k3n5hak021'))
149
+ }
150
+ editOpen.value = false
151
+ void load()
152
+ }
153
+ catch (e) {
154
+ message.error(apiErrorMessage(e))
155
+ }
156
+ }
157
+ const remove = async (t: AgentTemplateDto): Promise<void> => {
158
+ await api.deleteTemplate(t.id)
159
+ message.success(tt('agents.k3n5sd7022'))
160
+ void load()
161
+ }
162
+
163
+ const toggleEnabled = async (t: AgentTemplateDto): Promise<void> => {
164
+ await api.updateTemplate(t.id, { enabled: t.enabled === 1 ? 0 : 1 })
165
+ void load()
166
+ }
167
+
168
+ /** 一键切换可见性(属主/admin;行内 switch) */
169
+ const toggleVisibility = async (t: AgentTemplateDto, pub: boolean): Promise<void> => {
170
+ try {
171
+ await api.updateTemplate(t.id, { visibility: pub ? 'public' : 'private' })
172
+ message.success(pub ? tt('agents.kxa6cxx023') : tt('agents.k1xxabaf024'))
173
+ void load()
174
+ }
175
+ catch (e) {
176
+ message.error(apiErrorMessage(e))
177
+ }
178
+ }
179
+
180
+ useHead({ title: () => tt('titles.agents') })
181
+ </script>
182
+
183
+ <template>
184
+ <div class="page">
185
+ <div class="head">
186
+ <div>
187
+ <h2>Agent {{ $t('agents.k3pa5h4025') }}</h2>
188
+ <p class="sub">
189
+ {{ $t('agents.k5nsz7y008') }}
190
+ </p>
191
+ </div>
192
+ <a-space>
193
+ <a-button @click="navigateTo('/workshop')">
194
+ {{ $t('agents.krpx6qa009') }}
195
+ </a-button>
196
+ <a-button
197
+ type="primary"
198
+ @click="openCreate"
199
+ >
200
+ {{ $t('agents.k1efixrj010') }}
201
+ </a-button>
202
+ </a-space>
203
+ </div>
204
+
205
+ <div class="toolbar">
206
+ <a-segmented
207
+ v-model:value="filter"
208
+ size="small"
209
+ :options="filterOptions"
210
+ />
211
+ <span
212
+ v-if="userStore.isAdmin"
213
+ class="admin-note"
214
+ ><span class="i-tabler-shield-check" /> {{ $t('agents.ka1gpdj011') }}</span>
215
+ </div>
216
+
217
+ <a-table
218
+ :data-source="shown"
219
+ :loading="loading"
220
+ row-key="id"
221
+ size="small"
222
+ :pagination="false"
223
+ >
224
+ <a-table-column
225
+ :title="$t('agents.k3xhia001')"
226
+ data-index="name"
227
+ >
228
+ <template #default="{ record }">
229
+ <span class="tpl-name">
230
+ <span class="i-tabler-user-square" />
231
+ {{ tplName(record) }}
232
+ </span>
233
+ </template>
234
+ </a-table-column>
235
+ <a-table-column
236
+ title="harness"
237
+ data-index="harness"
238
+ :width="120"
239
+ >
240
+ <template #default="{ record }">
241
+ <span
242
+ class="h-cell"
243
+ :class="{ off: isUnavailable(record.harness) }"
244
+ :title="isUnavailable(record.harness) ? (harnessById.get(record.harness)?.error ?? '') : (harnessById.get(record.harness)?.resolvedPath ?? '')"
245
+ >
246
+ <span class="h-dot" />{{ record.harness }}<span
247
+ v-if="isUnavailable(record.harness)"
248
+ class="h-miss"
249
+ >({{ tt('agents.notInstalled') }})</span>
250
+ </span>
251
+ </template>
252
+ </a-table-column>
253
+ <a-table-column
254
+ :title="$t('agents.k3lrqn0002')"
255
+ :width="120"
256
+ >
257
+ <template #default="{ record }">
258
+ <a-tag
259
+ :color="visTag(record).color"
260
+ class="vis-tag"
261
+ >
262
+ <span
263
+ v-if="visTag(record).icon"
264
+ :class="visTag(record).icon"
265
+ />{{ visTag(record).text }}
266
+ </a-tag>
267
+ <!-- 开关**不带文字**:左边那枚 tag 已经在说"公开/私有"了。
268
+ 两处同时显示同一个词,是同一列里把一条信息讲了两遍(实测桌面版每行都这样)。
269
+ 开关只负责"可切换"这个动作,语义由 tag + title 承载。 -->
270
+ <a-switch
271
+ v-if="!record.isBuiltin && canWrite(record)"
272
+ :checked="record.visibility === 'public'"
273
+ size="small"
274
+ :title="record.visibility === 'public' ? $t('agents.toPrivate') : $t('agents.toPublic')"
275
+ @change="(v: unknown) => toggleVisibility(record, v === true)"
276
+ />
277
+ </template>
278
+ </a-table-column>
279
+ <a-table-column
280
+ :title="$t('agents.k3l98u7003')"
281
+ :width="130"
282
+ >
283
+ <template #default="{ record }">
284
+ <span class="owner">{{ record.ownerName ?? record.ownerUserId?.slice(0, 8) ?? '-' }}</span>
285
+ </template>
286
+ </a-table-column>
287
+ <a-table-column
288
+ :title="$t('agents.k3mr526004')"
289
+ :width="70"
290
+ >
291
+ <template #default="{ record }">
292
+ {{ record.instances.length }}
293
+ </template>
294
+ </a-table-column>
295
+ <a-table-column
296
+ :title="$t('agents.k3xhfg005')"
297
+ :width="70"
298
+ >
299
+ <template #default="{ record }">
300
+ <a-switch
301
+ :checked="record.enabled === 1"
302
+ size="small"
303
+ :disabled="!canWrite(record)"
304
+ @change="toggleEnabled(record)"
305
+ />
306
+ </template>
307
+ </a-table-column>
308
+ <a-table-column
309
+ :title="$t('agents.k40aa6006')"
310
+ :width="130"
311
+ >
312
+ <template #default="{ record }">
313
+ <a-space size="small">
314
+ <a-button
315
+ size="small"
316
+ type="text"
317
+ :disabled="!canWrite(record)"
318
+ :title="record.isBuiltin ? $t('agents.builtinNoEdit') : !canWrite(record) ? $t('agents.ownerOnlyEdit') : $t('common.edit')"
319
+ @click="openEdit(record)"
320
+ >
321
+ {{ $t('agents.k45eb0012') }}
322
+ </a-button>
323
+ <a-popconfirm
324
+ :title="$t('agents.keetzvf007')"
325
+ :disabled="!canWrite(record)"
326
+ @confirm="remove(record)"
327
+ >
328
+ <a-button
329
+ size="small"
330
+ type="text"
331
+ danger
332
+ :disabled="!canWrite(record)"
333
+ :title="record.isBuiltin ? $t('agents.builtinNoDelete') : $t('common.delete')"
334
+ >
335
+ {{ $t('agents.k3xakp013') }}
336
+ </a-button>
337
+ </a-popconfirm>
338
+ </a-space>
339
+ </template>
340
+ </a-table-column>
341
+ <template #expandedRowRender="{ record }">
342
+ <div
343
+ v-for="inst in record.instances"
344
+ :key="inst.id"
345
+ class="inst"
346
+ >
347
+ <a-tag :color="inst.role === 'lead' ? 'gold' : 'blue'">
348
+ {{ inst.role }}
349
+ </a-tag>
350
+ <span class="inst-id">{{ inst.id.slice(0, 8) }}</span>
351
+ <span class="inst-ch">channel {{ inst.channelId.slice(0, 8) }}</span>
352
+ </div>
353
+ <div
354
+ v-if="record.instances.length === 0"
355
+ class="empty"
356
+ >
357
+ {{ $t('agents.k1d036zs014') }}
358
+ </div>
359
+ </template>
360
+ </a-table>
361
+
362
+ <a-modal
363
+ v-model:open="editOpen"
364
+ :title="editing ? $t('agents.editTpl') : $t('agents.newTpl')"
365
+ :ok-text="$t('common.save')"
366
+ :cancel-text="$t('common.cancel')"
367
+ @ok="save"
368
+ >
369
+ <a-form layout="vertical">
370
+ <a-form-item :label="$t('agents.k3xhia001')">
371
+ <a-input v-model:value="form.name" />
372
+ </a-form-item>
373
+ <a-form-item label="harness">
374
+ <a-select
375
+ v-model:value="form.harness"
376
+ :options="harnessOptions"
377
+ >
378
+ <template #option="{ value, label }">
379
+ <span
380
+ class="h-opt"
381
+ :class="{ off: isUnavailable(String(value)) }"
382
+ >
383
+ <span class="h-opt-dot" /> {{ label }}
384
+ </span>
385
+ </template>
386
+ </a-select>
387
+ <div
388
+ v-if="harnesses.length > 0"
389
+ class="harness-status"
390
+ :class="{ off: isUnavailable(form.harness) }"
391
+ >
392
+ <template v-if="isUnavailable(form.harness)">
393
+ <span class="i-tabler-plug-off" /> {{ harnessById.get(form.harness)?.error }}
394
+ <a
395
+ v-if="harnessById.get(form.harness)?.homepage"
396
+ class="h-install"
397
+ :href="harnessById.get(form.harness)?.homepage"
398
+ target="_blank"
399
+ rel="noopener"
400
+ >{{ tt('agents.installLink') }}</a>
401
+ </template>
402
+ <template v-else-if="harnessById.get(form.harness)?.inprocess">
403
+ <span class="i-tabler-plug-connected" /> {{ tt('agents.harnessInprocess') }}
404
+ </template>
405
+ <template v-else-if="harnessById.get(form.harness)?.resolvedPath">
406
+ <span class="i-tabler-plug-connected" /> <span class="mono">{{ harnessById.get(form.harness)?.resolvedPath }}</span>
407
+ </template>
408
+ </div>
409
+ <div
410
+ v-if="capBadges(form.harness).length"
411
+ class="harness-caps"
412
+ >
413
+ <span
414
+ v-for="b in capBadges(form.harness)"
415
+ :key="b"
416
+ class="harness-cap"
417
+ >{{ b }}</span>
418
+ </div>
419
+ </a-form-item>
420
+ <a-form-item :label="$t('agents.k3lrqn0002')">
421
+ <a-radio-group v-model:value="form.visibility">
422
+ <a-radio value="private">
423
+ {{ $t('agents.k17jfge3015') }}
424
+ </a-radio>
425
+ <a-radio value="public">
426
+ {{ $t('agents.k1tt5zyo016') }}
427
+ </a-radio>
428
+ </a-radio-group>
429
+ </a-form-item>
430
+ <a-form-item :label="$t('agents.configLabel')">
431
+ <a-textarea
432
+ v-model:value="form.configJson"
433
+ :rows="6"
434
+ />
435
+ </a-form-item>
436
+ </a-form>
437
+ </a-modal>
438
+ </div>
439
+ </template>
440
+
441
+ <style scoped>
442
+ .harness-caps { margin-top: 6px; display: flex; gap: 4px; flex-wrap: wrap; }
443
+ .harness-cap {
444
+ font-size: 11px;
445
+ line-height: 18px;
446
+ padding: 0 6px;
447
+ border: 1px solid var(--border, #2a3a4a);
448
+ border-radius: 3px;
449
+ opacity: 0.75;
450
+ }
451
+ .h-opt { display: inline-flex; align-items: center; gap: 7px; }
452
+ .h-opt-dot { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--tone-success-dot, #4a6b57); }
453
+ .h-opt.off .h-opt-dot { background: var(--tone-danger-dot, #c25a4e); }
454
+ .h-opt.off { opacity: 0.6; }
455
+ .h-install { margin-left: 8px; color: var(--accent); text-decoration: none; }
456
+ .h-install:hover { text-decoration: underline; }
457
+ .harness-status {
458
+ display: flex;
459
+ gap: 6px;
460
+ align-items: center;
461
+ margin-top: 6px;
462
+ font-size: 11px;
463
+ color: var(--ink-faint);
464
+ }
465
+ .harness-status.off { color: var(--danger, #c25a4e); }
466
+ .h-cell {
467
+ display: inline-flex;
468
+ gap: 6px;
469
+ align-items: center;
470
+ font-family: var(--font-mono);
471
+ font-size: 12px;
472
+ }
473
+ .h-dot {
474
+ width: 6px;
475
+ height: 6px;
476
+ border-radius: 50%;
477
+ background: var(--ok, #4a6b57);
478
+ }
479
+ .h-cell.off .h-dot { background: var(--danger, #c25a4e); }
480
+ .h-cell.off { opacity: 0.75; }
481
+ .h-miss { color: var(--danger, #c25a4e); font-family: var(--font-sans); font-size: 11px; }
482
+ .page { padding: 4px; }
483
+ .head {
484
+ display: flex;
485
+ align-items: center;
486
+ justify-content: space-between;
487
+ margin-bottom: 12px;
488
+ }
489
+ h2 { margin: 0 0 4px; }
490
+ .sub { margin: 0; font-size: 12px; opacity: 0.55; }
491
+ .toolbar {
492
+ display: flex;
493
+ gap: 12px;
494
+ align-items: center;
495
+ margin-bottom: 12px;
496
+ }
497
+ .admin-note {
498
+ display: inline-flex;
499
+ gap: 5px;
500
+ align-items: center;
501
+ font-size: 11px;
502
+ color: var(--ink-faint);
503
+ }
504
+ .tpl-name {
505
+ display: inline-flex;
506
+ gap: 6px;
507
+ align-items: center;
508
+ }
509
+ .vis-tag {
510
+ margin-right: 8px;
511
+ }
512
+ .owner { font-size: 12px; color: var(--ink-soft); }
513
+ .inst {
514
+ display: flex;
515
+ gap: 8px;
516
+ align-items: center;
517
+ padding: 2px 0;
518
+ font-size: 12px;
519
+ }
520
+ .inst-id,
521
+ .inst-ch { font-family: var(--font-mono); opacity: 0.6; }
522
+ .empty { padding: 6px 0; font-size: 12px; opacity: 0.4; }
523
+
524
+ /* ══ 窄屏(v9):页头纵向堆叠 / 筛选条换行 / 表格横向卷轴 + 首列可读 ═════════ */
525
+ @media (max-width: 900px) {
526
+ .head {
527
+ flex-direction: column;
528
+ align-items: stretch;
529
+ gap: 12px;
530
+ }
531
+
532
+ .head > div { min-width: 0; }
533
+ .head h2 { font-size: 21px; line-height: 1.25; }
534
+
535
+ .head :deep(.ant-space) {
536
+ display: flex;
537
+ flex-direction: column;
538
+ gap: 8px;
539
+ align-items: stretch;
540
+ width: 100%;
541
+ }
542
+
543
+ .head :deep(.ant-space-item) { width: 100%; }
544
+ .head :deep(.ant-btn) { width: 100%; min-height: 40px; }
545
+
546
+ .toolbar {
547
+ flex-wrap: wrap;
548
+ gap: 8px;
549
+ }
550
+
551
+ .toolbar :deep(.ant-segmented) {
552
+ flex: 1 1 100%;
553
+ min-width: 0;
554
+ }
555
+
556
+ .admin-note {
557
+ flex: 1 1 100%;
558
+ min-width: 0;
559
+ font-size: 11.5px;
560
+ line-height: 1.5;
561
+ }
562
+
563
+ /* 整表给出可读下限:横向卷轴交给全局 v5 的 .ant-table-content */
564
+ .page :deep(.ant-table-content) table { min-width: 828px; }
565
+
566
+ /* 有 expandedRowRender 时 antd 会把"展开图标列"放在第一列,而全局 v5 钉住的正是第一列。
567
+ auto 布局下没有显式宽度的列会吃掉全部余量 → 展开列白占 ~115px,真正的身份列还会被卷走。
568
+ 这里把展开列收成 44px,并让身份列(模板名)紧随其后一起钉住:横扫时始终知道这一行是谁。 */
569
+ .page :deep(.ant-table colgroup col:first-child) { width: 44px; }
570
+
571
+ .page :deep(.ant-table-thead > tr > th:first-child),
572
+ .page :deep(.ant-table-tbody > tr > td:first-child) {
573
+ width: 44px;
574
+ min-width: 44px;
575
+ padding-right: 2px;
576
+ padding-left: 6px;
577
+ }
578
+
579
+ .page :deep(.ant-table-thead > tr > th:nth-child(2)),
580
+ .page :deep(.ant-table-tbody > tr > td:nth-child(2)) {
581
+ position: sticky;
582
+ left: 44px;
583
+ z-index: 2;
584
+ min-width: 132px;
585
+ background: var(--paper-raised);
586
+ box-shadow: 1px 0 0 var(--line);
587
+ }
588
+
589
+ .page :deep(.ant-table-thead > tr > th:nth-child(2)) { z-index: 3; }
590
+ }
591
+
592
+ @media (max-width: 640px) {
593
+ .page { padding: 0; }
594
+ .head h2 { font-size: 19px; }
595
+ .sub { font-size: 11.5px; line-height: 1.5; }
596
+ .tpl-name { min-width: 120px; }
597
+ .page :deep(.ant-table) .ant-btn-sm { min-height: 34px; }
598
+ }
599
+
600
+ /* 触摸命中区:antd 小开关本体只有 28×16(手指点不中),视觉尺寸保持不变,
601
+ 用伪元素把命中区外扩到 ~40×40 —— 与全局 v5 给展开图标做的事同一手法。
602
+ 行内没有相邻可点元素(可见性列是 tag+switch,启用列只有 switch),不会误伤。 */
603
+ @media (max-width: 900px) {
604
+ .page :deep(.ant-switch) { position: relative; }
605
+
606
+ .page :deep(.ant-switch)::after {
607
+ position: absolute;
608
+ inset: -12px -6px;
609
+ content: '';
610
+ }
611
+ }
612
+ </style>