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
@@ -3,12 +3,14 @@ import { copyFileSync, existsSync, mkdirSync, readdirSync } from "fs";
3
3
  import path from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { log } from "../system/logger/index.js";
6
- import { EAGER_WORKSPACE_DIRS, WORKSPACE_FILES, WORKSPACE_PATHS, workspacePath } from "./paths.js";
7
- import { existsInWorkspace, writeWorkspaceTextSync } from "../utils/files/workspace-io.js";
6
+ import { EAGER_WORKSPACE_DIRS, WORKSPACE_PATHS, workspacePath } from "./paths.js";
7
+ import { readWorkspaceTextSync, writeWorkspaceTextSync } from "../utils/files/workspace-io.js";
8
8
  import { loadCustomDirs, ensureCustomDirs } from "./custom-dirs.js";
9
+ import { syncPresetSkills } from "./skills-preset.js";
9
10
 
10
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
12
  const TEMPLATES_DIR = path.join(__dirname, "helps");
13
+ const SKILLS_PRESET_SOURCE_DIR = path.join(__dirname, "skills-preset");
12
14
 
13
15
  // Re-exported so existing callers (`import { workspacePath } from
14
16
  // "./workspace.js"`) keep working. See workspace-paths.ts for the
@@ -24,10 +26,14 @@ export function initWorkspace(): string {
24
26
  mkdirSync(WORKSPACE_PATHS[key], { recursive: true });
25
27
  }
26
28
 
27
- // Create memory.md if it doesn't exist
28
- if (!existsInWorkspace(WORKSPACE_FILES.memory)) {
29
- writeWorkspaceTextSync(WORKSPACE_FILES.memory, "# Memory\n\nDistilled facts about you and your work.\n");
30
- }
29
+ // Ensure the typed-memory directory exists (#1029). Individual
30
+ // entry files are written by the agent or by the legacy-memory
31
+ // migration; init just guarantees the directory is there so the
32
+ // reader and migration both have a place to write to. The legacy
33
+ // `conversations/memory.md` is no longer auto-created — migration
34
+ // converts it on first start and the new layout becomes the source
35
+ // of truth thereafter.
36
+ mkdirSync(WORKSPACE_PATHS.memoryDir, { recursive: true });
31
37
 
32
38
  // Always sync all files from server/helps/ into workspace/helps/
33
39
  mkdirSync(WORKSPACE_PATHS.helps, { recursive: true });
@@ -35,15 +41,23 @@ export function initWorkspace(): string {
35
41
  copyFileSync(path.join(TEMPLATES_DIR, file), path.join(WORKSPACE_PATHS.helps, file));
36
42
  }
37
43
 
44
+ // Sync preset skills (#1210) from server/workspace/skills-preset/
45
+ // into <workspaceRoot>/.claude/skills/. Only `mc-*` slugs are
46
+ // touched — user-authored skills coexist untouched. Retired
47
+ // presets (no longer in source) are removed from the workspace.
48
+ syncPresetSkills({
49
+ sourceDir: SKILLS_PRESET_SOURCE_DIR,
50
+ destDir: WORKSPACE_PATHS.claudeSkills,
51
+ onInfo: (message, data) => log.info("skills-preset", message, data),
52
+ onWarn: (message, data) => log.warn("skills-preset", message, data),
53
+ });
54
+
38
55
  // Create .gitignore if missing. The workspace is a git repo for
39
56
  // version-tracking user data, but cloned dev repos under github/
40
- // have their own .git and shouldn't be committed (#256).
41
- if (!existsInWorkspace(".gitignore")) {
42
- writeWorkspaceTextSync(
43
- ".gitignore",
44
- ["# Cloned repositories have their own .git — don't nest", "github/", "", "# Auth token (regenerated each startup)", ".session-token", ""].join("\n"),
45
- );
46
- }
57
+ // have their own .git and shouldn't be committed (#256). Runtime
58
+ // files (`.session-token`, `.server-port`) are regenerated on
59
+ // every startup and should never be committed (#917).
60
+ ensureWorkspaceGitignore();
47
61
 
48
62
  // User-defined custom directories (#239)
49
63
  const customDirs = loadCustomDirs();
@@ -64,3 +78,51 @@ export function initWorkspace(): string {
64
78
  log.info("workspace", "ready", { workspacePath });
65
79
  return workspacePath;
66
80
  }
81
+
82
+ export const REQUIRED_GITIGNORE_LINES = [
83
+ "github/", // cloned repos have their own .git
84
+ ".session-token", // bearer token regenerated each startup
85
+ ".server-port", // runtime port published for the LLM wiki-write hook
86
+ ] as const;
87
+
88
+ const FRESH_GITIGNORE = [
89
+ "# Cloned repositories have their own .git — don't nest",
90
+ "github/",
91
+ "",
92
+ "# Auth token (regenerated each startup)",
93
+ ".session-token",
94
+ "",
95
+ "# Bound port published at startup for the wiki-write hook",
96
+ ".server-port",
97
+ "",
98
+ ].join("\n");
99
+
100
+ /** Decide what `.gitignore` should look like given its current
101
+ * content. Returns null when no rewrite is needed.
102
+ * - `null` existing → fresh template
103
+ * - existing missing required lines → existing + missing lines
104
+ * appended (preserves user customisation)
105
+ * - existing with all required lines → null (no change) */
106
+ export function nextGitignoreContent(existing: string | null): string | null {
107
+ if (existing === null) return FRESH_GITIGNORE;
108
+ // Treat the file as line-oriented; ignore comments and blanks for
109
+ // the comparison so a user's annotated copy still counts as
110
+ // having the line.
111
+ const present = new Set(
112
+ existing
113
+ .split(/\r?\n/)
114
+ .map((raw) => raw.trim())
115
+ .filter((line) => line.length > 0 && !line.startsWith("#")),
116
+ );
117
+ const missing = REQUIRED_GITIGNORE_LINES.filter((line) => !present.has(line));
118
+ if (missing.length === 0) return null;
119
+ const trailingNewline = existing.endsWith("\n") ? existing : `${existing}\n`;
120
+ return `${trailingNewline}${missing.join("\n")}\n`;
121
+ }
122
+
123
+ function ensureWorkspaceGitignore(): void {
124
+ const existing = readWorkspaceTextSync(".gitignore");
125
+ const next = nextGitignoreContent(existing);
126
+ if (next === null) return;
127
+ writeWorkspaceTextSync(".gitignore", next);
128
+ }
package/src/App.vue CHANGED
@@ -9,7 +9,6 @@
9
9
  :gemini-available="geminiAvailable"
10
10
  :title-style="debugTitleStyle"
11
11
  @test-query="(q) => sendMessage(q)"
12
- @notification-navigate="handleNotificationNavigate"
13
12
  @open-settings="showSettings = true"
14
13
  @home="handleHomeClick"
15
14
  />
@@ -77,6 +76,8 @@
77
76
  :roles="roles"
78
77
  :error-message="historyError"
79
78
  @load-session="handleSessionSelect"
79
+ @toggle-bookmark="(id, bookmarked) => setBookmark(id, bookmarked)"
80
+ @delete-session="(id) => deleteSessionFromHistory(id)"
80
81
  />
81
82
  <SessionHistoryExpandButton :model-value="sidePanelExpanded" @update:model-value="(value: boolean) => (sidePanelExpanded = value)" />
82
83
  </div>
@@ -123,7 +124,7 @@
123
124
  v-model="userInput"
124
125
  v-model:pasted-file="pastedFile"
125
126
  :is-running="activeSessionRunning"
126
- :queries="sessionRole.queries ?? []"
127
+ :queries="sessionRoleQueries"
127
128
  @send="sendMessage()"
128
129
  @suggestion-send="(q) => sendMessage(q)"
129
130
  />
@@ -163,16 +164,51 @@
163
164
  @update:layout-mode="setLayoutMode"
164
165
  @toggle-right-sidebar="toggleRightSidebar"
165
166
  />
166
- <!-- Distinct pages -->
167
+ <!-- Distinct pages. Plugin-owned views (Todo / Calendar /
168
+ Automations / Wiki / Skills) call `useRuntime()` from
169
+ `gui-chat-protocol/vue` inside their composables — that
170
+ throws unless mounted under `<PluginScopedRoot>`. The
171
+ plugin registry's `wrapWithScope` wraps the chat-mounted
172
+ variants; standalone routes are wrapped here against the
173
+ same `pkg-name + endpoints` pair so the `useRuntime()`
174
+ call resolves. -->
167
175
  <FilesView v-else-if="currentPage === 'files'" :refresh-token="filesRefreshToken" @load-session="handleSessionSelect" />
168
- <TodoExplorer v-else-if="currentPage === 'todos'" />
169
- <CalendarView v-else-if="currentPage === 'calendar'" />
170
- <AutomationsView v-else-if="currentPage === 'automations'" />
171
- <WikiView v-else-if="currentPage === 'wiki'" />
172
- <SkillsView v-else-if="currentPage === 'skills'" />
176
+ <PluginScopedRoot v-else-if="currentPage === 'todos'" pkg-name="@mulmoclaude/todo-plugin">
177
+ <TodoExplorer />
178
+ </PluginScopedRoot>
179
+ <PluginScopedRoot v-else-if="currentPage === 'calendar'" pkg-name="scheduler" :endpoints="API_ROUTES.scheduler">
180
+ <CalendarView />
181
+ </PluginScopedRoot>
182
+ <PluginScopedRoot v-else-if="currentPage === 'automations'" pkg-name="scheduler" :endpoints="API_ROUTES.scheduler">
183
+ <AutomationsView />
184
+ </PluginScopedRoot>
185
+ <PluginScopedRoot v-else-if="currentPage === 'wiki'" pkg-name="wiki" :endpoints="API_ROUTES.wiki">
186
+ <WikiView />
187
+ </PluginScopedRoot>
188
+ <PluginScopedRoot v-else-if="currentPage === 'skills'" pkg-name="skills" :endpoints="API_ROUTES.skills">
189
+ <SkillsView />
190
+ </PluginScopedRoot>
173
191
  <RolesView v-else-if="currentPage === 'roles'" />
174
192
  <SourcesView v-else-if="currentPage === 'sources'" />
175
193
  <NewsView v-else-if="currentPage === 'news'" />
194
+ <!-- Debug page (encore plan PR 1 follow-up). The View ships
195
+ inside the @mulmoclaude/debug-plugin runtime package; we
196
+ look it up by tool name and render the registered
197
+ viewComponent — already wrapped in PluginScopedRoot by
198
+ the runtime loader, so no extra scope wrapper here.
199
+
200
+ Literal English fallback below is intentional: the debug
201
+ surface is dev-only chrome behind `VITE_DEV_MODE=1`, so
202
+ we keep its strings out of the 8-locale i18n bundle.
203
+ Same policy applies to the launcher button (see
204
+ PluginLauncher.vue's `literalLabel`/`literalTitle`) and
205
+ to the page itself (debug-plugin/src/View.vue). -->
206
+ <component :is="debugViewComponent" v-else-if="currentPage === 'debug' && debugViewComponent" />
207
+ <!-- eslint-disable @intlify/vue-i18n/no-raw-text -- debug page is dev-only chrome behind VITE_DEV_MODE=1; we deliberately keep its strings out of the 8-locale i18n bundle (see policy comment above). -->
208
+ <div v-else-if="currentPage === 'debug'" class="h-full flex items-center justify-center text-sm text-gray-500">
209
+ Debug plugin is not loaded. Make sure @mulmoclaude/debug-plugin is built and registered as a preset.
210
+ </div>
211
+ <!-- eslint-enable @intlify/vue-i18n/no-raw-text -->
176
212
  </div>
177
213
 
178
214
  <!-- Bottom bar (Stack chat only — plugin views have no
@@ -190,7 +226,7 @@
190
226
  v-model="userInput"
191
227
  v-model:pasted-file="pastedFile"
192
228
  :is-running="activeSessionRunning"
193
- :queries="sessionRole.queries ?? []"
229
+ :queries="sessionRoleQueries"
194
230
  @send="sendMessage()"
195
231
  @suggestion-send="(q) => sendMessage(q)"
196
232
  />
@@ -219,7 +255,6 @@
219
255
  @update:open="showSettings = $event"
220
256
  @ask-gemini="handleAskGemini"
221
257
  />
222
- <NotificationToast />
223
258
  </div>
224
259
  </template>
225
260
 
@@ -227,8 +262,6 @@
227
262
  import { ref, computed, watch, nextTick, onMounted, reactive } from "vue";
228
263
  import { useI18n } from "vue-i18n";
229
264
  import { v4 as uuidv4 } from "uuid";
230
-
231
- const { t } = useI18n();
232
265
  import { getPlugin } from "./tools";
233
266
  import type { ToolResultComplete } from "gui-chat-protocol/vue";
234
267
  import RightSidebar from "./components/RightSidebar.vue";
@@ -249,24 +282,22 @@ import AutomationsView from "./plugins/scheduler/AutomationsView.vue";
249
282
  import WikiView from "./plugins/wiki/View.vue";
250
283
  import { buildWikiRouteParams } from "./plugins/wiki/route";
251
284
  import SkillsView from "./plugins/manageSkills/View.vue";
252
- import RolesView from "./plugins/manageRoles/View.vue";
285
+ import RolesView from "./components/RolesView.vue";
253
286
  import SourcesView from "./components/SourcesView.vue";
254
287
  import NewsView from "./components/NewsView.vue";
288
+ import PluginScopedRoot from "./components/PluginScopedRoot.vue";
255
289
  import SettingsModal from "./components/SettingsModal.vue";
256
- import NotificationToast from "./components/NotificationToast.vue";
257
- import type { NotificationAction } from "./types/notification";
258
290
  import { PAGE_ROUTES, type PageRouteName } from "./router";
259
291
  import type { SseEvent } from "./types/sse";
260
- import { type SessionEntry, type ActiveSession } from "./types/session";
292
+ import type { SessionEntry, ActiveSession } from "./types/session";
261
293
  import { EVENT_TYPES } from "./types/events";
262
- import { extractImageData } from "./utils/tools/result";
263
294
  import { buildAgentRequestBody, postAgentRun } from "./utils/agent/request";
295
+ import { resolvePastedAttachment } from "./utils/agent/pastedAttachment";
264
296
  import { applyAgentEvent, type AgentEventContext } from "./utils/agent/eventDispatch";
265
297
  import { pushErrorMessage, beginUserTurn, updateResult } from "./utils/session/sessionHelpers";
266
298
  import { roleName, roleIcon } from "./utils/role/icon";
267
299
  import { createEmptySession } from "./utils/session/sessionFactory";
268
300
  import { buildLoadedSession, parseSessionEntries } from "./utils/session/sessionEntries";
269
- import { resolveNotificationTarget } from "./utils/notification/dispatch";
270
301
  import { usePendingCalls } from "./composables/usePendingCalls";
271
302
  import { useRunElapsed } from "./composables/useRunElapsed";
272
303
  import { useKeyNavigation } from "./composables/useKeyNavigation";
@@ -276,6 +307,7 @@ import { useViewLayout } from "./composables/useViewLayout";
276
307
  import { useSessionSync } from "./composables/useSessionSync";
277
308
  import { useSessionDerived } from "./composables/useSessionDerived";
278
309
  import { useFaviconState } from "./composables/useFaviconState";
310
+ import { useGlobalImageErrorRepair } from "./composables/useImageErrorRepair";
279
311
  import { useMergedSessions } from "./composables/useMergedSessions";
280
312
  import { useLayoutMode } from "./composables/useLayoutMode";
281
313
  import { useSidePanelVisible } from "./composables/useSidePanelVisible";
@@ -283,6 +315,7 @@ import { useSelectedResult } from "./composables/useSelectedResult";
283
315
  import { useMcpTools } from "./composables/useMcpTools";
284
316
  import { useRoles } from "./composables/useRoles";
285
317
  import { useCurrentRole } from "./composables/useCurrentRole";
318
+ import { useTranslatedQueries } from "./composables/useTranslatedQueries";
286
319
  import { BUILTIN_ROLE_IDS, type Role } from "./config/roles";
287
320
  import { usePubSub } from "./composables/usePubSub";
288
321
  import { sessionChannel } from "./config/pubsubChannels";
@@ -297,6 +330,8 @@ import { apiGet } from "./utils/api";
297
330
  import { API_ROUTES } from "./config/apiRoutes";
298
331
  import { classifyWorkspacePath } from "./utils/path/workspaceLinkRouter";
299
332
 
333
+ const { t, locale } = useI18n();
334
+
300
335
  // --- Per-session state ---
301
336
  // Declared early so that pub/sub callbacks and function declarations
302
337
  // below can reference them without forward-reference ambiguity.
@@ -340,17 +375,6 @@ function navigateToSession(sessionId: string, replace = false): void {
340
375
  });
341
376
  }
342
377
 
343
- function handleNotificationNavigate(action: NotificationAction): void {
344
- const target = resolveNotificationTarget(action);
345
- if (!target) return;
346
- // No special-casing for chat targets: PR #774 moved the role
347
- // selector's state into the `useCurrentRole` singleton, so the
348
- // role no longer needs to be pinned via `?role=` on every
349
- // session-navigate. router.push(target) keeps the user's
350
- // current role choice across the navigation automatically.
351
- router.push(target).catch(() => {});
352
- }
353
-
354
378
  // External URL changes (back/forward button, typed URL) → update ref.
355
379
  // If the session isn't in memory, load it from the server.
356
380
  watch(
@@ -384,11 +408,17 @@ const userInput = ref("");
384
408
  const pastedFile = ref<PastedFile | null>(null);
385
409
  const activePane = ref<"sidebar" | "main">("sidebar");
386
410
 
387
- const { sessions, historyError, fetchSessions } = useSessionHistory();
411
+ const { sessions, historyError, fetchSessions, setBookmark, deleteSession: deleteSessionFromHistory } = useSessionHistory();
388
412
  const { markSessionRead } = useSessionSync({
389
413
  sessionMap,
390
414
  currentSessionId,
391
415
  fetchSessions,
416
+ // Another tab hard-deleted the chat we're currently viewing. The
417
+ // sessionMap eviction has already cleared the in-memory state; the
418
+ // URL still points at the dead id. Mirror the URL→404 fallback on
419
+ // line ~366 by spinning up a fresh session so the user lands on a
420
+ // working /chat instead of a blank pane.
421
+ onCurrentSessionDeleted: () => createNewSession(),
392
422
  });
393
423
  const { geminiAvailable, sandboxEnabled, cpuLoadRatio, fetchHealth } = useHealth();
394
424
 
@@ -429,6 +459,12 @@ const sessionRole = computed<Role>(() => {
429
459
  return roles.value[0];
430
460
  });
431
461
 
462
+ // Translated suggested-query strings for the active session's role.
463
+ // Falls back to the role's English source until /api/translation
464
+ // returns; subsequent role swaps hit the in-memory cache.
465
+ const currentLocale = computed(() => String(locale.value));
466
+ const { queries: sessionRoleQueries } = useTranslatedQueries(sessionRole, currentLocale);
467
+
432
468
  const { mergedSessions, tabSessions } = useMergedSessions({
433
469
  sessionMap,
434
470
  sessions,
@@ -446,6 +482,11 @@ const { mergedSessions, tabSessions } = useMergedSessions({
446
482
  // very same tick as `isRunning` flips, without waiting for the next
447
483
  // /api/sessions refetch.
448
484
  useFaviconState({ isRunning, sessions: mergedSessions, sessionsUnreadCount: unreadCount, cpuLoadRatio });
485
+ useGlobalImageErrorRepair();
486
+ // Boot-time plugin META aggregator collisions surface as notifier
487
+ // entries from the server side; the notifier engine's persistent
488
+ // `active.json` covers the late-mount case (PR 4 of feat-encore),
489
+ // so no client-side catch-up fetch is required here.
449
490
 
450
491
  const sessionSidebarRef = ref<{ root: HTMLDivElement | null } | null>(null);
451
492
  const canvasRef = ref<HTMLDivElement | null>(null);
@@ -476,7 +517,7 @@ function setSidePanelVisibleAndCollapse(value: boolean): void {
476
517
  // full-width views.
477
518
  const isChatPage = computed(() => route.name === PAGE_ROUTES.chat);
478
519
  const currentPage = computed<PageRouteName | null>(() => {
479
- const name = route.name;
520
+ const { name } = route;
480
521
  return typeof name === "string" && isPageRouteName(name) ? name : null;
481
522
  });
482
523
 
@@ -561,6 +602,12 @@ const { elapsedMs: runElapsedMs, teardown: teardownRunElapsed } = useRunElapsed(
561
602
 
562
603
  const selectedResult = computed(() => toolResults.value.find((result) => result.uuid === selectedResultUuid.value) ?? null);
563
604
 
605
+ // Debug-plugin View component, looked up by tool name. The plugin
606
+ // loader populates this asynchronously at boot — `runtimeRegistry` is
607
+ // reactive, so this computed re-evaluates when the load completes and
608
+ // the /debug branch in the template lights up without a refresh.
609
+ const debugViewComponent = computed(() => getPlugin("manageDebug")?.viewComponent ?? null);
610
+
564
611
  // Centralised session-switch handler: subscribe to the current session's
565
612
  // pub/sub channel so we receive real-time events even if the session is
566
613
  // idle (another tab may start a run). Unsubscribe from idle sessions
@@ -579,9 +626,11 @@ watch(currentSessionId, (sessionId) => {
579
626
  // event, leaving the session's busy indicator stuck on.
580
627
  if (previousSessionId && previousSessionId !== sessionId) {
581
628
  const prevSession = sessionMap.get(previousSessionId);
582
- const prevBusy = !!prevSession && (prevSession.isRunning || Object.keys(prevSession.pendingGenerations ?? {}).length > 0);
583
- if (prevSession && !prevBusy) {
584
- unsubscribeSession(previousSessionId);
629
+ if (prevSession !== undefined) {
630
+ const prevBusy = prevSession.isRunning || Object.keys(prevSession.pendingGenerations ?? {}).length > 0;
631
+ if (!prevBusy) {
632
+ unsubscribeSession(previousSessionId);
633
+ }
585
634
  }
586
635
  }
587
636
  previousSessionId = sessionId;
@@ -647,7 +696,7 @@ function createNewSession(roleId?: string): ActiveSession {
647
696
  navigateToSession(session.id, replace);
648
697
  chatInputRef.value?.collapseSuggestions();
649
698
  nextTick(() => focusChatInput());
650
- return sessionMap.get(session.id)!;
699
+ return session;
651
700
  }
652
701
 
653
702
  function onRoleChange(roleId: string) {
@@ -748,7 +797,8 @@ async function refreshSessionTranscript(sessionId: string): Promise<void> {
748
797
  if (!session) return;
749
798
  const response = await apiGet<SessionEntry[]>(API_ROUTES.sessions.detail.replace(":id", encodeURIComponent(sessionId)));
750
799
  if (!response.ok) return;
751
- const serverResults = parseSessionEntries(response.data);
800
+ const summary = sessions.value.find((entry) => entry.id === sessionId);
801
+ const serverResults = parseSessionEntries(response.data, summary?.origin);
752
802
  // Only patch if the server knows more than we do — avoids
753
803
  // replacing a richer in-flight state with a stale snapshot when
754
804
  // session_finished races with the last few events.
@@ -775,7 +825,8 @@ function buildAgentEventContext(session: ActiveSession): AgentEventContext {
775
825
 
776
826
  function hasPendingGenerations(sessionId: string): boolean {
777
827
  const live = sessionMap.get(sessionId);
778
- return !!live && Object.keys(live.pendingGenerations).length > 0;
828
+ if (live === undefined) return false;
829
+ return Object.keys(live.pendingGenerations).length > 0;
779
830
  }
780
831
 
781
832
  function handleSessionFinished(sessionId: string): void {
@@ -826,11 +877,35 @@ async function sendMessage(text?: string) {
826
877
  const fileSnapshot = pastedFile.value;
827
878
  pastedFile.value = null;
828
879
 
880
+ // Pasted / dropped files are pre-uploaded to a workspace file so
881
+ // the server (and the LLM downstream) sees a relative path — never
882
+ // a data: URI. The path then rides on `attachments[]` as a path-only
883
+ // entry. On upload failure, restore both userInput and pastedFile so
884
+ // the user can retry without retyping.
885
+ let attachmentForRequest: string | undefined;
886
+ if (fileSnapshot) {
887
+ const resolved = await resolvePastedAttachment(fileSnapshot);
888
+ if (!resolved.ok) {
889
+ userInput.value = message;
890
+ pastedFile.value = fileSnapshot;
891
+ const recoverySession = sessionMap.get(currentSessionId.value);
892
+ if (recoverySession) pushErrorMessage(recoverySession, t("chatInput.attachImageFailed", { error: resolved.error }));
893
+ return;
894
+ }
895
+ attachmentForRequest = resolved.value;
896
+ }
897
+
829
898
  const session = sessionMap.get(currentSessionId.value);
830
899
  if (!session) return;
831
900
 
832
- beginUserTurn(session, message);
833
- const selectedRes = session.toolResults.find((result) => result.uuid === session.selectedResultUuid) ?? undefined;
901
+ // Only files the user explicitly attached this turn (paste / drop /
902
+ // file-picker) ride on the message. Do NOT auto-attach whatever
903
+ // image happens to be selected in the sidebar — selection moves to
904
+ // the latest generated image automatically, which would silently
905
+ // glue the previous picture onto every follow-up comment.
906
+ const attachmentPaths = attachmentForRequest ? [attachmentForRequest] : undefined;
907
+
908
+ beginUserTurn(session, message, attachmentPaths);
834
909
 
835
910
  ensureSessionSubscription(session);
836
911
 
@@ -839,7 +914,7 @@ async function sendMessage(text?: string) {
839
914
  message,
840
915
  role: sessionRole.value,
841
916
  chatSessionId: session.id,
842
- selectedImageData: fileSnapshot?.dataUrl ?? extractImageData(selectedRes),
917
+ attachmentPaths,
843
918
  }),
844
919
  );
845
920
  if (!result.ok) {
@@ -0,0 +1,21 @@
1
+ // Re-export `gui-chat-protocol/vue` so a runtime-loaded plugin (#1110)
2
+ // can share the host's `useRuntime()` / `PLUGIN_RUNTIME_KEY` instances
3
+ // via importmap.
4
+ //
5
+ // Why a re-export at all: PLUGIN_RUNTIME_KEY is a `Symbol` created at
6
+ // module load time. If two copies of `gui-chat-protocol/vue` were
7
+ // loaded — one bundled into the host, another inlined in the plugin —
8
+ // they would each create their own Symbol, and `provide(K, runtime)`
9
+ // from the host would land on a different key than the plugin's
10
+ // `inject(K)` — `useRuntime()` would throw "called outside of
11
+ // <PluginScopedRoot>" even though the wrapper IS in the tree.
12
+ //
13
+ // The importmap entry in `index.html` (`gui-chat-protocol/vue` →
14
+ // `/src/_runtime/protocol-vue.ts`) makes the browser resolve the
15
+ // plugin's bare `import { useRuntime } from "gui-chat-protocol/vue"`
16
+ // to this same file the host already loaded — guaranteeing one
17
+ // shared module instance / one Symbol / one working inject path.
18
+ //
19
+ // `export *` mirrors the runtime-vue pattern next door: plugins compile
20
+ // against an evolving surface; we don't want to whitelist names here.
21
+ export * from "gui-chat-protocol/vue";
@@ -0,0 +1,22 @@
1
+ // Re-export Vue's runtime so a runtime-loaded plugin (#1043 C-2) can
2
+ // share the host's Vue instance via importmap. The browser resolves a
3
+ // plugin's bare `import "vue"` (after the importmap declared in
4
+ // `index.html`) to this file. Vite transforms this module's own
5
+ // `from "vue"` to the same Vue chunk the host app uses, so host and
6
+ // runtime-loaded plugin component code end up referencing the same
7
+ // Vue singleton — reactivity flows across the boundary, and there is
8
+ // only one `createApp`, one Composition API, etc.
9
+ //
10
+ // Why `export *` and not a named re-export: plugin bundles compiled
11
+ // against `gui-chat-protocol/vue` import a wide and evolving surface
12
+ // of Vue (defineComponent, h, ref, computed, watch, …). Whitelisting
13
+ // would force this file to track Vue's API. `export *` gives the
14
+ // plugin the full surface it was compiled against.
15
+ //
16
+ // Build emit: registered as a Rollup input (`runtime-vue`) in
17
+ // `vite.config.ts` so it survives tree-shaking even though no
18
+ // static `import` references it. The build-only
19
+ // `runtimeImportmapBuildPlugin` rewrites the dev importmap URL
20
+ // (`/src/_runtime/vue.ts`) to the hashed asset filename in the
21
+ // emitted `index.html`.
22
+ export * from "vue";
@@ -1,6 +1,12 @@
1
1
  <template>
2
2
  <div class="border-t border-gray-200" @dragover.prevent @drop="onDropFile">
3
- <SuggestionsPanel v-model:expanded="suggestionsExpanded" :queries="queries" @send="onSuggestionSend" @edit="onSuggestionEdit" />
3
+ <SuggestionsPanel
4
+ v-model:expanded="suggestionsExpanded"
5
+ :queries="queries"
6
+ :trigger-ref="suggestionsBtnRef"
7
+ @send="onSuggestionSend"
8
+ @edit="onSuggestionEdit"
9
+ />
4
10
  <div class="p-2">
5
11
  <div v-if="fileError" class="mb-2 text-xs text-red-600 bg-red-50 border border-red-200 rounded px-3 py-1.5" data-testid="file-error">
6
12
  {{ fileError }}
@@ -30,12 +36,12 @@
30
36
  />
31
37
  <div class="flex flex-col gap-1">
32
38
  <button
33
- v-if="queries.length > 0"
39
+ ref="suggestionsBtnRef"
34
40
  data-testid="suggestions-btn"
35
41
  class="rounded w-8 h-8 flex items-center justify-center"
36
42
  :class="suggestionsExpanded ? 'text-blue-600 bg-blue-50' : 'text-gray-400 hover:text-gray-600'"
37
- :title="t('suggestionsPanel.suggestions')"
38
- :aria-label="t('suggestionsPanel.suggestions')"
43
+ :title="t('suggestionsPanel.tooltip')"
44
+ :aria-label="t('suggestionsPanel.tooltip')"
39
45
  @click="suggestionsExpanded = !suggestionsExpanded"
40
46
  >
41
47
  <span class="material-icons text-base leading-none">lightbulb</span>
@@ -77,14 +83,11 @@ import { useI18n } from "vue-i18n";
77
83
  import ChatAttachmentPreview from "./ChatAttachmentPreview.vue";
78
84
  import SuggestionsPanel from "./SuggestionsPanel.vue";
79
85
  import { useImeAwareEnter } from "../composables/useImeAwareEnter";
86
+ import type { PastedFile } from "../types/pastedFile";
80
87
 
81
- const { t } = useI18n();
88
+ export type { PastedFile };
82
89
 
83
- export interface PastedFile {
84
- dataUrl: string;
85
- name: string;
86
- mime: string;
87
- }
90
+ const { t } = useI18n();
88
91
 
89
92
  withDefaults(
90
93
  defineProps<{
@@ -107,6 +110,7 @@ const textarea = ref<HTMLTextAreaElement | null>(null);
107
110
  const fileError = ref<string | null>(null);
108
111
  const fileInput = ref<HTMLInputElement | null>(null);
109
112
  const suggestionsExpanded = ref(false);
113
+ const suggestionsBtnRef = ref<HTMLButtonElement | null>(null);
110
114
 
111
115
  const MAX_ATTACH_BYTES = 30 * 1024 * 1024;
112
116
 
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <!-- Copies the current chat session to the clipboard as Markdown.
3
+ Lives in both <SessionSidebar> (single layout) and <StackView>
4
+ (stack layout) so the affordance is in the same visual slot
5
+ regardless of which layout the user is in.
6
+
7
+ Success feedback is local: the icon swaps to a check for ~1.5s.
8
+ No global toast composable yet (#TODO once one exists), and a
9
+ transient icon swap is enough since the user's hand is already
10
+ on the button. -->
11
+ <button
12
+ type="button"
13
+ class="h-8 w-8 flex items-center justify-center rounded text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
14
+ :class="{ '!text-green-600': justCopied }"
15
+ data-testid="copy-chat-md"
16
+ :disabled="results.length === 0"
17
+ :title="justCopied ? t('sidebarHeader.copiedMarkdown') : t('sidebarHeader.copyMarkdown')"
18
+ :aria-label="justCopied ? t('sidebarHeader.copiedMarkdown') : t('sidebarHeader.copyMarkdown')"
19
+ @click="onCopy"
20
+ >
21
+ <span class="material-icons text-lg" aria-hidden="true">{{ justCopied ? "check" : "content_copy" }}</span>
22
+ </button>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ import { ref, onUnmounted } from "vue";
27
+ import { useI18n } from "vue-i18n";
28
+ import type { ToolResultComplete } from "gui-chat-protocol/vue";
29
+ import { exportChatToMarkdown } from "../utils/chat/exportMarkdown";
30
+ import { apiGet } from "../utils/api";
31
+ import { API_ROUTES } from "../config/apiRoutes";
32
+
33
+ const COPIED_FEEDBACK_MS = 1500;
34
+
35
+ const { t } = useI18n();
36
+
37
+ const props = defineProps<{
38
+ results: ToolResultComplete[];
39
+ resultTimestamps: Map<string, number>;
40
+ sessionRoleName?: string;
41
+ }>();
42
+
43
+ const justCopied = ref(false);
44
+ let resetTimeout: ReturnType<typeof setTimeout> | null = null;
45
+
46
+ async function readWorkspaceFile(path: string): Promise<string | null> {
47
+ const result = await apiGet<{ content?: string }>(API_ROUTES.files.content, { path });
48
+ if (!result.ok) return null;
49
+ return result.data.content ?? null;
50
+ }
51
+
52
+ async function onCopy(): Promise<void> {
53
+ const markdown = await exportChatToMarkdown(props.results, {
54
+ sessionRoleName: props.sessionRoleName,
55
+ resultTimestamps: props.resultTimestamps,
56
+ readFile: readWorkspaceFile,
57
+ });
58
+ try {
59
+ await navigator.clipboard.writeText(markdown);
60
+ justCopied.value = true;
61
+ if (resetTimeout !== null) clearTimeout(resetTimeout);
62
+ resetTimeout = setTimeout(() => {
63
+ justCopied.value = false;
64
+ resetTimeout = null;
65
+ }, COPIED_FEEDBACK_MS);
66
+ } catch {
67
+ // Clipboard write can reject when the document isn't focused or
68
+ // permission is denied. The missing visual confirmation is itself
69
+ // the user-facing signal; logging would only spam the console.
70
+ }
71
+ }
72
+
73
+ onUnmounted(() => {
74
+ if (resetTimeout !== null) clearTimeout(resetTimeout);
75
+ });
76
+ </script>