mulmoclaude 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (486) hide show
  1. package/Dockerfile.sandbox +100 -0
  2. package/README.md +17 -4
  3. package/bin/mulmoclaude.js +46 -15
  4. package/bin/prepare-dist.js +18 -2
  5. package/client/assets/chunk-CernVdwh.js +1 -0
  6. package/client/assets/chunk-D8eiyYIV-C1eAZMzz.js +1 -0
  7. package/client/assets/html2canvas-CDGcmOD3-BbPeutDg.js +5 -0
  8. package/client/assets/index-BbgSjFQ8.js +4968 -0
  9. package/client/assets/index-ECD0lgIv.css +2 -0
  10. package/client/assets/{index.es-D4YyL_Dg-BgT6a3Nd.js → index.es-DqtpmBm8-DJdTPdnc.js} +5 -5
  11. package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
  12. package/client/assets/runtime-protocol-vue-6WYa8hAs.js +1 -0
  13. package/client/assets/runtime-vue-BVUzgYGA.js +1 -0
  14. package/client/assets/typeof-DBp4T-Ny-C2xoZtcz.js +1 -0
  15. package/client/assets/vue-1e_vz2LW.js +1 -0
  16. package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +4 -0
  17. package/client/index.html +33 -2
  18. package/package.json +20 -18
  19. package/sandbox-entrypoint.sh +106 -0
  20. package/server/accounting/accountNormalize.ts +32 -0
  21. package/server/accounting/defaultAccounts.ts +87 -0
  22. package/server/accounting/eventPublisher.ts +51 -0
  23. package/server/accounting/journal.ts +252 -0
  24. package/server/accounting/openingBalances.ts +114 -0
  25. package/server/accounting/report.ts +237 -0
  26. package/server/accounting/service.ts +718 -0
  27. package/server/accounting/snapshotCache.ts +333 -0
  28. package/server/accounting/timeSeries.ts +265 -0
  29. package/server/accounting/types.ts +148 -0
  30. package/server/agent/activeTools.ts +128 -0
  31. package/server/agent/attachmentConverter.ts +10 -5
  32. package/server/agent/backend/claude-code.ts +8 -2
  33. package/server/agent/backend/types.ts +1 -1
  34. package/server/agent/config.ts +101 -31
  35. package/server/agent/index.ts +45 -33
  36. package/server/agent/mcp-server.ts +146 -69
  37. package/server/agent/mcp-tools/index.ts +1 -5
  38. package/server/agent/mcp-tools/notify.ts +2 -22
  39. package/server/agent/mcp-tools/x.ts +0 -4
  40. package/server/agent/mcpHealth.ts +168 -0
  41. package/server/agent/plugin-names.ts +20 -77
  42. package/server/agent/prompt.ts +259 -51
  43. package/server/agent/resumeFailover.ts +1 -1
  44. package/server/agent/stream.ts +0 -1
  45. package/server/api/auth/bearerAuth.ts +5 -5
  46. package/server/api/csrfGuard.ts +1 -1
  47. package/server/api/routes/accounting.ts +366 -0
  48. package/server/api/routes/agent.ts +509 -46
  49. package/server/api/routes/attachment.ts +104 -0
  50. package/server/api/routes/chart.ts +2 -1
  51. package/server/api/routes/config.ts +12 -12
  52. package/server/api/routes/files.ts +105 -48
  53. package/server/api/routes/image.ts +70 -25
  54. package/server/api/routes/journal.ts +35 -0
  55. package/server/api/routes/mulmo-script.ts +358 -118
  56. package/server/api/routes/mulmoScriptValidate.ts +1 -1
  57. package/server/api/routes/news.ts +1 -1
  58. package/server/api/routes/notifications.ts +92 -22
  59. package/server/api/routes/notifier.ts +98 -0
  60. package/server/api/routes/pdf.ts +188 -48
  61. package/server/api/routes/plugins.ts +34 -14
  62. package/server/api/routes/presentHtml.ts +58 -3
  63. package/server/api/routes/roles.ts +1 -8
  64. package/server/api/routes/runtime-plugin.ts +224 -0
  65. package/server/api/routes/scheduler.ts +7 -5
  66. package/server/api/routes/schedulerHandlers.ts +1 -1
  67. package/server/api/routes/schedulerTasks.ts +8 -7
  68. package/server/api/routes/sessions.ts +234 -121
  69. package/server/api/routes/skills.ts +56 -51
  70. package/server/api/routes/sources.ts +52 -45
  71. package/server/api/routes/translation.ts +44 -0
  72. package/server/api/routes/wiki/frontmatter.ts +13 -65
  73. package/server/api/routes/wiki/history.ts +261 -0
  74. package/server/api/routes/wiki/pageIndex.ts +1 -1
  75. package/server/api/routes/wiki.ts +50 -26
  76. package/server/events/file-change.ts +83 -0
  77. package/server/events/notifications.ts +247 -91
  78. package/server/events/pub-sub/index.ts +1 -1
  79. package/server/events/relay-client.ts +5 -5
  80. package/server/events/scheduler-adapter.ts +2 -2
  81. package/server/events/session-store/index.ts +110 -22
  82. package/server/events/task-manager/index.ts +10 -9
  83. package/server/index.ts +509 -33
  84. package/server/notifier/engine.ts +419 -0
  85. package/server/notifier/legacy-adapters.ts +76 -0
  86. package/server/notifier/runtime-api.ts +74 -0
  87. package/server/notifier/store.ts +70 -0
  88. package/server/notifier/types.ts +121 -0
  89. package/server/plugins/dev-loader.ts +171 -0
  90. package/server/plugins/dev-watcher.ts +150 -0
  91. package/server/plugins/diagnostics.ts +188 -0
  92. package/server/plugins/preset-list.ts +52 -0
  93. package/server/plugins/preset-loader.ts +112 -0
  94. package/server/plugins/runtime-chat-api.ts +38 -0
  95. package/server/plugins/runtime-loader.ts +430 -0
  96. package/server/plugins/runtime-registry.ts +112 -0
  97. package/server/plugins/runtime-tasks-api.ts +50 -0
  98. package/server/plugins/runtime.ts +378 -0
  99. package/server/services/translation/cache.ts +72 -0
  100. package/server/services/translation/index.ts +106 -0
  101. package/server/services/translation/llm.ts +140 -0
  102. package/server/services/translation/types.ts +35 -0
  103. package/server/system/credentials.ts +13 -2
  104. package/server/system/env.ts +6 -1
  105. package/server/system/logger/formatters.ts +46 -4
  106. package/server/system/logger/index.ts +4 -4
  107. package/server/system/logger/sinks.ts +26 -5
  108. package/server/system/logger/types.ts +2 -2
  109. package/server/utils/dev-plugin-args.d.mts +11 -0
  110. package/server/utils/dev-plugin-args.mjs +43 -0
  111. package/server/utils/errors.ts +13 -4
  112. package/server/utils/files/accounting-io.ts +295 -0
  113. package/server/utils/files/atomic.ts +17 -49
  114. package/server/utils/files/attachment-store.ts +182 -0
  115. package/server/utils/files/html-io.ts +1 -7
  116. package/server/utils/files/html-store.ts +19 -0
  117. package/server/utils/files/image-store.ts +20 -22
  118. package/server/utils/files/index.ts +5 -15
  119. package/server/utils/files/journal-io.ts +7 -35
  120. package/server/utils/files/json.ts +2 -29
  121. package/server/utils/files/markdown-image-fill.ts +6 -37
  122. package/server/utils/files/markdown-store.ts +6 -21
  123. package/server/utils/files/naming.ts +3 -39
  124. package/server/utils/files/plugins-io.ts +100 -0
  125. package/server/utils/files/reference-dirs-io.ts +1 -9
  126. package/server/utils/files/roles-io.ts +2 -10
  127. package/server/utils/files/safe.ts +17 -19
  128. package/server/utils/files/scheduler-io.ts +1 -7
  129. package/server/utils/files/scheduler-overrides-io.ts +3 -12
  130. package/server/utils/files/session-io.ts +21 -30
  131. package/server/utils/files/spreadsheet-store.ts +9 -22
  132. package/server/utils/files/translation-io.ts +46 -0
  133. package/server/utils/files/user-tasks-io.ts +1 -7
  134. package/server/utils/files/workspace-io.ts +3 -79
  135. package/server/utils/gemini.ts +33 -11
  136. package/server/utils/html/htmlArtifactSplicer.ts +41 -0
  137. package/server/utils/markdown/frontmatter.ts +112 -0
  138. package/server/utils/regex.ts +56 -0
  139. package/server/utils/router.ts +41 -0
  140. package/server/utils/slug.ts +5 -3
  141. package/server/utils/time.ts +12 -0
  142. package/server/workspace/chat-index/indexer.ts +15 -2
  143. package/server/workspace/chat-index/summarizer.ts +1 -1
  144. package/server/workspace/custom-dirs.ts +1 -1
  145. package/server/workspace/helps/gemini.md +1 -1
  146. package/server/workspace/helps/guide.md +61 -0
  147. package/server/workspace/helps/index.md +4 -0
  148. package/server/workspace/helps/presenthtml.md +80 -0
  149. package/server/workspace/helps/sandbox.md +7 -0
  150. package/server/workspace/helps/storyteller.md +101 -0
  151. package/server/workspace/helps/telegram.md +1 -0
  152. package/server/workspace/helps/wiki.md +9 -7
  153. package/server/workspace/journal/archivist-cli.ts +7 -33
  154. package/server/workspace/journal/archivist-schemas.ts +5 -43
  155. package/server/workspace/journal/dailyPass.ts +34 -187
  156. package/server/workspace/journal/diff.ts +3 -28
  157. package/server/workspace/journal/index.ts +10 -81
  158. package/server/workspace/journal/indexFile.ts +3 -24
  159. package/server/workspace/journal/latestDaily.ts +51 -0
  160. package/server/workspace/journal/memoryExtractor.ts +4 -20
  161. package/server/workspace/journal/optimizationPass.ts +4 -21
  162. package/server/workspace/journal/paths.ts +4 -23
  163. package/server/workspace/journal/state.ts +6 -29
  164. package/server/workspace/memory/io.ts +213 -0
  165. package/server/workspace/memory/llm-classifier.ts +158 -0
  166. package/server/workspace/memory/migrate.ts +263 -0
  167. package/server/workspace/memory/run.ts +84 -0
  168. package/server/workspace/memory/topic-cluster.ts +218 -0
  169. package/server/workspace/memory/topic-detect.ts +67 -0
  170. package/server/workspace/memory/topic-index-hook.ts +128 -0
  171. package/server/workspace/memory/topic-io.ts +180 -0
  172. package/server/workspace/memory/topic-migrate.ts +248 -0
  173. package/server/workspace/memory/topic-run.ts +172 -0
  174. package/server/workspace/memory/topic-swap.ts +135 -0
  175. package/server/workspace/memory/topic-types.ts +142 -0
  176. package/server/workspace/memory/types.ts +83 -0
  177. package/server/workspace/news/reader.ts +4 -5
  178. package/server/workspace/paths.ts +124 -47
  179. package/server/workspace/roles.ts +2 -11
  180. package/server/workspace/skills/parser.ts +38 -55
  181. package/server/workspace/skills/user-tasks.ts +1 -2
  182. package/server/workspace/skills-preset/mc-library/SKILL.md +188 -0
  183. package/server/workspace/skills-preset.ts +196 -0
  184. package/server/workspace/sources/fetchers/githubIssues.ts +13 -11
  185. package/server/workspace/sources/fetchers/index.ts +1 -1
  186. package/server/workspace/sources/fetchers/rssParser.ts +1 -1
  187. package/server/workspace/sources/pipeline/index.ts +2 -2
  188. package/server/workspace/sources/pipeline/notify.ts +3 -3
  189. package/server/workspace/sources/pipeline/write.ts +2 -2
  190. package/server/workspace/sources/registry.ts +39 -61
  191. package/server/workspace/sources/robots.ts +1 -1
  192. package/server/workspace/tool-trace/classify.ts +2 -1
  193. package/server/workspace/tool-trace/index.ts +1 -1
  194. package/server/workspace/tool-trace/writeSearch.ts +6 -1
  195. package/server/workspace/wiki-backlinks/index.ts +19 -7
  196. package/server/workspace/wiki-backlinks/sessionBacklinks.ts +1 -0
  197. package/server/workspace/wiki-history/hook/snapshot.mjs +98 -0
  198. package/server/workspace/wiki-history/hook/snapshot.ts +135 -0
  199. package/server/workspace/wiki-history/provision.ts +181 -0
  200. package/server/workspace/wiki-pages/io.ts +217 -0
  201. package/server/workspace/wiki-pages/snapshot.ts +380 -0
  202. package/server/workspace/workspace.ts +75 -13
  203. package/src/App.vue +115 -40
  204. package/src/_runtime/protocol-vue.ts +21 -0
  205. package/src/_runtime/vue.ts +22 -0
  206. package/src/components/ChatInput.vue +14 -10
  207. package/src/components/CopyChatButton.vue +76 -0
  208. package/src/components/FileContentRenderer.vue +67 -14
  209. package/src/components/FileTree.vue +2 -2
  210. package/src/components/FilesView.vue +17 -1
  211. package/src/components/NewsView.vue +16 -2
  212. package/src/components/NotificationBell.vue +320 -93
  213. package/src/components/PageChatComposer.vue +5 -4
  214. package/src/components/PluginLauncher.vue +42 -6
  215. package/src/components/PluginScopedRoot.vue +87 -0
  216. package/src/components/RoleSelector.vue +12 -1
  217. package/src/components/RolesView.vue +562 -0
  218. package/src/components/SentAttachmentChip.vue +102 -0
  219. package/src/components/SessionHistoryPanel.vue +109 -20
  220. package/src/components/SessionRoleIcon.vue +7 -4
  221. package/src/components/SessionSidebar.vue +20 -7
  222. package/src/components/SessionTabBar.vue +1 -1
  223. package/src/components/SettingsMcpTab.vue +4 -4
  224. package/src/components/SettingsModal.vue +2 -0
  225. package/src/components/SidebarHeader.vue +16 -5
  226. package/src/components/SourcesManager.vue +23 -9
  227. package/src/components/SourcesView.vue +1 -1
  228. package/src/components/StackView.vue +102 -6
  229. package/src/components/SuggestionsPanel.vue +105 -16
  230. package/src/components/SystemFileBanner.vue +1 -1
  231. package/src/components/TodoExplorer.vue +4 -5
  232. package/src/components/todo/TodoAddDialog.vue +2 -3
  233. package/src/components/todo/TodoEditDialog.vue +1 -2
  234. package/src/components/todo/TodoEditPanel.vue +2 -3
  235. package/src/components/todo/TodoKanbanView.vue +8 -5
  236. package/src/components/todo/TodoListView.vue +3 -5
  237. package/src/components/todo/TodoTableView.vue +7 -5
  238. package/src/composables/useAccountingChannel.ts +58 -0
  239. package/src/composables/useActiveSession.ts +4 -25
  240. package/src/composables/useAppApi.ts +6 -44
  241. package/src/composables/useClipboardCopy.ts +3 -20
  242. package/src/composables/useContentDisplay.ts +33 -2
  243. package/src/composables/useDevPluginReload.ts +23 -0
  244. package/src/composables/useDynamicFavicon.ts +5 -31
  245. package/src/composables/useEventListeners.ts +0 -20
  246. package/src/composables/useExpandedDirs.ts +4 -15
  247. package/src/composables/useFaviconState.ts +12 -46
  248. package/src/composables/useFileChange.ts +53 -0
  249. package/src/composables/useFreshPluginData.ts +6 -43
  250. package/src/composables/useHealth.ts +14 -43
  251. package/src/composables/useImageErrorRepair.ts +104 -0
  252. package/src/composables/useLatestDaily.ts +40 -0
  253. package/src/composables/useMarkdownDoc.ts +39 -0
  254. package/src/composables/useMarkdownLinkHandler.ts +1 -1
  255. package/src/composables/useMcpTools.ts +3 -16
  256. package/src/composables/useNotifications.ts +138 -112
  257. package/src/composables/usePdfDownload.ts +17 -3
  258. package/src/composables/usePendingCalls.ts +8 -26
  259. package/src/composables/usePluginErrorBoundary.ts +68 -0
  260. package/src/composables/usePubSub.ts +9 -17
  261. package/src/composables/useRunElapsed.ts +5 -22
  262. package/src/composables/useSandboxStatus.ts +4 -20
  263. package/src/composables/useSessionDerived.ts +7 -15
  264. package/src/composables/useSessionHistory.ts +70 -29
  265. package/src/composables/useSessionSync.ts +25 -3
  266. package/src/composables/useSkillsList.ts +59 -0
  267. package/src/composables/useTranslatedQueries.ts +109 -0
  268. package/src/config/apiRoutes.ts +181 -80
  269. package/src/config/historyFilters.ts +5 -3
  270. package/src/config/hostEvents.ts +17 -0
  271. package/src/config/mcpCatalog.ts +277 -5
  272. package/src/config/pubsubChannels.ts +134 -12
  273. package/src/config/roles.ts +212 -147
  274. package/src/config/systemFileDescriptors.ts +5 -5
  275. package/src/config/toolNames.ts +52 -30
  276. package/src/config/workspacePaths.ts +26 -2
  277. package/src/lang/de.ts +483 -27
  278. package/src/lang/en.ts +448 -27
  279. package/src/lang/es.ts +474 -27
  280. package/src/lang/fr.ts +476 -27
  281. package/src/lang/ja.ts +465 -27
  282. package/src/lang/ko.ts +466 -27
  283. package/src/lang/pt-BR.ts +473 -27
  284. package/src/lang/zh.ts +463 -27
  285. package/src/lib/vue-i18n.ts +1 -1
  286. package/src/lib/wiki-page/slug.ts +66 -0
  287. package/src/main.ts +85 -0
  288. package/src/plugins/_extras.ts +58 -0
  289. package/src/plugins/_generated/metas.ts +42 -0
  290. package/src/plugins/_generated/registrations.ts +44 -0
  291. package/src/plugins/_generated/server-bindings.ts +47 -0
  292. package/src/plugins/accounting/Preview.vue +106 -0
  293. package/src/plugins/accounting/View.vue +632 -0
  294. package/src/plugins/accounting/actions.ts +34 -0
  295. package/src/plugins/accounting/api.ts +301 -0
  296. package/src/plugins/accounting/components/AccountEditor.vue +250 -0
  297. package/src/plugins/accounting/components/AccountRow.vue +50 -0
  298. package/src/plugins/accounting/components/AccountsList.vue +102 -0
  299. package/src/plugins/accounting/components/AccountsModal.vue +300 -0
  300. package/src/plugins/accounting/components/BalanceSheet.vue +186 -0
  301. package/src/plugins/accounting/components/BookSettings.vue +284 -0
  302. package/src/plugins/accounting/components/BookSwitcher.vue +78 -0
  303. package/src/plugins/accounting/components/DateRangePicker.vue +140 -0
  304. package/src/plugins/accounting/components/JournalEntryForm.vue +504 -0
  305. package/src/plugins/accounting/components/JournalList.vue +553 -0
  306. package/src/plugins/accounting/components/Ledger.vue +206 -0
  307. package/src/plugins/accounting/components/NewBookForm.vue +211 -0
  308. package/src/plugins/accounting/components/OpeningBalancesForm.vue +271 -0
  309. package/src/plugins/accounting/components/ProfitLoss.vue +160 -0
  310. package/src/plugins/accounting/components/accountDraft.ts +13 -0
  311. package/src/plugins/accounting/components/accountNumbering.ts +103 -0
  312. package/src/plugins/accounting/components/accountValidation.ts +75 -0
  313. package/src/plugins/accounting/components/useLatestRequest.ts +44 -0
  314. package/src/plugins/accounting/countries.ts +158 -0
  315. package/src/plugins/accounting/currencies.ts +64 -0
  316. package/src/plugins/accounting/dates.ts +51 -0
  317. package/src/plugins/accounting/definition.ts +199 -0
  318. package/src/plugins/accounting/fiscalYear.ts +136 -0
  319. package/src/plugins/accounting/index.ts +49 -0
  320. package/src/plugins/accounting/meta.ts +91 -0
  321. package/src/plugins/accounting/timeSeriesEnums.ts +16 -0
  322. package/src/plugins/api.ts +125 -0
  323. package/src/plugins/canvas/View.vue +38 -28
  324. package/src/plugins/canvas/definition.ts +10 -8
  325. package/src/plugins/canvas/index.ts +15 -8
  326. package/src/plugins/canvas/meta.ts +12 -0
  327. package/src/plugins/chart/Preview.vue +1 -1
  328. package/src/plugins/chart/View.vue +2 -2
  329. package/src/plugins/chart/definition.ts +12 -2
  330. package/src/plugins/chart/index.ts +15 -7
  331. package/src/plugins/chart/meta.ts +18 -0
  332. package/src/plugins/editImages/definition.ts +44 -0
  333. package/src/plugins/editImages/index.ts +43 -0
  334. package/src/plugins/editImages/meta.ts +5 -0
  335. package/src/plugins/generateImage/View.vue +3 -1
  336. package/src/plugins/generateImage/definition.ts +2 -0
  337. package/src/plugins/generateImage/index.ts +13 -5
  338. package/src/plugins/generateImage/meta.ts +5 -0
  339. package/src/plugins/index.ts +35 -0
  340. package/src/plugins/manageRoles/Preview.vue +7 -4
  341. package/src/plugins/manageRoles/View.vue +12 -8
  342. package/src/plugins/manageRoles/definition.ts +6 -0
  343. package/src/plugins/manageRoles/index.ts +7 -6
  344. package/src/plugins/manageSkills/View.vue +11 -7
  345. package/src/plugins/manageSkills/definition.ts +4 -1
  346. package/src/plugins/manageSkills/index.ts +14 -7
  347. package/src/plugins/manageSkills/meta.ts +21 -0
  348. package/src/plugins/manageSource/definition.ts +4 -1
  349. package/src/plugins/manageSource/index.ts +15 -7
  350. package/src/plugins/manageSource/meta.ts +21 -0
  351. package/src/plugins/markdown/Preview.vue +10 -8
  352. package/src/plugins/markdown/View.vue +84 -17
  353. package/src/plugins/markdown/definition.ts +7 -1
  354. package/src/plugins/markdown/index.ts +15 -8
  355. package/src/plugins/markdown/meta.ts +16 -0
  356. package/src/plugins/meta-types.ts +97 -0
  357. package/src/plugins/metas.ts +224 -0
  358. package/src/plugins/presentForm/Preview.vue +4 -15
  359. package/src/plugins/presentForm/View.vue +35 -78
  360. package/src/plugins/presentForm/definition.ts +7 -6
  361. package/src/plugins/presentForm/index.ts +12 -5
  362. package/src/plugins/presentForm/meta.ts +11 -0
  363. package/src/plugins/presentForm/plugin.ts +8 -9
  364. package/src/plugins/presentForm/types.ts +0 -24
  365. package/src/plugins/presentHtml/Preview.vue +1 -8
  366. package/src/plugins/presentHtml/View.vue +401 -30
  367. package/src/plugins/presentHtml/definition.ts +8 -5
  368. package/src/plugins/presentHtml/index.ts +15 -8
  369. package/src/plugins/presentHtml/meta.ts +14 -0
  370. package/src/plugins/presentMulmoScript/View.vue +327 -107
  371. package/src/plugins/presentMulmoScript/definition.ts +34 -7
  372. package/src/plugins/presentMulmoScript/helpers.ts +4 -5
  373. package/src/plugins/presentMulmoScript/index.ts +20 -7
  374. package/src/plugins/presentMulmoScript/meta.ts +52 -0
  375. package/src/plugins/scheduler/AutomationsPreview.vue +2 -8
  376. package/src/plugins/scheduler/Preview.vue +5 -2
  377. package/src/plugins/scheduler/TasksTab.vue +16 -36
  378. package/src/plugins/scheduler/View.vue +22 -54
  379. package/src/plugins/scheduler/automationsDefinition.ts +14 -9
  380. package/src/plugins/scheduler/automationsMeta.ts +5 -0
  381. package/src/plugins/scheduler/calendarDefinition.ts +4 -7
  382. package/src/plugins/scheduler/calendarMeta.ts +28 -0
  383. package/src/plugins/scheduler/formatSchedule.ts +6 -24
  384. package/src/plugins/scheduler/index.ts +26 -52
  385. package/src/plugins/scope.ts +57 -0
  386. package/src/plugins/server-bindings-types.ts +38 -0
  387. package/src/plugins/server.ts +32 -0
  388. package/src/plugins/skill/Preview.vue +25 -0
  389. package/src/plugins/skill/View.vue +125 -0
  390. package/src/plugins/skill/definition.ts +23 -0
  391. package/src/plugins/skill/index.ts +36 -0
  392. package/src/plugins/skill/plugin.ts +31 -0
  393. package/src/plugins/skill/types.ts +21 -0
  394. package/src/plugins/spreadsheet/Preview.vue +1 -3
  395. package/src/plugins/spreadsheet/View.vue +29 -49
  396. package/src/plugins/spreadsheet/cellHighlights.ts +2 -3
  397. package/src/plugins/spreadsheet/definition.ts +5 -2
  398. package/src/plugins/spreadsheet/index.ts +15 -8
  399. package/src/plugins/spreadsheet/keyboardNav.ts +38 -0
  400. package/src/plugins/spreadsheet/meta.ts +14 -0
  401. package/src/plugins/textResponse/Preview.vue +9 -1
  402. package/src/plugins/textResponse/View.vue +59 -8
  403. package/src/plugins/textResponse/index.ts +11 -3
  404. package/src/plugins/textResponse/plugin.ts +8 -10
  405. package/src/plugins/textResponse/types.ts +28 -0
  406. package/src/plugins/wiki/Preview.vue +6 -4
  407. package/src/plugins/wiki/View.vue +463 -254
  408. package/src/plugins/wiki/components/WikiPageBody.vue +159 -0
  409. package/src/plugins/wiki/helpers.ts +17 -0
  410. package/src/plugins/wiki/history/HistoryDetail.vue +325 -0
  411. package/src/plugins/wiki/history/HistoryTab.vue +167 -0
  412. package/src/plugins/wiki/history/RestoreConfirm.vue +63 -0
  413. package/src/plugins/wiki/history/api.ts +52 -0
  414. package/src/plugins/wiki/history/diff.ts +145 -0
  415. package/src/plugins/wiki/index.ts +42 -32
  416. package/src/plugins/wiki/meta.ts +10 -0
  417. package/src/plugins/wiki/pageEditLoader.ts +53 -0
  418. package/src/plugins/wiki/route.ts +8 -0
  419. package/src/router/guards.ts +2 -1
  420. package/src/router/index.ts +19 -0
  421. package/src/router/pageRoutes.ts +1 -0
  422. package/src/tools/index.ts +50 -51
  423. package/src/tools/runtimeLoader.ts +141 -0
  424. package/src/tools/types.ts +44 -1
  425. package/src/types/notification.ts +23 -0
  426. package/src/types/pastedFile.ts +10 -0
  427. package/src/types/session.ts +61 -3
  428. package/src/types/sse.ts +21 -6
  429. package/src/utils/agent/eventDispatch.ts +12 -9
  430. package/src/utils/agent/pastedAttachment.ts +35 -0
  431. package/src/utils/agent/request.ts +32 -3
  432. package/src/utils/agent/toolCalls.ts +7 -1
  433. package/src/utils/api.ts +1 -1
  434. package/src/utils/chat/exportMarkdown.ts +243 -0
  435. package/src/utils/errors.ts +10 -2
  436. package/src/utils/files/expandedDirs.ts +1 -1
  437. package/src/utils/filesPreview/todoPreview.ts +13 -2
  438. package/src/utils/format/date.ts +1 -3
  439. package/src/utils/format/jsonSyntax.ts +5 -0
  440. package/src/utils/html/iframeHeightReporterScript.ts +62 -0
  441. package/src/utils/html/previewCsp.ts +29 -2
  442. package/src/utils/image/htmlSrcAttrs.ts +122 -0
  443. package/src/utils/image/imageRepairInlineScript.ts +115 -0
  444. package/src/utils/image/resolve.ts +17 -3
  445. package/src/utils/image/rewriteMarkdownImageRefs.ts +62 -9
  446. package/src/utils/markdown/frontmatter.ts +125 -0
  447. package/src/utils/markdown/taskList.ts +7 -2
  448. package/src/utils/plugin/runtime.ts +132 -0
  449. package/src/utils/session/mergeSessions.ts +40 -37
  450. package/src/utils/session/sessionEntries.ts +74 -18
  451. package/src/utils/session/sessionHelpers.ts +54 -10
  452. package/src/utils/tools/result.ts +76 -14
  453. package/src/vite-env.d.ts +6 -0
  454. package/client/assets/html2canvas-Cx501zZr-Bug0qRNv.js +0 -5
  455. package/client/assets/index-CY-WpQUm.css +0 -2
  456. package/client/assets/index-DbTz2Mfs.js +0 -4911
  457. package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
  458. package/server/api/routes/html.ts +0 -114
  459. package/server/api/routes/todos.ts +0 -293
  460. package/server/api/routes/todosColumnsHandlers.ts +0 -333
  461. package/server/api/routes/todosHandlers.ts +0 -274
  462. package/server/api/routes/todosItemsHandlers.ts +0 -386
  463. package/server/utils/files/todos-io.ts +0 -29
  464. package/src/components/NotificationToast.vue +0 -75
  465. package/src/plugins/editImage/definition.ts +0 -27
  466. package/src/plugins/editImage/index.ts +0 -37
  467. package/src/plugins/presentHtml/helpers.ts +0 -72
  468. package/src/plugins/scheduler/LegacySchedulerView.vue +0 -32
  469. package/src/plugins/scheduler/legacyShape.ts +0 -34
  470. package/src/plugins/todo/Preview.vue +0 -68
  471. package/src/plugins/todo/View.vue +0 -378
  472. package/src/plugins/todo/composables/useTodos.ts +0 -179
  473. package/src/plugins/todo/definition.ts +0 -45
  474. package/src/plugins/todo/index.ts +0 -62
  475. package/src/plugins/todo/labels.ts +0 -163
  476. package/src/plugins/todo/priority.ts +0 -98
  477. package/src/plugins/todo/viewModes.ts +0 -19
  478. package/src/plugins/wiki/definition.ts +0 -25
  479. package/src/tools/legacyPluginNames.ts +0 -13
  480. package/src/utils/format/frontmatter.ts +0 -80
  481. package/src/utils/image/rewriteHtmlImageRefs.ts +0 -50
  482. package/src/utils/notification/dispatch.ts +0 -58
  483. /package/client/assets/{purify.es-Fx1Nqyry-BwJECkqS.js → purify.es-Fx1Nqyry-BSVNht6S.js} +0 -0
  484. /package/src/plugins/{editImage → editImages}/Preview.vue +0 -0
  485. /package/src/plugins/{editImage → editImages}/View.vue +0 -0
  486. /package/src/{config/schedulerActions.ts → plugins/scheduler/actions.ts} +0 -0
@@ -0,0 +1,87 @@
1
+ <script setup lang="ts">
2
+ // Scope-provider wrapper for runtime plugin Vue components (#1110).
3
+ // Builds a per-plugin BrowserPluginRuntime and `provide`s it under
4
+ // PLUGIN_RUNTIME_KEY, so plugin descendants can pull it via
5
+ // `useRuntime()` from `gui-chat-protocol/vue`.
6
+ //
7
+ // One instance per mounted plugin component subtree. Mount this in
8
+ // the host's runtime plugin loader (the place that dynamically
9
+ // imports the plugin's `dist/vue.js` and instantiates its
10
+ // `viewComponent` / `previewComponent`).
11
+ //
12
+ // Doubles as the per-plugin error boundary: a Vue `errorCaptured`
13
+ // hook catches uncaught errors thrown during the plugin subtree's
14
+ // render / setup / lifecycle and renders a fallback panel instead
15
+ // of letting the exception break out and unmount the whole chat
16
+ // canvas. One bad plugin should fail in place, not take the host
17
+ // with it. The captured error is logged to the console; the user
18
+ // sees a "crashed" panel with optional stack details and a Retry
19
+ // button that re-mounts the plugin.
20
+
21
+ import { onErrorCaptured, provide } from "vue";
22
+ import { useI18n } from "vue-i18n";
23
+ import { PLUGIN_RUNTIME_KEY } from "gui-chat-protocol/vue";
24
+ import { makeBrowserPluginRuntime } from "../utils/plugin/runtime";
25
+ import { usePluginErrorBoundary } from "../composables/usePluginErrorBoundary";
26
+
27
+ interface Props {
28
+ /** npm package name of the plugin whose subtree we're scoping. */
29
+ pkgName: string;
30
+ /** Optional URL map exposed to the plugin via `runtime.endpoints`.
31
+ * Multi-URL built-in plugins (todos, scheduler, mulmoScript, …)
32
+ * pass their endpoint group ({ method, url } records since #1141);
33
+ * runtime-loaded plugins (the common single-dispatch shape) omit
34
+ * this AND host-shared scopes (`files`, `imageStore`, `mcpTools`)
35
+ * pass plain string URLs. Treated opaquely here — each consumer
36
+ * asserts the shape it expects via `pluginEndpoints<E>(scope)`.
37
+ * Contract: `gui-chat-protocol@>=0.3.1`. */
38
+ endpoints?: Readonly<Record<string, unknown>>;
39
+ }
40
+
41
+ const props = defineProps<Props>();
42
+
43
+ const { t } = useI18n();
44
+
45
+ // Construct once — `pkgName` and `endpoints` are fixed for the
46
+ // lifetime of a mounted plugin. If the host needs to rotate the
47
+ // scope (rare), it should remount the entire wrapper rather than
48
+ // mutate either prop.
49
+ const runtime = makeBrowserPluginRuntime({ pkgName: props.pkgName, endpoints: props.endpoints });
50
+ provide(PLUGIN_RUNTIME_KEY, runtime);
51
+
52
+ // ── Error boundary state ────────────────────────────────────────
53
+ //
54
+ // State + reset logic live in `usePluginErrorBoundary` (testable
55
+ // without a DOM). The Vue lifecycle hook `onErrorCaptured` itself
56
+ // must be wired here — the composable can't call `setup()`-only
57
+ // hooks. Returning `false` from the hook tells Vue we've handled
58
+ // the error; without it, the exception propagates to the parent
59
+ // and ultimately to the global error handler.
60
+ const { error, showDetails, mountKey, errorDetails, captureError, retry } = usePluginErrorBoundary(props.pkgName);
61
+ onErrorCaptured((err) => {
62
+ captureError(err);
63
+ return false;
64
+ });
65
+ </script>
66
+
67
+ <template>
68
+ <div v-if="error" class="rounded border border-red-200 bg-red-50 p-3 text-sm" data-testid="plugin-error-boundary" role="alert">
69
+ <div class="flex items-center gap-2 mb-1">
70
+ <span class="material-icons text-red-500" aria-hidden="true">error_outline</span>
71
+ <span class="font-medium text-red-800">{{ t("pluginErrorBoundary.title", { pkg: pkgName }) }}</span>
72
+ </div>
73
+ <p class="text-red-700 mb-2">{{ t("pluginErrorBoundary.subtitle") }}</p>
74
+ <div class="flex items-center gap-3">
75
+ <button type="button" class="text-xs text-red-600 hover:underline" data-testid="plugin-error-toggle-details" @click="showDetails = !showDetails">
76
+ {{ showDetails ? t("pluginErrorBoundary.hideDetails") : t("pluginErrorBoundary.showDetails") }}
77
+ </button>
78
+ <button type="button" class="text-xs text-red-600 hover:underline" data-testid="plugin-error-retry" @click="retry">
79
+ {{ t("pluginErrorBoundary.retry") }}
80
+ </button>
81
+ </div>
82
+ <pre v-if="showDetails" class="mt-2 text-xs text-red-900 bg-red-100 p-2 rounded overflow-auto max-h-40 whitespace-pre-wrap break-words">{{
83
+ errorDetails
84
+ }}</pre>
85
+ </div>
86
+ <slot v-else :key="mountKey" />
87
+ </template>
@@ -4,6 +4,7 @@
4
4
  ref="button"
5
5
  class="flex-1 h-8 flex items-center gap-1 bg-white border border-gray-300 rounded px-2.5 text-sm text-gray-900 hover:bg-gray-50 text-left"
6
6
  data-testid="role-selector-btn"
7
+ :data-role="currentRoleId"
7
8
  @click="open = !open"
8
9
  >
9
10
  <span class="material-icons text-base text-gray-500">{{ roleIcon(roles, currentRoleId) }}</span>
@@ -12,7 +13,7 @@
12
13
  </button>
13
14
  <div v-if="open" ref="dropdown" class="absolute left-0 right-0 top-full z-50 bg-white border border-gray-200 rounded-lg shadow-lg overflow-hidden">
14
15
  <button
15
- v-for="role in roles"
16
+ v-for="role in visibleRoles"
16
17
  :key="role.id"
17
18
  :data-testid="`role-option-${role.id}`"
18
19
  class="w-full flex items-center gap-1.5 px-3 py-1 text-sm text-gray-900 hover:bg-gray-50 text-left"
@@ -48,6 +49,16 @@ const dropdown = ref<HTMLDivElement | null>(null);
48
49
 
49
50
  const currentRoleName = computed(() => roleName(props.roles, props.currentRoleId));
50
51
 
52
+ // Hide `isDebugRole` entries from the dropdown unless dev mode is
53
+ // on. Set `VITE_DEV_MODE=1` in `.env` to surface them. Existing
54
+ // chat sessions tied to a debug role still render normally elsewhere
55
+ // — this filter only gates the entry point for new sessions.
56
+ const visibleRoles = computed(() => {
57
+ const devMode = import.meta.env.VITE_DEV_MODE === "1";
58
+ if (devMode) return props.roles;
59
+ return props.roles.filter((role) => !role.isDebugRole);
60
+ });
61
+
51
62
  function selectRole(roleId: string): void {
52
63
  emit("update:currentRoleId", roleId);
53
64
  open.value = false;
@@ -0,0 +1,562 @@
1
+ <template>
2
+ <div class="h-full bg-white flex flex-col">
3
+ <div class="flex items-center justify-between gap-2 px-3 py-2 border-b border-gray-100">
4
+ <h2 class="text-lg font-semibold text-gray-800">{{ t("pluginManageRoles.heading") }}</h2>
5
+ <div class="flex items-center gap-2">
6
+ <span class="text-sm text-gray-500">{{ t("pluginManageRoles.roleCount", customRoles.length, { named: { count: customRoles.length } }) }}</span>
7
+ <button
8
+ v-if="!creating"
9
+ data-testid="role-add-btn"
10
+ class="h-8 px-2.5 flex items-center gap-1 text-sm rounded bg-blue-500 text-white hover:bg-blue-600"
11
+ @click="startCreate"
12
+ >
13
+ {{ t("pluginManageRoles.addButton") }}
14
+ </button>
15
+ </div>
16
+ </div>
17
+
18
+ <div class="flex-1 overflow-y-auto">
19
+ <!-- New role creation panel -->
20
+ <div v-if="creating" class="m-4 border border-blue-300 bg-blue-50 rounded-lg p-4 space-y-3">
21
+ <div class="text-sm font-semibold text-gray-700">{{ t("pluginManageRoles.createPanel") }}</div>
22
+
23
+ <!-- ID + Name + Icon row -->
24
+ <div class="flex gap-3">
25
+ <div class="w-40">
26
+ <label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldId") }}</label>
27
+ <input
28
+ v-model="newForm.id"
29
+ type="text"
30
+ placeholder="unique-id"
31
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono focus:outline-none focus:border-blue-400"
32
+ />
33
+ </div>
34
+ <div class="flex-1">
35
+ <label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldName") }}</label>
36
+ <input
37
+ v-model="newForm.name"
38
+ type="text"
39
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
40
+ />
41
+ </div>
42
+ <div class="w-32">
43
+ <label class="block text-xs font-medium text-gray-600 mb-1">
44
+ {{ t("pluginManageRoles.fieldIcon") }}
45
+ <a class="text-blue-400 font-normal ml-1" href="https://fonts.google.com/icons" target="_blank" rel="noopener">{{
46
+ t("pluginManageRoles.helpLink")
47
+ }}</a>
48
+ </label>
49
+ <input
50
+ v-model="newForm.icon"
51
+ type="text"
52
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono focus:outline-none focus:border-blue-400"
53
+ />
54
+ </div>
55
+ </div>
56
+
57
+ <!-- Prompt -->
58
+ <div>
59
+ <label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldPrompt") }}</label>
60
+ <textarea
61
+ v-model="newForm.prompt"
62
+ rows="6"
63
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono resize-y focus:outline-none focus:border-blue-400"
64
+ spellcheck="false"
65
+ />
66
+ </div>
67
+
68
+ <!-- Plugins -->
69
+ <div>
70
+ <label class="block text-xs font-medium text-gray-600 mb-2">{{ t("pluginManageRoles.fieldPlugins") }}</label>
71
+ <div class="grid gap-x-4 gap-y-1 [grid-template-columns:repeat(auto-fit,minmax(180px,1fr))]">
72
+ <label
73
+ v-for="plugin in availablePlugins"
74
+ :key="plugin.name"
75
+ class="flex items-center gap-2 text-sm cursor-pointer"
76
+ :class="plugin.enabled ? 'text-gray-700' : 'text-gray-400 cursor-not-allowed'"
77
+ :title="plugin.enabled ? '' : t('pluginManageRoles.requiresEnv', { env: plugin.requiredEnv.join(', ') })"
78
+ >
79
+ <input
80
+ v-model="newForm.selectedPlugins"
81
+ type="checkbox"
82
+ :value="plugin.name"
83
+ :disabled="!plugin.enabled"
84
+ class="cursor-pointer disabled:cursor-not-allowed"
85
+ />
86
+ {{ plugin.name }}
87
+ <span v-if="!plugin.enabled" class="text-xs text-gray-400">{{ t("pluginManageRoles.missingEnv", { env: plugin.requiredEnv.join(", ") }) }}</span>
88
+ </label>
89
+ </div>
90
+ </div>
91
+
92
+ <!-- Starter queries -->
93
+ <div>
94
+ <label class="block text-xs font-medium text-gray-600 mb-1">
95
+ {{ t("pluginManageRoles.fieldStarterQueries") }}
96
+ <span class="text-gray-400 font-normal">{{ t("pluginManageRoles.onePerLine") }}</span>
97
+ </label>
98
+ <textarea
99
+ v-model="newForm.queriesText"
100
+ rows="3"
101
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded resize-y focus:outline-none focus:border-blue-400"
102
+ />
103
+ </div>
104
+
105
+ <!-- Buttons -->
106
+ <div class="flex gap-2 pt-1">
107
+ <button
108
+ class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
109
+ :disabled="saving || !!newFormError"
110
+ :title="newFormError ?? ''"
111
+ @click="saveNew"
112
+ >
113
+ {{ saving ? t("pluginManageRoles.creating") : t("pluginManageRoles.create") }}
114
+ </button>
115
+ <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="cancelCreate">
116
+ {{ t("common.cancel") }}
117
+ </button>
118
+ </div>
119
+ <div v-if="newFormError" class="text-xs text-gray-500" data-testid="role-form-hint">
120
+ {{ newFormError }}
121
+ </div>
122
+ <div v-if="createError" class="text-xs text-red-500">
123
+ {{ createError }}
124
+ </div>
125
+ </div>
126
+
127
+ <div v-if="!creating && customRoles.length === 0" class="h-full flex items-center justify-center text-gray-400 text-sm">
128
+ {{ t("pluginManageRoles.emptyHint") }}
129
+ </div>
130
+
131
+ <ul v-if="customRoles.length > 0" class="p-4 space-y-2">
132
+ <li v-for="role in customRoles" :key="role.id" class="rounded-lg border" :class="selectedId === role.id ? 'border-blue-400' : 'border-gray-200'">
133
+ <!-- Role header row -->
134
+ <div
135
+ class="flex items-center gap-3 p-3 cursor-pointer hover:bg-gray-50 rounded-lg"
136
+ :class="selectedId === role.id ? 'rounded-b-none' : ''"
137
+ @click="selectRole(role)"
138
+ >
139
+ <span class="material-icons text-gray-500">{{ role.icon }}</span>
140
+ <div class="flex-1 min-w-0">
141
+ <div class="font-medium text-sm text-gray-800">
142
+ {{ role.name }}
143
+ <span class="ml-1 text-xs font-mono text-gray-400">{{ t("pluginManageRoles.idFormatted", { id: role.id }) }}</span>
144
+ </div>
145
+ <div class="text-xs text-gray-400 truncate">
146
+ {{ role.availablePlugins.join(", ") }}
147
+ </div>
148
+ </div>
149
+ <span
150
+ class="material-icons text-gray-400 text-sm"
151
+ :title="selectedId === role.id ? t('pluginManageRoles.collapse') : t('pluginManageRoles.expand')"
152
+ >
153
+ {{ selectedId === role.id ? "expand_less" : "expand_more" }}
154
+ </span>
155
+ </div>
156
+
157
+ <!-- Inline editor -->
158
+ <div v-if="selectedId === role.id" class="border-t border-blue-100 bg-blue-50 p-4 space-y-3 rounded-b-lg">
159
+ <!-- ID + Name + Icon row -->
160
+ <div class="flex gap-3">
161
+ <div class="w-40">
162
+ <label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldId") }}</label>
163
+ <input
164
+ v-model="editForm.id"
165
+ type="text"
166
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono focus:outline-none focus:border-blue-400"
167
+ />
168
+ </div>
169
+ <div class="flex-1">
170
+ <label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldName") }}</label>
171
+ <input
172
+ v-model="editForm.name"
173
+ type="text"
174
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
175
+ @keydown.enter="saveEdit(role.id)"
176
+ />
177
+ </div>
178
+ <div class="w-32">
179
+ <label class="block text-xs font-medium text-gray-600 mb-1">
180
+ {{ t("pluginManageRoles.fieldIcon") }}
181
+ <a class="text-blue-400 font-normal ml-1" href="https://fonts.google.com/icons" target="_blank" rel="noopener">{{
182
+ t("pluginManageRoles.helpLink")
183
+ }}</a>
184
+ </label>
185
+ <input
186
+ v-model="editForm.icon"
187
+ type="text"
188
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono focus:outline-none focus:border-blue-400"
189
+ />
190
+ </div>
191
+ </div>
192
+
193
+ <!-- Prompt -->
194
+ <div>
195
+ <label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldPrompt") }}</label>
196
+ <textarea
197
+ v-model="editForm.prompt"
198
+ rows="6"
199
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono resize-y focus:outline-none focus:border-blue-400"
200
+ spellcheck="false"
201
+ />
202
+ </div>
203
+
204
+ <!-- Plugins -->
205
+ <div>
206
+ <label class="block text-xs font-medium text-gray-600 mb-2">{{ t("pluginManageRoles.fieldPlugins") }}</label>
207
+ <div class="grid gap-x-4 gap-y-1 [grid-template-columns:repeat(auto-fit,minmax(180px,1fr))]">
208
+ <label
209
+ v-for="plugin in availablePlugins"
210
+ :key="plugin.name"
211
+ class="flex items-center gap-2 text-sm cursor-pointer"
212
+ :class="plugin.enabled ? 'text-gray-700' : 'text-gray-400 cursor-not-allowed'"
213
+ :title="plugin.enabled ? '' : t('pluginManageRoles.requiresEnv', { env: plugin.requiredEnv.join(', ') })"
214
+ >
215
+ <input
216
+ v-model="editForm.selectedPlugins"
217
+ type="checkbox"
218
+ :value="plugin.name"
219
+ :disabled="!plugin.enabled"
220
+ class="cursor-pointer disabled:cursor-not-allowed"
221
+ />
222
+ {{ plugin.name }}
223
+ <span v-if="!plugin.enabled" class="text-xs text-gray-400">{{
224
+ t("pluginManageRoles.missingEnv", { env: plugin.requiredEnv.join(", ") })
225
+ }}</span>
226
+ </label>
227
+ </div>
228
+ </div>
229
+
230
+ <!-- Starter queries -->
231
+ <div>
232
+ <label class="block text-xs font-medium text-gray-600 mb-1">
233
+ {{ t("pluginManageRoles.fieldStarterQueries") }}
234
+ <span class="text-gray-400 font-normal">{{ t("pluginManageRoles.onePerLine") }}</span>
235
+ </label>
236
+ <textarea
237
+ v-model="editForm.queriesText"
238
+ rows="3"
239
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded resize-y focus:outline-none focus:border-blue-400"
240
+ />
241
+ </div>
242
+
243
+ <!-- Buttons -->
244
+ <div class="flex items-center justify-between pt-1">
245
+ <div class="flex gap-2">
246
+ <button
247
+ class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
248
+ :disabled="saving || !!editFormError"
249
+ :title="editFormError ?? ''"
250
+ @click="saveEdit(role.id)"
251
+ >
252
+ {{ saving ? t("pluginManageRoles.updating") : t("pluginManageRoles.update") }}
253
+ </button>
254
+ <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="selectedId = null">
255
+ {{ t("common.cancel") }}
256
+ </button>
257
+ </div>
258
+ <button
259
+ class="px-3 py-1.5 text-sm rounded border border-red-200 text-red-500 hover:bg-red-50 disabled:opacity-50"
260
+ :disabled="saving"
261
+ @click="deleteRole(role.id)"
262
+ >
263
+ {{ t("pluginManageRoles.delete") }}
264
+ </button>
265
+ </div>
266
+ <div v-if="editFormError" class="text-xs text-gray-500">
267
+ {{ editFormError }}
268
+ </div>
269
+ <div v-if="saveError" class="text-xs text-red-500">
270
+ {{ saveError }}
271
+ </div>
272
+ </div>
273
+ </li>
274
+ </ul>
275
+ </div>
276
+ </div>
277
+ </template>
278
+
279
+ <script setup lang="ts">
280
+ import { ref, computed, watch, onMounted } from "vue";
281
+ import { useI18n } from "vue-i18n";
282
+ import { useFreshPluginData } from "../composables/useFreshPluginData";
283
+ import { useAppApi } from "../composables/useAppApi";
284
+ import type { ToolResultComplete } from "gui-chat-protocol/vue";
285
+ import { getAllPluginNames } from "../tools/index";
286
+ import { apiGet, apiPost } from "../utils/api";
287
+ import { API_ROUTES } from "../config/apiRoutes";
288
+
289
+ // Inlined from the former `src/plugins/manageRoles/index.ts`
290
+ // (deleted alongside the manageRoles MCP tool — #949). RolesView
291
+ // is the only consumer, so co-locating the types here avoids a
292
+ // dangling single-purpose module.
293
+ export interface CustomRole {
294
+ id: string;
295
+ name: string;
296
+ icon: string;
297
+ prompt: string;
298
+ availablePlugins: string[];
299
+ queries?: string[];
300
+ }
301
+
302
+ export interface ManageRolesData {
303
+ customRoles: CustomRole[];
304
+ }
305
+
306
+ const { t } = useI18n();
307
+
308
+ interface PluginEntry {
309
+ name: string;
310
+ enabled: boolean;
311
+ requiredEnv: string[];
312
+ }
313
+
314
+ // Plugins the user can assign — exclude internal/auto-managed ones
315
+ const EXCLUDED = new Set(["text-response"]);
316
+ const guiPlugins: PluginEntry[] = getAllPluginNames()
317
+ .filter((name) => !EXCLUDED.has(name))
318
+ .map((name) => ({ name, enabled: true, requiredEnv: [] }));
319
+
320
+ const availablePlugins = ref<PluginEntry[]>(guiPlugins);
321
+
322
+ onMounted(async () => {
323
+ const result = await apiGet<PluginEntry[]>(API_ROUTES.mcpTools.list);
324
+ if (result.ok) {
325
+ availablePlugins.value = [...guiPlugins, ...result.data];
326
+ }
327
+ // Non-critical: MCP tools enrich the plugin palette for role editing
328
+ // but the view works fine with GUI plugins alone. No error banner needed.
329
+ });
330
+
331
+ const props = defineProps<{
332
+ selectedResult?: ToolResultComplete<ManageRolesData>;
333
+ }>();
334
+ const emit = defineEmits<{ updateResult: [result: ToolResultComplete] }>();
335
+
336
+ const appApi = useAppApi();
337
+
338
+ const customRoles = ref<CustomRole[]>(props.selectedResult?.data?.customRoles ?? []);
339
+
340
+ const { refresh: refreshCustomRoles } = useFreshPluginData<CustomRole[]>({
341
+ endpoint: () => API_ROUTES.roles.list,
342
+ extract: (json) => (Array.isArray(json) ? (json as CustomRole[]) : null),
343
+ apply: (data) => {
344
+ customRoles.value = data;
345
+ },
346
+ });
347
+
348
+ watch(
349
+ () => props.selectedResult?.uuid,
350
+ () => {
351
+ customRoles.value = props.selectedResult?.data?.customRoles ?? [];
352
+ void refreshCustomRoles();
353
+ },
354
+ );
355
+
356
+ // ── Selection & edit form ─────────────────────────────────────────────────────
357
+
358
+ const selectedId = ref<string | null>(null);
359
+ const saving = ref(false);
360
+ const saveError = ref("");
361
+
362
+ interface EditForm {
363
+ id: string;
364
+ name: string;
365
+ icon: string;
366
+ prompt: string;
367
+ selectedPlugins: string[];
368
+ queriesText: string;
369
+ }
370
+
371
+ const editForm = ref<EditForm>({
372
+ id: "",
373
+ name: "",
374
+ icon: "",
375
+ prompt: "",
376
+ selectedPlugins: [],
377
+ queriesText: "",
378
+ });
379
+
380
+ const creating = ref(false);
381
+ const createError = ref("");
382
+ const newForm = ref<EditForm>({
383
+ id: "",
384
+ name: "",
385
+ icon: "person",
386
+ prompt: "",
387
+ selectedPlugins: [],
388
+ queriesText: "",
389
+ });
390
+
391
+ function startCreate() {
392
+ selectedId.value = null;
393
+ createError.value = "";
394
+ newForm.value = {
395
+ id: "",
396
+ name: "",
397
+ icon: "person",
398
+ prompt: "",
399
+ selectedPlugins: [],
400
+ queriesText: "",
401
+ };
402
+ creating.value = true;
403
+ }
404
+
405
+ function cancelCreate() {
406
+ creating.value = false;
407
+ createError.value = "";
408
+ }
409
+
410
+ function selectRole(role: CustomRole) {
411
+ if (selectedId.value === role.id) {
412
+ selectedId.value = null;
413
+ return;
414
+ }
415
+ selectedId.value = role.id;
416
+ saveError.value = "";
417
+ editForm.value = {
418
+ id: role.id,
419
+ name: role.name,
420
+ icon: role.icon,
421
+ prompt: role.prompt,
422
+ selectedPlugins: [...role.availablePlugins],
423
+ queriesText: (role.queries ?? []).join("\n"),
424
+ };
425
+ }
426
+
427
+ // ── API ───────────────────────────────────────────────────────────────────────
428
+
429
+ interface ManageResult {
430
+ success?: boolean;
431
+ error?: string;
432
+ [key: string]: unknown;
433
+ }
434
+
435
+ async function callManage(body: Record<string, unknown>): Promise<ManageResult> {
436
+ const result = await apiPost<ManageResult>(API_ROUTES.roles.manage, body);
437
+ if (!result.ok) {
438
+ // Prefer the backend's error message (e.g. validation failure
439
+ // details). Fall back to a status code only when the server didn't
440
+ // give us anything useful.
441
+ return {
442
+ success: false,
443
+ error:
444
+ result.status === 0
445
+ ? result.error || t("pluginManageRoles.errNetworkError")
446
+ : result.error || t("pluginManageRoles.errServerError", { status: result.status }),
447
+ };
448
+ }
449
+ return result.data;
450
+ }
451
+
452
+ async function refreshList() {
453
+ const result = await callManage({ action: "list" });
454
+ if (result.success) {
455
+ const data = result as { data?: { customRoles?: CustomRole[] } };
456
+ customRoles.value = data.data?.customRoles ?? [];
457
+ if (props.selectedResult) {
458
+ emit("updateResult", {
459
+ ...props.selectedResult,
460
+ ...result,
461
+ uuid: props.selectedResult.uuid,
462
+ });
463
+ }
464
+ // Let App.vue know the dropdown needs to refresh.
465
+ await Promise.resolve(appApi.refreshRoles());
466
+ }
467
+ }
468
+
469
+ function validateRoleForm(form: EditForm, excludeId: string | null): string | null {
470
+ const trimmedId = form.id.trim();
471
+ const trimmedName = form.name.trim();
472
+ if (!trimmedId) return t("pluginManageRoles.errIdRequired");
473
+ if (!/^[a-zA-Z0-9_-]+$/.test(trimmedId)) {
474
+ return t("pluginManageRoles.errIdInvalid");
475
+ }
476
+ if (!trimmedName) return t("pluginManageRoles.errNameRequired");
477
+ if (customRoles.value.some((existing) => existing.id === trimmedId && existing.id !== excludeId)) {
478
+ return t("pluginManageRoles.errIdDuplicate", { id: trimmedId });
479
+ }
480
+ return null;
481
+ }
482
+
483
+ const newFormError = computed<string | null>(() => validateRoleForm(newForm.value, null));
484
+
485
+ const editFormError = computed<string | null>(() => validateRoleForm(editForm.value, selectedId.value));
486
+
487
+ function buildNewRole(): CustomRole {
488
+ return {
489
+ id: newForm.value.id.trim(),
490
+ name: newForm.value.name.trim(),
491
+ icon: newForm.value.icon.trim() || "person",
492
+ prompt: newForm.value.prompt,
493
+ availablePlugins: newForm.value.selectedPlugins,
494
+ queries: newForm.value.queriesText
495
+ .split("\n")
496
+ .map((line) => line.trim())
497
+ .filter(Boolean),
498
+ };
499
+ }
500
+
501
+ async function saveNew() {
502
+ if (newFormError.value) {
503
+ createError.value = newFormError.value;
504
+ return;
505
+ }
506
+ saving.value = true;
507
+ createError.value = "";
508
+ const result = await callManage({ action: "create", role: buildNewRole() });
509
+ if (result.success) {
510
+ creating.value = false;
511
+ await refreshList();
512
+ } else {
513
+ createError.value = result.error ?? t("pluginManageRoles.errCreateFailed");
514
+ }
515
+ saving.value = false;
516
+ }
517
+
518
+ async function saveEdit(originalId: string) {
519
+ if (editFormError.value) {
520
+ saveError.value = editFormError.value;
521
+ return;
522
+ }
523
+ saving.value = true;
524
+ saveError.value = "";
525
+ const role: CustomRole = {
526
+ id: editForm.value.id.trim(),
527
+ name: editForm.value.name.trim(),
528
+ icon: editForm.value.icon.trim(),
529
+ prompt: editForm.value.prompt,
530
+ availablePlugins: editForm.value.selectedPlugins,
531
+ queries: editForm.value.queriesText
532
+ .split("\n")
533
+ .map((line) => line.trim())
534
+ .filter(Boolean),
535
+ };
536
+ const result = await callManage({
537
+ action: "update",
538
+ role,
539
+ oldRoleId: originalId,
540
+ });
541
+ if (result.success) {
542
+ selectedId.value = null;
543
+ await refreshList();
544
+ } else {
545
+ saveError.value = result.error ?? t("pluginManageRoles.errSaveFailed");
546
+ }
547
+ saving.value = false;
548
+ }
549
+
550
+ async function deleteRole(roleId: string) {
551
+ saving.value = true;
552
+ saveError.value = "";
553
+ const result = await callManage({ action: "delete", roleId });
554
+ if (result.success) {
555
+ selectedId.value = null;
556
+ await refreshList();
557
+ } else {
558
+ saveError.value = result.error ?? t("pluginManageRoles.errDeleteFailed");
559
+ }
560
+ saving.value = false;
561
+ }
562
+ </script>