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,32 @@
1
+ // Vue-free server-facing barrel for every built-in plugin's MCP
2
+ // binding (ToolDefinition + REST endpoint). Sibling barrels:
3
+ //
4
+ // - `src/plugins/index.ts` — REGISTRATIONS with Vue View / Preview
5
+ // (frontend uses this; server can't —
6
+ // Vue dependency)
7
+ // - `src/plugins/metas.ts` — per-plugin META (toolName, apiRoutes,
8
+ // workspaceDirs, staticChannels). Both
9
+ // server and frontend safe.
10
+ // - `src/plugins/server.ts` — THIS FILE: server-only binding table
11
+ // (definition + MCP dispatch endpoint).
12
+ // Imports each plugin's `definition.ts`
13
+ // directly (no Vue, server-safe).
14
+ //
15
+ // **Auto-generated**. Standard plugins (one dir, one META declaring
16
+ // `apiNamespace` + `mcpDispatch`) flow through
17
+ // `_generated/server-bindings.ts`; cross-namespace bindings (image
18
+ // plugins reaching the host's `/api/image/*` routes) and external
19
+ // npm plugins (mindmap / quiz / present3d) live in `_extras.ts`.
20
+ // Adding a standard plugin requires no edit here.
21
+
22
+ import { ServerPluginBinding } from "./server-bindings-types";
23
+ import { GENERATED_SERVER_BINDINGS } from "./_generated/server-bindings";
24
+ import { EXTRA_SERVER_BINDINGS } from "./_extras";
25
+
26
+ export type { ServerPluginBinding };
27
+
28
+ /** All built-in plugin MCP bindings. The two scheduler defs share
29
+ * one dispatch URL (the server splits calendar vs task actions via
30
+ * the action enum), so the codegen emits both rows pointing at the
31
+ * calendar META — see `_generated/server-bindings.ts`. */
32
+ export const BUILT_IN_SERVER_BINDINGS: readonly ServerPluginBinding[] = [...GENERATED_SERVER_BINDINGS, ...EXTRA_SERVER_BINDINGS];
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <!-- Sidebar / chat-history thumbnail for a skill envelope.
3
+ Single line: extension icon + skill name. The full body and
4
+ description live behind the canvas's expand toggle. -->
5
+ <div class="flex items-center gap-1.5 text-sm text-gray-700">
6
+ <span class="material-icons text-purple-500 text-sm shrink-0">extension</span>
7
+ <span class="truncate font-medium">{{ skillName }}</span>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { computed } from "vue";
13
+ import type { ToolResultComplete } from "gui-chat-protocol/vue";
14
+ import type { SkillData } from "./types";
15
+
16
+ // Sidebar previews are rendered with `:result="result"` (see
17
+ // SessionSidebar.vue), not `:selected-result`. Codex iter-2 review
18
+ // on PR #1220 — the previous prop name left `props.result`
19
+ // undefined and broke skill rendering in the chat-history sidebar.
20
+ const props = defineProps<{
21
+ result: ToolResultComplete<SkillData>;
22
+ }>();
23
+
24
+ const skillName = computed(() => props.result.data?.skillName ?? "skill");
25
+ </script>
@@ -0,0 +1,125 @@
1
+ <template>
2
+ <!-- The canvas reserves the full pane height for the selected
3
+ result regardless of how much vertical space it actually
4
+ needs. Without explicit centering, a short collapsed skill
5
+ card sits top-anchored with a tall empty void below. Outer
6
+ is `h-full flex flex-col` so the inner can use `my-auto` to
7
+ vertically centre when the card is short, while still
8
+ falling back to natural top-alignment + scrolling when the
9
+ user expands the markdown body and content overflows
10
+ (auto-margins collapse to 0 in the overflow case, unlike
11
+ `justify-content: center` which would clip the top of tall
12
+ content out of reach). #1218 follow-up. -->
13
+ <div class="h-full flex flex-col overflow-y-auto p-6">
14
+ <div class="my-auto max-w-3xl mx-auto w-full">
15
+ <div class="rounded-lg border border-purple-200 bg-purple-50 shadow-sm">
16
+ <!-- Collapsed header — clickable. The whole card collapses by
17
+ default; clicking expands the skill body. The use of
18
+ `<details>` keeps keyboard / a11y semantics correct. -->
19
+ <details class="group">
20
+ <summary class="cursor-pointer list-none p-4 flex items-start gap-3 hover:bg-purple-100/40 rounded-lg" :data-testid="'skill-summary-' + skillName">
21
+ <span class="material-icons text-purple-600 text-base mt-0.5 shrink-0">extension</span>
22
+ <div class="flex-1 min-w-0">
23
+ <div class="flex items-baseline gap-2 flex-wrap">
24
+ <span class="font-medium text-purple-900">{{ skillName }}</span>
25
+ <span v-if="skillScope !== 'unknown'" class="text-[10px] uppercase tracking-wide text-purple-500 px-1.5 py-0.5 rounded-full bg-purple-100">
26
+ {{ skillScope }}
27
+ </span>
28
+ </div>
29
+ <div v-if="skillDescription" class="text-sm text-gray-700 mt-1">{{ skillDescription }}</div>
30
+ <div v-else class="text-sm text-gray-500 italic mt-1">{{ t("pluginSkill.noDescription") }}</div>
31
+ </div>
32
+ <span class="material-icons text-gray-400 text-base shrink-0 group-open:rotate-180 transition-transform">expand_more</span>
33
+ </summary>
34
+
35
+ <!-- Expanded body — markdown-rendered. -->
36
+ <div class="border-t border-purple-200 p-4 bg-white rounded-b-lg">
37
+ <div v-if="skillPath" class="text-[11px] font-mono text-gray-400 mb-3 break-all">{{ skillPath }}</div>
38
+ <!-- eslint-disable-next-line vue/no-v-html -- DOMPurify-sanitized markdown of the SKILL.md body Claude CLI synthesised. The body comes from the user's local skill file, surfaced verbatim here. -->
39
+ <div class="markdown-content prose prose-slate max-w-none" v-html="renderedHtml"></div>
40
+ </div>
41
+ </details>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </template>
46
+
47
+ <script setup lang="ts">
48
+ import { computed } from "vue";
49
+ import { useI18n } from "vue-i18n";
50
+ import { marked } from "marked";
51
+ import DOMPurify from "dompurify";
52
+ import type { ToolResultComplete } from "gui-chat-protocol/vue";
53
+ import type { SkillData } from "./types";
54
+
55
+ const { t } = useI18n();
56
+
57
+ const props = defineProps<{
58
+ selectedResult: ToolResultComplete<SkillData>;
59
+ }>();
60
+
61
+ const skillName = computed(() => props.selectedResult.data?.skillName ?? "");
62
+ const skillScope = computed(() => props.selectedResult.data?.skillScope ?? "unknown");
63
+ const skillPath = computed(() => props.selectedResult.data?.skillPath ?? null);
64
+ const skillDescription = computed(() => props.selectedResult.data?.skillDescription ?? null);
65
+ const body = computed(() => props.selectedResult.data?.body ?? "");
66
+
67
+ const renderedHtml = computed(() => DOMPurify.sanitize(marked(body.value, { breaks: true, gfm: true }) as string));
68
+ </script>
69
+
70
+ <style scoped>
71
+ .markdown-content :deep(h1) {
72
+ font-size: 1.5rem;
73
+ font-weight: bold;
74
+ margin-top: 1em;
75
+ margin-bottom: 0.5em;
76
+ }
77
+ .markdown-content :deep(h2) {
78
+ font-size: 1.25rem;
79
+ font-weight: bold;
80
+ margin-top: 1em;
81
+ margin-bottom: 0.5em;
82
+ }
83
+ .markdown-content :deep(h3) {
84
+ font-size: 1.125rem;
85
+ font-weight: bold;
86
+ margin-top: 0.75em;
87
+ margin-bottom: 0.5em;
88
+ }
89
+ .markdown-content :deep(p) {
90
+ margin-bottom: 0.75em;
91
+ }
92
+ .markdown-content :deep(ul),
93
+ .markdown-content :deep(ol) {
94
+ margin-left: 1.5em;
95
+ margin-bottom: 0.75em;
96
+ }
97
+ .markdown-content :deep(code) {
98
+ background-color: #f5f5f5;
99
+ padding: 0.2em 0.4em;
100
+ border-radius: 3px;
101
+ font-family: monospace;
102
+ font-size: 0.9em;
103
+ }
104
+ .markdown-content :deep(pre) {
105
+ background-color: #f5f5f5;
106
+ padding: 0.75em;
107
+ border-radius: 4px;
108
+ overflow-x: auto;
109
+ margin-bottom: 0.75em;
110
+ }
111
+ .markdown-content :deep(pre code) {
112
+ background-color: transparent;
113
+ padding: 0;
114
+ }
115
+ .markdown-content :deep(blockquote) {
116
+ border-left: 3px solid #d1d5db;
117
+ padding-left: 1em;
118
+ color: #4b5563;
119
+ margin: 0.75em 0;
120
+ }
121
+ .markdown-content :deep(a) {
122
+ color: #2563eb;
123
+ text-decoration: underline;
124
+ }
125
+ </style>
@@ -0,0 +1,23 @@
1
+ // Skill plugin — pseudo-tool used only by the client to render
2
+ // `type: "skill"` jsonl entries (#1218). The LLM never invokes this
3
+ // tool directly — it sees the real `Skill` tool defined by Claude
4
+ // CLI; this plugin's job is purely to claim a `toolName` slot in
5
+ // `getPlugin()` so the canvas routes skill envelopes through
6
+ // `View.vue` instead of the default text-response renderer.
7
+
8
+ import type { ToolDefinition } from "gui-chat-protocol";
9
+
10
+ export const TOOL_NAME = "skill";
11
+
12
+ export const TOOL_DEFINITION: ToolDefinition = {
13
+ type: "function",
14
+ name: TOOL_NAME,
15
+ description: "Internal: collapsed view of a SKILL.md body Claude CLI synthesised after a `Skill` tool invocation.",
16
+ parameters: {
17
+ type: "object",
18
+ properties: {},
19
+ required: [],
20
+ },
21
+ };
22
+
23
+ export const SYSTEM_PROMPT = "";
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Skill plugin (#1218) — internal-only, claims `toolName: "skill"` so
3
+ * the canvas routes synthetic envelopes built by `makeSkillResult`
4
+ * through `View.vue` instead of the default text-response renderer.
5
+ *
6
+ * Discovered automatically by `yarn plugins:codegen` (which scans
7
+ * `src/plugins/<name>/index.ts` for an exported `REGISTRATION`).
8
+ */
9
+
10
+ import type { ToolPlugin } from "gui-chat-protocol/vue";
11
+ import type { PluginRegistration } from "../../tools/types";
12
+ import type { SkillData, SkillArgs } from "./types";
13
+ import { pluginCore, TOOL_NAME } from "./plugin";
14
+ import View from "./View.vue";
15
+ import Preview from "./Preview.vue";
16
+
17
+ export const plugin: ToolPlugin<SkillData, unknown, SkillArgs> = {
18
+ ...pluginCore,
19
+ viewComponent: View,
20
+ previewComponent: Preview,
21
+ };
22
+
23
+ export type { SkillData, SkillArgs } from "./types";
24
+
25
+ export { TOOL_DEFINITION, SYSTEM_PROMPT, executeSkill, pluginCore } from "./plugin";
26
+ export { TOOL_NAME };
27
+
28
+ export { View, Preview };
29
+
30
+ const skillPlugin = { plugin };
31
+ export default skillPlugin;
32
+
33
+ export const REGISTRATION: PluginRegistration = {
34
+ toolName: TOOL_NAME,
35
+ entry: plugin,
36
+ };
@@ -0,0 +1,31 @@
1
+ // Skill plugin core — pseudo-tool, never advertised to the LLM.
2
+ // `isEnabled: () => false` keeps it out of the agent's tool list;
3
+ // the plugin only exists so the canvas's `getPlugin("skill")`
4
+ // returns a registration that points the renderer at this View.
5
+
6
+ import type { ToolPluginCore, ToolContext, ToolResult } from "gui-chat-protocol";
7
+ import type { SkillData, SkillArgs } from "./types";
8
+ import { TOOL_DEFINITION, SYSTEM_PROMPT } from "./definition";
9
+
10
+ export { TOOL_NAME, TOOL_DEFINITION, SYSTEM_PROMPT } from "./definition";
11
+
12
+ export const executeSkill = async (_context: ToolContext, args: SkillArgs): Promise<ToolResult<SkillData, unknown>> => ({
13
+ data: {
14
+ skillName: args.skillName,
15
+ skillScope: args.skillScope,
16
+ skillPath: args.skillPath,
17
+ skillDescription: args.skillDescription,
18
+ body: args.body,
19
+ },
20
+ message: args.skillDescription ?? args.skillName,
21
+ });
22
+
23
+ export const pluginCore: ToolPluginCore<SkillData, unknown, SkillArgs> = {
24
+ toolDefinition: TOOL_DEFINITION,
25
+ execute: executeSkill,
26
+ generatingMessage: "Loading skill...",
27
+ // Pseudo-tool — only the client builds these envelopes when
28
+ // parsing `type: "skill"` jsonl entries / `SseSkill` events.
29
+ isEnabled: () => false,
30
+ systemPrompt: SYSTEM_PROMPT,
31
+ };
@@ -0,0 +1,21 @@
1
+ // Wire shape of a `toolName: "skill"` ToolResultComplete envelope —
2
+ // produced by `makeSkillResult` in `src/utils/tools/result.ts` from
3
+ // either a `type: "skill"` jsonl entry on session reload, or a
4
+ // `SseSkill` event broadcast at flush time during a live run.
5
+
6
+ import type { SkillScope } from "../../types/session";
7
+
8
+ export interface SkillData {
9
+ skillName: string;
10
+ skillScope: SkillScope;
11
+ skillPath: string | null;
12
+ skillDescription: string | null;
13
+ /** Full SKILL.md body as Claude CLI synthesised it. Frontmatter is
14
+ * already stripped (Claude CLI does that before injection); the
15
+ * body starts with `Base directory for this skill: <path>` and
16
+ * ends with `ARGUMENTS: <user message>`. The View renders this
17
+ * with markdown when the user expands the collapsed card. */
18
+ body: string;
19
+ }
20
+
21
+ export type SkillArgs = SkillData;
@@ -21,9 +21,7 @@ const props = defineProps<{
21
21
  result: ToolResult<SpreadsheetToolData>;
22
22
  }>();
23
23
 
24
- const displayTitle = computed(() => {
25
- return props.result.title || t("pluginSpreadsheet.previewUntitled");
26
- });
24
+ const displayTitle = computed(() => props.result.title || t("pluginSpreadsheet.previewUntitled"));
27
25
 
28
26
  const sheetCount = computed(() => {
29
27
  const sheets = props.result.data?.sheets;
@@ -41,6 +41,7 @@
41
41
  </div>
42
42
 
43
43
  <!-- Spreadsheet table -->
44
+ <!-- eslint-disable-next-line vue/no-v-html -- XLSX.utils.sheet_to_html output of app-owned sheet data; trusted in-process render -->
44
45
  <div ref="tableContainer" class="table-container" @click="handleTableClick" v-html="renderedHtml"></div>
45
46
  </div>
46
47
  </div>
@@ -110,7 +111,7 @@ import { computed, ref, watch, onMounted, onUnmounted } from "vue";
110
111
  import { useI18n } from "vue-i18n";
111
112
  import * as XLSX from "xlsx";
112
113
  import type { ToolResult } from "gui-chat-protocol";
113
- import type { SpreadsheetToolData, SpreadsheetSheet } from "./definition";
114
+ import type { SpreadsheetToolData, SpreadsheetSheet, SpreadsheetEndpoints } from "./definition";
114
115
  import {
115
116
  SpreadsheetEngine,
116
117
  indexToColumn,
@@ -122,15 +123,19 @@ import {
122
123
  type CellValue,
123
124
  } from "./engine";
124
125
  import { applyCellHighlights, clearCellHighlights } from "./cellHighlights";
126
+ import { getArrowKeyOffset, isWithinSheetBounds } from "./keyboardNav";
125
127
  import { errorMessage as formatErrorMessage } from "../../utils/errors";
126
128
 
127
129
  // Import all spreadsheet functions to populate the function registry
128
130
  import "./engine/functions";
129
131
  import { apiGet, apiPut } from "../../utils/api";
130
- import { API_ROUTES } from "../../config/apiRoutes";
132
+ import { pluginEndpoints } from "../api";
131
133
  import type { FilesContentResponseLike } from "./engine/responseDecoder";
132
134
  import { isObj, isRecord } from "../../utils/types";
133
135
 
136
+ const endpoints = pluginEndpoints<SpreadsheetEndpoints>("spreadsheet");
137
+ const filesEndpoints = pluginEndpoints<{ content: string }>("files");
138
+
134
139
  const { t } = useI18n();
135
140
 
136
141
  /**
@@ -202,6 +207,11 @@ function isFilePath(value: unknown): value is string {
202
207
  return typeof value === "string" && value.startsWith("artifacts/spreadsheets/") && value.endsWith(".json");
203
208
  }
204
209
 
210
+ // Editor textarea backing ref. Declared up here (rather than next to
211
+ // the other view-state refs further down) so the `fetchSheets().then`
212
+ // initializer below can assign to it without TDZ.
213
+ const editableData = ref(JSON.stringify(resolvedSheets.value || [], null, 2));
214
+
205
215
  async function fetchSheets(): Promise<void> {
206
216
  const raw = props.selectedResult.data?.sheets;
207
217
  // Clear any stale error from a previous result BEFORE the early
@@ -218,7 +228,7 @@ async function fetchSheets(): Promise<void> {
218
228
  return;
219
229
  }
220
230
  loading.value = true;
221
- const response = await apiGet<FilesContentResponseLike>(API_ROUTES.files.content, { path: raw });
231
+ const response = await apiGet<FilesContentResponseLike>(filesEndpoints.content, { path: raw });
222
232
  if (!response.ok) {
223
233
  errorMessage.value = t("pluginSpreadsheet.loadFailed", { error: response.error });
224
234
  resolvedSheets.value = [];
@@ -250,7 +260,7 @@ async function persistSheets(sheets: SpreadsheetSheet[]): Promise<void> {
250
260
  // Send the full workspace-relative path so the route doesn't have
251
261
  // to reconstruct one from a basename — paths under
252
262
  // `artifacts/spreadsheets/` are now sharded by YYYY/MM (#764).
253
- const result = await apiPut<unknown>(API_ROUTES.plugins.updateSpreadsheet, {
263
+ const result = await apiPut<unknown>(endpoints.update.url, {
254
264
  relativePath: raw,
255
265
  sheets,
256
266
  });
@@ -273,7 +283,9 @@ async function persistSheets(sheets: SpreadsheetSheet[]): Promise<void> {
273
283
  }
274
284
 
275
285
  const activeSheetIndex = ref(0);
276
- const editableData = ref(JSON.stringify(resolvedSheets.value || [], null, 2));
286
+ // Editor state declared together with the other refs above. Seeded
287
+ // in the on-mount `fetchSheets().then(...)` block above (the `.value`
288
+ // assignment is in scope by the time that .then callback fires).
277
289
  const editorTextarea = ref<HTMLTextAreaElement | null>(null);
278
290
  const editorDetails = ref<HTMLDetailsElement | null>(null);
279
291
  const tableContainer = ref<HTMLDivElement | null>(null);
@@ -288,7 +300,7 @@ const miniEditorFormula = ref("");
288
300
  const miniEditorFormat = ref("");
289
301
 
290
302
  // Referenced cells state (for formula highlighting)
291
- const referencedCells = ref<Array<{ row: number; col: number }>>([]);
303
+ const referencedCells = ref<{ row: number; col: number }[]>([]);
292
304
 
293
305
  // Check if spreadsheet data has been modified
294
306
  const hasChanges = computed(() => {
@@ -527,7 +539,7 @@ function handleTableClick(event: MouseEvent) {
527
539
  const row = cell.parentElement as HTMLTableRowElement;
528
540
 
529
541
  const colIndex = cell.cellIndex;
530
- const rowIndex = row.rowIndex;
542
+ const { rowIndex } = row;
531
543
 
532
544
  // Check if the main editor details is open
533
545
  const isEditorOpen = editorDetails.value?.open ?? false;
@@ -620,56 +632,24 @@ watch(
620
632
  { flush: "post" },
621
633
  );
622
634
 
623
- // Keyboard navigation handler
635
+ function isEditableTarget(target: EventTarget | null): boolean {
636
+ if (!(target instanceof HTMLElement)) return false;
637
+ return target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
638
+ }
639
+
624
640
  function handleKeyboardNavigation(event: KeyboardEvent) {
625
- // Only handle arrow keys when mini editor is open and not focused on input
626
641
  if (!miniEditorOpen.value || !miniEditorCell.value) return;
627
-
628
- // Don't interfere if user is typing in an input field
629
- const target = event.target as HTMLElement;
630
- if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
631
- return;
632
- }
642
+ if (isEditableTarget(event.target)) return;
633
643
 
634
644
  const { row, col } = miniEditorCell.value;
635
- let newRow = row;
636
- let newCol = col;
637
-
638
- // Determine new position based on arrow key
639
- switch (event.key) {
640
- case "ArrowUp":
641
- newRow = Math.max(0, row - 1);
642
- break;
643
- case "ArrowDown":
644
- newRow = row + 1;
645
- break;
646
- case "ArrowLeft":
647
- newCol = Math.max(0, col - 1);
648
- break;
649
- case "ArrowRight":
650
- newCol = col + 1;
651
- break;
652
- default:
653
- return; // Not an arrow key, ignore
654
- }
645
+ const next = getArrowKeyOffset(event.key, row, col);
646
+ if (!next) return;
655
647
 
656
- // Get current sheet data to validate bounds
657
648
  try {
658
649
  const sheets = JSON.parse(editableData.value);
659
- const currentSheet = sheets[activeSheetIndex.value];
660
-
661
- if (!currentSheet || !currentSheet.data) return;
662
-
663
- // Validate new position is within bounds
664
- if (newRow < 0 || newRow >= currentSheet.data.length || newCol < 0 || !currentSheet.data[newRow] || newCol >= currentSheet.data[newRow].length) {
665
- return; // Out of bounds, ignore
666
- }
667
-
668
- // Prevent default scrolling behavior
650
+ if (!isWithinSheetBounds(sheets[activeSheetIndex.value], next.row, next.col)) return;
669
651
  event.preventDefault();
670
-
671
- // Move to new cell
672
- openMiniEditor(newRow, newCol);
652
+ openMiniEditor(next.row, next.col);
673
653
  } catch (error) {
674
654
  console.error("Failed to navigate cells:", error);
675
655
  }
@@ -28,9 +28,8 @@ export interface HighlightableContainer {
28
28
  // Overload: the spreadsheet root container is known to return a
29
29
  // table when asked for the table id, so callers can keep the
30
30
  // result strongly typed without casting.
31
- querySelector(selector: "#spreadsheet-table"): HighlightableTable | null;
32
- querySelector(selector: string): HighlightableElement | null;
33
- querySelectorAll(selector: string): ArrayLike<HighlightableElement> & Iterable<HighlightableElement>;
31
+ querySelector: ((selector: "#spreadsheet-table") => HighlightableTable | null) & ((selector: string) => HighlightableElement | null);
32
+ querySelectorAll: (selector: string) => ArrayLike<HighlightableElement> & Iterable<HighlightableElement>;
34
33
  }
35
34
 
36
35
  export interface CellCoord {
@@ -1,6 +1,9 @@
1
1
  import type { ToolDefinition } from "gui-chat-protocol";
2
+ import { META } from "./meta";
3
+ import type { ResolvedRoute } from "../meta-types";
2
4
 
3
- export const TOOL_NAME = "presentSpreadsheet";
5
+ export const TOOL_NAME = META.toolName;
6
+ export type SpreadsheetEndpoints = { readonly [K in keyof typeof META.apiRoutes]: ResolvedRoute };
4
7
 
5
8
  export interface SpreadsheetCell {
6
9
  v: string | number;
@@ -9,7 +12,7 @@ export interface SpreadsheetCell {
9
12
 
10
13
  export interface SpreadsheetSheet {
11
14
  name: string;
12
- data: Array<Array<SpreadsheetCell>>;
15
+ data: SpreadsheetCell[][];
13
16
  }
14
17
 
15
18
  export interface SpreadsheetToolData {
@@ -1,18 +1,20 @@
1
- import type { ToolPlugin } from "../../tools/types";
1
+ import type { PluginRegistration, ToolPlugin } from "../../tools/types";
2
2
  import type { ToolResult } from "gui-chat-protocol";
3
- import toolDefinition, { TOOL_NAME } from "./definition";
4
- import type { SpreadsheetToolData } from "./definition";
3
+ import toolDefinition, { TOOL_NAME, type SpreadsheetEndpoints, type SpreadsheetToolData } from "./definition";
4
+ import { pluginEndpoints } from "../api";
5
+ import { wrapWithScope } from "../scope";
5
6
  import View from "./View.vue";
6
7
  import Preview from "./Preview.vue";
7
- import { apiPost } from "../../utils/api";
8
- import { API_ROUTES } from "../../config/apiRoutes";
8
+ import { apiCall } from "../../utils/api";
9
9
  import { makeUuid } from "../../utils/id";
10
10
 
11
11
  const spreadsheetPlugin: ToolPlugin<SpreadsheetToolData> = {
12
12
  toolDefinition,
13
13
 
14
14
  async execute(_context, args) {
15
- const result = await apiPost<ToolResult<SpreadsheetToolData>>(API_ROUTES.plugins.presentSpreadsheet, args);
15
+ const endpoints = pluginEndpoints<SpreadsheetEndpoints>("spreadsheet");
16
+ const { method, url } = endpoints.create;
17
+ const result = await apiCall<ToolResult<SpreadsheetToolData>>(url, { method, body: args });
16
18
  if (!result.ok) {
17
19
  return {
18
20
  toolName: TOOL_NAME,
@@ -29,9 +31,14 @@ const spreadsheetPlugin: ToolPlugin<SpreadsheetToolData> = {
29
31
 
30
32
  isEnabled: () => true,
31
33
  generatingMessage: "Creating spreadsheet...",
32
- viewComponent: View,
33
- previewComponent: Preview,
34
+ viewComponent: wrapWithScope("spreadsheet", View),
35
+ previewComponent: wrapWithScope("spreadsheet", Preview),
34
36
  };
35
37
 
36
38
  export default spreadsheetPlugin;
37
39
  export { TOOL_NAME };
40
+
41
+ export const REGISTRATION: PluginRegistration = {
42
+ toolName: TOOL_NAME,
43
+ entry: spreadsheetPlugin,
44
+ };
@@ -0,0 +1,38 @@
1
+ // Pure helpers behind the spreadsheet mini-editor's arrow-key
2
+ // navigation. Lifted out of View.vue so each rule can be unit-tested
3
+ // without spinning up Vue or a DOM.
4
+
5
+ export interface CellPosition {
6
+ row: number;
7
+ col: number;
8
+ }
9
+
10
+ // Sheet shape we actually rely on — the mini-editor only reads
11
+ // `data` as a 2D array, so the type is intentionally loose to match
12
+ // what arrives from `JSON.parse(editableData.value)`.
13
+ export interface SheetLike {
14
+ data?: unknown[][];
15
+ }
16
+
17
+ export function getArrowKeyOffset(key: string, row: number, col: number): CellPosition | null {
18
+ switch (key) {
19
+ case "ArrowUp":
20
+ return { row: Math.max(0, row - 1), col };
21
+ case "ArrowDown":
22
+ return { row: row + 1, col };
23
+ case "ArrowLeft":
24
+ return { row, col: Math.max(0, col - 1) };
25
+ case "ArrowRight":
26
+ return { row, col: col + 1 };
27
+ default:
28
+ return null;
29
+ }
30
+ }
31
+
32
+ export function isWithinSheetBounds(sheet: SheetLike | null | undefined, row: number, col: number): boolean {
33
+ if (!sheet?.data) return false;
34
+ if (row < 0 || row >= sheet.data.length) return false;
35
+ const rowData = sheet.data[row];
36
+ if (!rowData) return false;
37
+ return col >= 0 && col < rowData.length;
38
+ }
@@ -0,0 +1,14 @@
1
+ import { definePluginMeta } from "../meta-types";
2
+
3
+ export const META = definePluginMeta({
4
+ toolName: "presentSpreadsheet",
5
+ apiNamespace: "spreadsheet",
6
+ apiRoutes: {
7
+ /** POST /api/spreadsheet — create a new spreadsheet. */
8
+ create: { method: "POST", path: "" },
9
+ /** PUT /api/spreadsheet/update — overwrite an existing
10
+ * spreadsheet. Body carries the workspace-relative path. */
11
+ update: { method: "PUT", path: "/update" },
12
+ },
13
+ mcpDispatch: "create",
14
+ });
@@ -1,5 +1,10 @@
1
1
  <template>
2
- <div class="preview-text p-2 text-sm leading-snug" :class="textColorClass">{{ previewText }}</div>
2
+ <div class="p-2">
3
+ <div class="preview-text text-sm leading-snug" :class="textColorClass">{{ previewText }}</div>
4
+ <div v-if="attachments.length > 0" class="flex flex-wrap gap-1 mt-1.5" data-testid="text-response-preview-attachments">
5
+ <SentAttachmentChip v-for="path in attachments" :key="path" :path="path" variant="thumb" />
6
+ </div>
7
+ </div>
3
8
  </template>
4
9
 
5
10
  <script setup lang="ts">
@@ -7,6 +12,7 @@ import { computed } from "vue";
7
12
  import { marked } from "marked";
8
13
  import type { ToolResultComplete } from "gui-chat-protocol/vue";
9
14
  import type { TextResponseData } from "./types";
15
+ import SentAttachmentChip from "../../components/SentAttachmentChip.vue";
10
16
 
11
17
  const props = defineProps<{
12
18
  result: ToolResultComplete<TextResponseData>;
@@ -27,6 +33,8 @@ const textColorClass = computed(() => {
27
33
 
28
34
  const previewText = computed(() => markdownToPlainText(props.result.data?.text ?? ""));
29
35
 
36
+ const attachments = computed<string[]>(() => props.result.data?.attachments ?? []);
37
+
30
38
  function markdownToPlainText(markdown: string): string {
31
39
  const html = marked(markdown, { breaks: true, gfm: true }) as string;
32
40
  const spaced = html