@thevinci/web 1.0.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.
- package/README.md +197 -0
- package/bin/cli-entry.js +55 -0
- package/bin/cli-output.js +145 -0
- package/bin/cli.js +4887 -0
- package/bin/cli.test.js +64 -0
- package/dist/apple-touch-icon-120x120.png +0 -0
- package/dist/apple-touch-icon-152x152.png +0 -0
- package/dist/apple-touch-icon-167x167.png +0 -0
- package/dist/apple-touch-icon-180x180.png +0 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/apple-touch-icon.svg +528 -0
- package/dist/assets/JsonTreeView-CSm9OzXG.js +1 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/MarkdownRendererImpl-DensKOLc.js +6 -0
- package/dist/assets/MultiRunWindow-Bo7THayo.js +1 -0
- package/dist/assets/OnboardingScreen-BDqmzTVR.js +2 -0
- package/dist/assets/SettingsWindow-coz__Ykw.js +1 -0
- package/dist/assets/TerminalView-DrZ-i3Dr.js +1 -0
- package/dist/assets/ToolOutputDialog-Eglzslt3.js +16 -0
- package/dist/assets/es-4o9ciP61.js +15 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/assets/index-DLTDToSP.css +1 -0
- package/dist/assets/index-DgiFEKGN.js +1 -0
- package/dist/assets/ko-B20imCHE.js +15 -0
- package/dist/assets/main-BV3KOtdA.css +1 -0
- package/dist/assets/main-CDKJj0sH.js +226 -0
- package/dist/assets/main-LC-PSNVM.js +2 -0
- package/dist/assets/miniChat-CQUiG_cr.js +2 -0
- package/dist/assets/modelPrefsAutoSave-Dm799vzR.js +6986 -0
- package/dist/assets/pl-DQJ7LSzj.js +15 -0
- package/dist/assets/pt-BR-OmjHUz9y.js +15 -0
- package/dist/assets/renderElectronMiniChatApp-CARbeW0G.js +2 -0
- package/dist/assets/uk-BNFxOlO4.js +15 -0
- package/dist/assets/vendor--DBfsbEis.css +1 -0
- package/dist/assets/vendor-.bun-B9l0ZNi2.js +4094 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/wasmSttWorker-Dtlxac_K.js +1 -0
- package/dist/assets/wasmSttWorker-oo7Dm_jy.js +1806 -0
- package/dist/assets/worker-CbT6TVo7.js +155 -0
- package/dist/assets/zh-CN-C6T-Ac7F.js +15 -0
- package/dist/favicon-16.png +0 -0
- package/dist/favicon-32.png +0 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +528 -0
- package/dist/index.html +607 -0
- package/dist/logo-dark-192x192.png +0 -0
- package/dist/logo-dark-512x512.png +0 -0
- package/dist/logo-dark-512x512.svg +528 -0
- package/dist/logo-light-192x192.png +0 -0
- package/dist/logo-light-512x512.png +0 -0
- package/dist/logo-light-512x512.svg +528 -0
- package/dist/mini-chat.html +16 -0
- package/dist/pwa-192.png +0 -0
- package/dist/pwa-512.png +0 -0
- package/dist/pwa-maskable-192.png +0 -0
- package/dist/pwa-maskable-512.png +0 -0
- package/dist/site.webmanifest +21 -0
- package/dist/sw.js +1 -0
- package/package.json +118 -0
- package/public/apple-touch-icon-120x120.png +0 -0
- package/public/apple-touch-icon-152x152.png +0 -0
- package/public/apple-touch-icon-167x167.png +0 -0
- package/public/apple-touch-icon-180x180.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/apple-touch-icon.svg +528 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +528 -0
- package/public/logo-dark-192x192.png +0 -0
- package/public/logo-dark-512x512.png +0 -0
- package/public/logo-dark-512x512.svg +528 -0
- package/public/logo-light-192x192.png +0 -0
- package/public/logo-light-512x512.png +0 -0
- package/public/logo-light-512x512.svg +528 -0
- package/public/pwa-192.png +0 -0
- package/public/pwa-512.png +0 -0
- package/public/pwa-maskable-192.png +0 -0
- package/public/pwa-maskable-512.png +0 -0
- package/public/site.webmanifest +21 -0
- package/server/TERMINAL_WS_PROTOCOL.md +48 -0
- package/server/index.d.ts +39 -0
- package/server/index.js +1311 -0
- package/server/lib/cloudflare-tunnel.js +650 -0
- package/server/lib/event-stream/DOCUMENTATION.md +61 -0
- package/server/lib/event-stream/directory-ws-bridge.js +185 -0
- package/server/lib/event-stream/global-hub.js +158 -0
- package/server/lib/event-stream/global-hub.test.js +140 -0
- package/server/lib/event-stream/global-ws-bridge.js +206 -0
- package/server/lib/event-stream/index.js +25 -0
- package/server/lib/event-stream/protocol.js +131 -0
- package/server/lib/event-stream/protocol.test.js +182 -0
- package/server/lib/event-stream/runtime.js +180 -0
- package/server/lib/event-stream/runtime.test.js +512 -0
- package/server/lib/event-stream/upstream-reader.js +226 -0
- package/server/lib/event-stream/upstream-reader.test.js +276 -0
- package/server/lib/fs/DOCUMENTATION.md +36 -0
- package/server/lib/fs/routes.js +1040 -0
- package/server/lib/fs/search.js +238 -0
- package/server/lib/git/DOCUMENTATION.md +152 -0
- package/server/lib/git/credentials.js +74 -0
- package/server/lib/git/identity-storage.js +112 -0
- package/server/lib/git/index.js +6 -0
- package/server/lib/git/routes.js +972 -0
- package/server/lib/git/service.js +3432 -0
- package/server/lib/git/service.test.js +39 -0
- package/server/lib/github/DOCUMENTATION.md +171 -0
- package/server/lib/github/auth.js +307 -0
- package/server/lib/github/device-flow.js +50 -0
- package/server/lib/github/index.js +24 -0
- package/server/lib/github/octokit.js +10 -0
- package/server/lib/github/pr-status.js +519 -0
- package/server/lib/github/repo/fork-detection.js +102 -0
- package/server/lib/github/repo/index.js +55 -0
- package/server/lib/github/routes.js +1560 -0
- package/server/lib/magic-prompts/routes.js +63 -0
- package/server/lib/magic-prompts/runtime.js +119 -0
- package/server/lib/notifications/DOCUMENTATION.md +122 -0
- package/server/lib/notifications/emitter-runtime.js +102 -0
- package/server/lib/notifications/index.js +4 -0
- package/server/lib/notifications/message.js +52 -0
- package/server/lib/notifications/message.test.js +34 -0
- package/server/lib/notifications/push-runtime.js +304 -0
- package/server/lib/notifications/routes.js +315 -0
- package/server/lib/notifications/runtime.js +566 -0
- package/server/lib/notifications/template-runtime.js +349 -0
- package/server/lib/notifications/template-runtime.test.js +26 -0
- package/server/lib/opencode/DOCUMENTATION.md +362 -0
- package/server/lib/opencode/agents.js +634 -0
- package/server/lib/opencode/auth-state-runtime.js +88 -0
- package/server/lib/opencode/auth.js +83 -0
- package/server/lib/opencode/bootstrap-runtime.js +131 -0
- package/server/lib/opencode/cli-entry-runtime.js +43 -0
- package/server/lib/opencode/cli-options.js +128 -0
- package/server/lib/opencode/commands.js +339 -0
- package/server/lib/opencode/config-entity-routes.js +370 -0
- package/server/lib/opencode/core-routes.js +500 -0
- package/server/lib/opencode/core-routes.test.js +26 -0
- package/server/lib/opencode/env-config.js +74 -0
- package/server/lib/opencode/env-keys.js +68 -0
- package/server/lib/opencode/env-runtime.js +1162 -0
- package/server/lib/opencode/env-runtime.test.js +116 -0
- package/server/lib/opencode/feature-routes-runtime.js +244 -0
- package/server/lib/opencode/hmr-state-runtime.js +85 -0
- package/server/lib/opencode/index.js +66 -0
- package/server/lib/opencode/lifecycle.js +1019 -0
- package/server/lib/opencode/lifecycle.test.js +240 -0
- package/server/lib/opencode/mcp.js +278 -0
- package/server/lib/opencode/network-runtime.js +104 -0
- package/server/lib/opencode/network-runtime.test.js +37 -0
- package/server/lib/opencode/opencode-resolution-runtime.js +71 -0
- package/server/lib/opencode/path-utils.js +100 -0
- package/server/lib/opencode/path-utils.test.js +71 -0
- package/server/lib/opencode/project-directory-runtime.js +124 -0
- package/server/lib/opencode/project-icon-routes.js +399 -0
- package/server/lib/opencode/project-icon-routes.test.js +107 -0
- package/server/lib/opencode/providers.js +96 -0
- package/server/lib/opencode/proxy.js +445 -0
- package/server/lib/opencode/pwa-manifest-routes.js +257 -0
- package/server/lib/opencode/pwa-manifest-routes.test.js +133 -0
- package/server/lib/opencode/routes.js +541 -0
- package/server/lib/opencode/server-startup-runtime.js +156 -0
- package/server/lib/opencode/server-utils-runtime.js +168 -0
- package/server/lib/opencode/server-utils-runtime.test.js +135 -0
- package/server/lib/opencode/session-runtime.js +356 -0
- package/server/lib/opencode/session-runtime.test.js +151 -0
- package/server/lib/opencode/settings-helpers.js +770 -0
- package/server/lib/opencode/settings-helpers.test.js +109 -0
- package/server/lib/opencode/settings-normalization-runtime.js +428 -0
- package/server/lib/opencode/settings-runtime.js +826 -0
- package/server/lib/opencode/settings-runtime.test.js +85 -0
- package/server/lib/opencode/shared.js +615 -0
- package/server/lib/opencode/shutdown-runtime.js +139 -0
- package/server/lib/opencode/shutdown-runtime.test.js +58 -0
- package/server/lib/opencode/skill-routes.js +701 -0
- package/server/lib/opencode/skills.js +548 -0
- package/server/lib/opencode/startup-pipeline-runtime.js +130 -0
- package/server/lib/opencode/static-routes-runtime.js +65 -0
- package/server/lib/opencode/theme-runtime.js +167 -0
- package/server/lib/opencode/tunnel-auth.js +591 -0
- package/server/lib/opencode/tunnel-wiring-runtime.js +94 -0
- package/server/lib/opencode/vinci-routes.js +76 -0
- package/server/lib/opencode/watcher.js +115 -0
- package/server/lib/opencode/watcher.test.js +239 -0
- package/server/lib/preview/proxy-runtime.js +1333 -0
- package/server/lib/preview/proxy-runtime.test.js +144 -0
- package/server/lib/projects/project-config.js +567 -0
- package/server/lib/projects/project-config.test.js +175 -0
- package/server/lib/projects/project-id.js +13 -0
- package/server/lib/quota/DOCUMENTATION.md +58 -0
- package/server/lib/quota/index.js +25 -0
- package/server/lib/quota/providers/claude.js +107 -0
- package/server/lib/quota/providers/codex.js +113 -0
- package/server/lib/quota/providers/copilot.js +165 -0
- package/server/lib/quota/providers/google/api.js +92 -0
- package/server/lib/quota/providers/google/auth.js +108 -0
- package/server/lib/quota/providers/google/index.js +124 -0
- package/server/lib/quota/providers/google/transforms.js +109 -0
- package/server/lib/quota/providers/index.js +168 -0
- package/server/lib/quota/providers/interface.js +55 -0
- package/server/lib/quota/providers/kimi.js +108 -0
- package/server/lib/quota/providers/minimax-cn-coding-plan.js +140 -0
- package/server/lib/quota/providers/minimax-coding-plan.js +139 -0
- package/server/lib/quota/providers/nanogpt.js +124 -0
- package/server/lib/quota/providers/ollama-cloud.js +112 -0
- package/server/lib/quota/providers/openai.js +91 -0
- package/server/lib/quota/providers/openrouter.js +92 -0
- package/server/lib/quota/providers/zai.js +91 -0
- package/server/lib/quota/providers/zhipuai-coding-plan.js +133 -0
- package/server/lib/quota/providers/zhipuai.js +114 -0
- package/server/lib/quota/routes.js +27 -0
- package/server/lib/quota/utils/auth.js +50 -0
- package/server/lib/quota/utils/formatters.js +85 -0
- package/server/lib/quota/utils/formatters.test.js +54 -0
- package/server/lib/quota/utils/index.js +10 -0
- package/server/lib/quota/utils/transformers.js +55 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +44 -0
- package/server/lib/scheduled-tasks/routes.js +235 -0
- package/server/lib/scheduled-tasks/runtime.js +773 -0
- package/server/lib/scheduled-tasks/runtime.test.js +100 -0
- package/server/lib/security/request-security.js +115 -0
- package/server/lib/session-folders/routes.js +63 -0
- package/server/lib/session-folders/routes.test.js +102 -0
- package/server/lib/skills-catalog/DOCUMENTATION.md +178 -0
- package/server/lib/skills-catalog/cache.js +29 -0
- package/server/lib/skills-catalog/clawdhub/api.js +158 -0
- package/server/lib/skills-catalog/clawdhub/index.js +30 -0
- package/server/lib/skills-catalog/clawdhub/install.js +238 -0
- package/server/lib/skills-catalog/clawdhub/scan.js +113 -0
- package/server/lib/skills-catalog/curated-sources.js +21 -0
- package/server/lib/skills-catalog/git.js +77 -0
- package/server/lib/skills-catalog/index.js +42 -0
- package/server/lib/skills-catalog/install.js +294 -0
- package/server/lib/skills-catalog/scan.js +221 -0
- package/server/lib/skills-catalog/source.js +87 -0
- package/server/lib/terminal/DOCUMENTATION.md +76 -0
- package/server/lib/terminal/index.js +31 -0
- package/server/lib/terminal/output-replay-buffer.js +78 -0
- package/server/lib/terminal/output-replay-buffer.test.js +75 -0
- package/server/lib/terminal/runtime.js +850 -0
- package/server/lib/terminal/runtime.test.js +96 -0
- package/server/lib/terminal/terminal-ws-protocol.js +68 -0
- package/server/lib/terminal/terminal-ws-protocol.test.js +145 -0
- package/server/lib/text/DOCUMENTATION.md +35 -0
- package/server/lib/text/summarization.js +138 -0
- package/server/lib/text/summarization.test.js +34 -0
- package/server/lib/tts/DOCUMENTATION.md +146 -0
- package/server/lib/tts/base-url.js +62 -0
- package/server/lib/tts/capability-runtime.js +31 -0
- package/server/lib/tts/index.js +19 -0
- package/server/lib/tts/routes.js +261 -0
- package/server/lib/tts/routes.test.js +53 -0
- package/server/lib/tts/service.js +178 -0
- package/server/lib/tts/stt.js +75 -0
- package/server/lib/tunnels/DOCUMENTATION.md +18 -0
- package/server/lib/tunnels/index.js +166 -0
- package/server/lib/tunnels/managed-config.js +201 -0
- package/server/lib/tunnels/providers/cloudflare.js +260 -0
- package/server/lib/tunnels/registry.js +51 -0
- package/server/lib/tunnels/routes.js +605 -0
- package/server/lib/tunnels/types.js +219 -0
- package/server/lib/ui-auth/DOCUMENTATION.md +38 -0
- package/server/lib/ui-auth/ui-auth.js +673 -0
- package/server/lib/ui-auth/ui-passkeys.js +545 -0
- package/server/opencode-proxy.test.js +151 -0
- package/server/proxy-headers.js +61 -0
- package/server/proxy-headers.test.js +58 -0
- package/server/sse-routes.test.js +152 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createMagicPromptRuntime } from './runtime.js';
|
|
2
|
+
|
|
3
|
+
export const registerMagicPromptRoutes = (app, dependencies) => {
|
|
4
|
+
const {
|
|
5
|
+
fsPromises,
|
|
6
|
+
path,
|
|
7
|
+
vinciDataDir,
|
|
8
|
+
} = dependencies;
|
|
9
|
+
|
|
10
|
+
const runtime = createMagicPromptRuntime({
|
|
11
|
+
fsPromises,
|
|
12
|
+
path,
|
|
13
|
+
filePath: path.join(vinciDataDir, 'magic-prompts.json'),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
app.get('/api/magic-prompts', async (_req, res) => {
|
|
17
|
+
try {
|
|
18
|
+
const state = await runtime.readPromptState();
|
|
19
|
+
res.json(state);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
res.status(500).json({ error: error instanceof Error ? error.message : 'Failed to read magic prompts' });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
app.put('/api/magic-prompts/:id', async (req, res) => {
|
|
26
|
+
const id = typeof req.params?.id === 'string' ? req.params.id : '';
|
|
27
|
+
const text = typeof req.body?.text === 'string' ? req.body.text : null;
|
|
28
|
+
if (text === null) {
|
|
29
|
+
return res.status(400).json({ error: 'text is required' });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const state = await runtime.setOverride(id, text);
|
|
34
|
+
return res.json(state);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
37
|
+
const status = message.includes('Invalid prompt id') || message.includes('too long') || message.includes('cannot be empty') ? 400 : 500;
|
|
38
|
+
return res.status(status).json({ error: message });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
app.delete('/api/magic-prompts/:id', async (req, res) => {
|
|
43
|
+
const id = typeof req.params?.id === 'string' ? req.params.id : '';
|
|
44
|
+
try {
|
|
45
|
+
const state = await runtime.resetOverride(id);
|
|
46
|
+
return res.json(state);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
const status = message.includes('Invalid prompt id') ? 400 : 500;
|
|
50
|
+
return res.status(status).json({ error: message });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
app.delete('/api/magic-prompts', async (_req, res) => {
|
|
55
|
+
try {
|
|
56
|
+
const state = await runtime.resetAllOverrides();
|
|
57
|
+
return res.json(state);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
60
|
+
return res.status(500).json({ error: message || 'Failed to reset magic prompts' });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const FILE_VERSION = 1;
|
|
2
|
+
const MAX_PROMPT_TEXT_LENGTH = 200_000;
|
|
3
|
+
const PROMPT_ID_PATTERN = /^[a-z0-9._-]{1,160}$/;
|
|
4
|
+
const isVisiblePromptID = (id) => typeof id === 'string' && id.endsWith('.visible');
|
|
5
|
+
|
|
6
|
+
const hasOwn = (input, key) => Object.prototype.hasOwnProperty.call(input, key);
|
|
7
|
+
|
|
8
|
+
const sanitizeOverrides = (value) => {
|
|
9
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const next = {};
|
|
14
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
15
|
+
if (!PROMPT_ID_PATTERN.test(key) || typeof entry !== 'string') {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
next[key] = entry;
|
|
19
|
+
}
|
|
20
|
+
return next;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const createMagicPromptRuntime = (dependencies) => {
|
|
24
|
+
const {
|
|
25
|
+
fsPromises,
|
|
26
|
+
path,
|
|
27
|
+
filePath,
|
|
28
|
+
} = dependencies;
|
|
29
|
+
|
|
30
|
+
let writeLock = Promise.resolve();
|
|
31
|
+
|
|
32
|
+
const readPromptState = async () => {
|
|
33
|
+
try {
|
|
34
|
+
const raw = await fsPromises.readFile(filePath, 'utf8');
|
|
35
|
+
const parsed = JSON.parse(raw);
|
|
36
|
+
const overrides = sanitizeOverrides(parsed?.overrides);
|
|
37
|
+
return {
|
|
38
|
+
version: FILE_VERSION,
|
|
39
|
+
overrides,
|
|
40
|
+
};
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error && typeof error === 'object' && error.code === 'ENOENT') {
|
|
43
|
+
return { version: FILE_VERSION, overrides: {} };
|
|
44
|
+
}
|
|
45
|
+
console.warn('Failed to read magic prompts file:', error);
|
|
46
|
+
return { version: FILE_VERSION, overrides: {} };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const writePromptState = async (state) => {
|
|
51
|
+
await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
|
|
52
|
+
await fsPromises.writeFile(filePath, JSON.stringify(state, null, 2), 'utf8');
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const persist = async (mutator) => {
|
|
56
|
+
const run = async () => {
|
|
57
|
+
const current = await readPromptState();
|
|
58
|
+
const next = await mutator(current);
|
|
59
|
+
await writePromptState(next);
|
|
60
|
+
return next;
|
|
61
|
+
};
|
|
62
|
+
writeLock = writeLock.then(run, run);
|
|
63
|
+
return writeLock;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const setOverride = async (id, text) => {
|
|
67
|
+
const normalizedId = typeof id === 'string' ? id.trim() : '';
|
|
68
|
+
if (!PROMPT_ID_PATTERN.test(normalizedId)) {
|
|
69
|
+
throw new Error('Invalid prompt id');
|
|
70
|
+
}
|
|
71
|
+
if (typeof text !== 'string') {
|
|
72
|
+
throw new Error('Prompt text must be a string');
|
|
73
|
+
}
|
|
74
|
+
if (isVisiblePromptID(normalizedId) && text.trim().length === 0) {
|
|
75
|
+
throw new Error('Visible prompt text cannot be empty');
|
|
76
|
+
}
|
|
77
|
+
if (text.length > MAX_PROMPT_TEXT_LENGTH) {
|
|
78
|
+
throw new Error('Prompt text is too long');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return persist(async (state) => {
|
|
82
|
+
const nextOverrides = { ...state.overrides, [normalizedId]: text };
|
|
83
|
+
return {
|
|
84
|
+
version: FILE_VERSION,
|
|
85
|
+
overrides: nextOverrides,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const resetOverride = async (id) => {
|
|
91
|
+
const normalizedId = typeof id === 'string' ? id.trim() : '';
|
|
92
|
+
if (!PROMPT_ID_PATTERN.test(normalizedId)) {
|
|
93
|
+
throw new Error('Invalid prompt id');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return persist(async (state) => {
|
|
97
|
+
if (!hasOwn(state.overrides, normalizedId)) {
|
|
98
|
+
return state;
|
|
99
|
+
}
|
|
100
|
+
const nextOverrides = { ...state.overrides };
|
|
101
|
+
delete nextOverrides[normalizedId];
|
|
102
|
+
return {
|
|
103
|
+
version: FILE_VERSION,
|
|
104
|
+
overrides: nextOverrides,
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const resetAllOverrides = async () => {
|
|
110
|
+
return persist(async () => ({ version: FILE_VERSION, overrides: {} }));
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
readPromptState,
|
|
115
|
+
setOverride,
|
|
116
|
+
resetOverride,
|
|
117
|
+
resetAllOverrides,
|
|
118
|
+
};
|
|
119
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Notifications Module Documentation
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
This module provides notification message preparation utilities for the web server runtime, including text truncation and plain-text normalization for system notifications.
|
|
5
|
+
|
|
6
|
+
## Entrypoints and structure
|
|
7
|
+
- `packages/web/server/lib/notifications/index.js`: public entrypoint imported by `packages/web/server/index.js`.
|
|
8
|
+
- `packages/web/server/lib/notifications/routes.js`: route registration for push, visibility, and session status/attention endpoints.
|
|
9
|
+
- `packages/web/server/lib/notifications/push-runtime.js`: push subscription persistence, VAPID initialization, and UI visibility runtime.
|
|
10
|
+
- `packages/web/server/lib/notifications/emitter-runtime.js`: desktop/stdout + UI SSE notification emission runtime.
|
|
11
|
+
- `packages/web/server/lib/notifications/runtime.js`: trigger runtime for OpenCode event-driven notification fanout.
|
|
12
|
+
- `packages/web/server/lib/notifications/template-runtime.js`: notification template variables and session text/title enrichment runtime. Zen-model helpers are retained as compatibility stubs only.
|
|
13
|
+
- `packages/web/server/lib/notifications/message.js`: helper implementation module.
|
|
14
|
+
- `packages/web/server/lib/notifications/message.test.js`: unit tests for notification message helpers.
|
|
15
|
+
|
|
16
|
+
## Public exports
|
|
17
|
+
|
|
18
|
+
### Notifications API (re-exported from message.js)
|
|
19
|
+
- `truncateNotificationText(text, maxLength)`: Truncates text to specified max length, appending `...` if truncated.
|
|
20
|
+
- `prepareNotificationLastMessage({ message, settings })`: Prepares the last message for notification display by normalizing and truncating text.
|
|
21
|
+
|
|
22
|
+
### Route registration API (routes.js)
|
|
23
|
+
- `registerNotificationRoutes(app, dependencies)`: Registers notification-owned endpoints:
|
|
24
|
+
- `GET /api/push/vapid-public-key`
|
|
25
|
+
- `POST /api/push/subscribe`
|
|
26
|
+
- `DELETE /api/push/subscribe`
|
|
27
|
+
- `POST /api/push/visibility`
|
|
28
|
+
- `GET /api/push/visibility`
|
|
29
|
+
- `GET /api/session-activity`
|
|
30
|
+
- `GET /api/sessions/snapshot`
|
|
31
|
+
- `GET /api/sessions/status`
|
|
32
|
+
- `GET /api/sessions/:id/status`
|
|
33
|
+
- `GET /api/sessions/attention`
|
|
34
|
+
- `GET /api/sessions/:id/attention`
|
|
35
|
+
- `POST /api/sessions/:id/view`
|
|
36
|
+
- `POST /api/sessions/:id/unview`
|
|
37
|
+
- `POST /api/sessions/:id/message-sent`
|
|
38
|
+
|
|
39
|
+
### Trigger runtime API (runtime.js)
|
|
40
|
+
- `createNotificationTriggerRuntime(dependencies)`: creates runtime-owned debounced trigger handling for OpenCode events.
|
|
41
|
+
- Returned API:
|
|
42
|
+
- `maybeSendPushForTrigger(payload)`
|
|
43
|
+
- Owns:
|
|
44
|
+
- completion/error/question/permission trigger routing
|
|
45
|
+
- session parent cache for subtask suppression
|
|
46
|
+
- template resolution and fallback behavior
|
|
47
|
+
- native notification fanout and web push payload fanout
|
|
48
|
+
|
|
49
|
+
### Push runtime API (push-runtime.js)
|
|
50
|
+
- `createPushRuntime(dependencies)`: creates runtime for web push and UI visibility state.
|
|
51
|
+
- Returned API:
|
|
52
|
+
- `getOrCreateVapidKeys()`
|
|
53
|
+
- `ensurePushInitialized()`
|
|
54
|
+
- `setPushInitialized(value)`
|
|
55
|
+
- `addOrUpdatePushSubscription(uiSessionToken, subscription, userAgent)`
|
|
56
|
+
- `removePushSubscription(uiSessionToken, endpoint)`
|
|
57
|
+
- `sendPushToAllUiSessions(payload, options?)`
|
|
58
|
+
- `updateUiVisibility(token, visible)`
|
|
59
|
+
- `isAnyUiVisible()`
|
|
60
|
+
- `isUiVisible(token)`
|
|
61
|
+
|
|
62
|
+
### Emitter runtime API (emitter-runtime.js)
|
|
63
|
+
- `createNotificationEmitterRuntime(dependencies)`: creates runtime for unified notification emission channels.
|
|
64
|
+
- Returned API:
|
|
65
|
+
- `writeSseEvent(res, payload)`
|
|
66
|
+
- `emitDesktopNotification(payload)`
|
|
67
|
+
- `broadcastUiNotification(payload)`
|
|
68
|
+
|
|
69
|
+
### Template runtime API (template-runtime.js)
|
|
70
|
+
- `createNotificationTemplateRuntime(dependencies)`: creates shared notification/template runtime. Model-backed summarization was retired after the Zen provider became unavailable.
|
|
71
|
+
- Returned API:
|
|
72
|
+
- `resolveNotificationTemplate(template, variables)`
|
|
73
|
+
- `shouldApplyResolvedTemplateMessage(template, resolved, variables)`
|
|
74
|
+
- `fetchFreeZenModels()` compatibility stub returning `[]`
|
|
75
|
+
- `resolveZenModel(override)` compatibility stub preserving stored values without validation
|
|
76
|
+
- `validateZenModelAtStartup()` compatibility no-op
|
|
77
|
+
- `summarizeText(text, targetLength, zenModel)` compatibility stub returning local fallback text
|
|
78
|
+
- `extractLastMessageText(payload, maxLength?)`
|
|
79
|
+
- `fetchLastAssistantMessageText(sessionId, messageId, maxLength?)`
|
|
80
|
+
- `maybeCacheSessionInfoFromEvent(payload)`
|
|
81
|
+
- `buildTemplateVariables(payload, sessionId)`
|
|
82
|
+
- `getCachedZenModels()`
|
|
83
|
+
|
|
84
|
+
## Constants
|
|
85
|
+
|
|
86
|
+
### Default values
|
|
87
|
+
- `DEFAULT_NOTIFICATION_MESSAGE_MAX_LENGTH`: 250 (default max length for notification text).
|
|
88
|
+
|
|
89
|
+
## Settings object format
|
|
90
|
+
|
|
91
|
+
The `settings` parameter for `prepareNotificationLastMessage` supports `maxLastMessageLength` (number), the maximum length for the final notification text (default: 250). Legacy summarization settings may still exist in persisted settings but are ignored.
|
|
92
|
+
|
|
93
|
+
## Response contracts
|
|
94
|
+
|
|
95
|
+
### `truncateNotificationText`
|
|
96
|
+
- Returns empty string for non-string input.
|
|
97
|
+
- Returns original text if under max length.
|
|
98
|
+
- Returns `${text.slice(0, maxLength)}...` for truncated text.
|
|
99
|
+
|
|
100
|
+
### `prepareNotificationLastMessage`
|
|
101
|
+
- Returns empty string for empty/null message.
|
|
102
|
+
- Returns truncated original message. Model-backed notification summarization is retired.
|
|
103
|
+
- Normalizes markdown-like formatting to plain text before truncation.
|
|
104
|
+
- Always applies `maxLastMessageLength` truncation to final result.
|
|
105
|
+
|
|
106
|
+
## Notes for contributors
|
|
107
|
+
|
|
108
|
+
### Adding new notification helpers
|
|
109
|
+
1. Add new helper functions to `packages/web/server/lib/notifications/message.js`.
|
|
110
|
+
2. Export functions that are intended for public use.
|
|
111
|
+
3. Follow existing patterns for input validation (e.g., type checking for strings).
|
|
112
|
+
4. Use `resolvePositiveNumber` for numeric parameters with fallbacks to maintain safe defaults.
|
|
113
|
+
5. Add corresponding unit tests in `packages/web/server/lib/notifications/message.test.js`.
|
|
114
|
+
|
|
115
|
+
### Error handling
|
|
116
|
+
- `prepareNotificationLastMessage` does not call model summarization.
|
|
117
|
+
- Invalid numeric parameters default to safe fallback values.
|
|
118
|
+
- Non-string inputs are handled gracefully (return empty string).
|
|
119
|
+
|
|
120
|
+
### Testing
|
|
121
|
+
- Run `bun run type-check`, `bun run lint`, and `bun run build` before finalizing changes.
|
|
122
|
+
- Unit tests should cover truncation behavior and edge cases (empty strings, invalid inputs).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export const createNotificationEmitterRuntime = (dependencies) => {
|
|
2
|
+
const {
|
|
3
|
+
process,
|
|
4
|
+
getDesktopNotifyEnabled,
|
|
5
|
+
desktopNotifyPrefix,
|
|
6
|
+
getUiNotificationClients,
|
|
7
|
+
getBroadcastGlobalUiEvent,
|
|
8
|
+
// Optional: in-process desktop shells (Electron main) inject a callback so
|
|
9
|
+
// notifications are delivered as a direct function call instead of a stdout
|
|
10
|
+
// stringly-typed IPC.
|
|
11
|
+
onDesktopNotification: initialOnDesktopNotification,
|
|
12
|
+
} = dependencies;
|
|
13
|
+
|
|
14
|
+
// Late-bindable: main() in server/index.js may call setOnDesktopNotification
|
|
15
|
+
// after runtime construction so the in-process shell can subscribe without
|
|
16
|
+
// restructuring the module-level wiring.
|
|
17
|
+
let onDesktopNotification = typeof initialOnDesktopNotification === 'function'
|
|
18
|
+
? initialOnDesktopNotification
|
|
19
|
+
: null;
|
|
20
|
+
|
|
21
|
+
const setOnDesktopNotification = (cb) => {
|
|
22
|
+
onDesktopNotification = typeof cb === 'function' ? cb : null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const writeSseEvent = (res, payload) => {
|
|
26
|
+
res.write(`data: ${JSON.stringify(payload)}\n\n`);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const emitDesktopNotification = (payload) => {
|
|
30
|
+
const desktopNotifyEnabled = getDesktopNotifyEnabled();
|
|
31
|
+
if (!desktopNotifyEnabled) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!payload || typeof payload !== 'object') {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (onDesktopNotification) {
|
|
40
|
+
try {
|
|
41
|
+
onDesktopNotification(payload);
|
|
42
|
+
} catch {
|
|
43
|
+
// ignore host-side throw
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// stdout IPC: Tauri shell spawns this process as a sidecar and parses
|
|
50
|
+
// its stdout for the one-line `${prefix}{json}` protocol.
|
|
51
|
+
process.stdout.write(`${desktopNotifyPrefix}${JSON.stringify(payload)}\n`);
|
|
52
|
+
} catch {
|
|
53
|
+
// ignore
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const broadcastUiNotification = (payload) => {
|
|
58
|
+
const desktopNotifyEnabled = getDesktopNotifyEnabled();
|
|
59
|
+
if (!payload || typeof payload !== 'object') {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const syntheticPayload = {
|
|
64
|
+
type: 'vinci:notification',
|
|
65
|
+
properties: {
|
|
66
|
+
...payload,
|
|
67
|
+
// Tell the UI whether the sidecar stdout notification channel is active.
|
|
68
|
+
// When true, the desktop UI should skip this SSE notification to avoid duplicates.
|
|
69
|
+
// When false (e.g. tauri dev), the UI must handle this SSE notification itself.
|
|
70
|
+
desktopStdoutActive: desktopNotifyEnabled,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const broadcastGlobalUiEvent = typeof getBroadcastGlobalUiEvent === 'function'
|
|
75
|
+
? getBroadcastGlobalUiEvent()
|
|
76
|
+
: null;
|
|
77
|
+
if (broadcastGlobalUiEvent) {
|
|
78
|
+
broadcastGlobalUiEvent(syntheticPayload);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const clients = getUiNotificationClients();
|
|
83
|
+
if (clients.size === 0) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const res of clients) {
|
|
88
|
+
try {
|
|
89
|
+
writeSseEvent(res, syntheticPayload);
|
|
90
|
+
} catch {
|
|
91
|
+
// ignore
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
writeSseEvent,
|
|
98
|
+
emitDesktopNotification,
|
|
99
|
+
broadcastUiNotification,
|
|
100
|
+
setOnDesktopNotification,
|
|
101
|
+
};
|
|
102
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { truncateNotificationText, prepareNotificationLastMessage } from './message.js';
|
|
2
|
+
export { createNotificationTriggerRuntime } from './runtime.js';
|
|
3
|
+
export { createPushRuntime } from './push-runtime.js';
|
|
4
|
+
export { createNotificationTemplateRuntime } from './template-runtime.js';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const DEFAULT_NOTIFICATION_MESSAGE_MAX_LENGTH = 250;
|
|
2
|
+
|
|
3
|
+
const resolvePositiveNumber = (value, fallback) => {
|
|
4
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
5
|
+
return fallback;
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const normalizeNotificationPlainText = (text) => {
|
|
11
|
+
if (typeof text !== 'string') {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return text
|
|
16
|
+
.replace(/```[\s\S]*?```/g, ' ')
|
|
17
|
+
.replace(/`([^`]*)`/g, '$1')
|
|
18
|
+
.replace(/^[\t ]*[-*+]\s+/gm, '')
|
|
19
|
+
.replace(/^#{1,6}\s+/gm, '')
|
|
20
|
+
.replace(/\*\*(.*?)\*\*/g, '$1')
|
|
21
|
+
.replace(/__(.*?)__/g, '$1')
|
|
22
|
+
.replace(/\*(.*?)\*/g, '$1')
|
|
23
|
+
.replace(/_(.*?)_/g, '$1')
|
|
24
|
+
.replace(/\[(.*?)\]\((.*?)\)/g, '$1')
|
|
25
|
+
.replace(/\s*\n\s*/g, ' ')
|
|
26
|
+
.replace(/\s+/g, ' ')
|
|
27
|
+
.trim();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const truncateNotificationText = (text, maxLength = DEFAULT_NOTIFICATION_MESSAGE_MAX_LENGTH) => {
|
|
31
|
+
if (typeof text !== 'string') {
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const safeMaxLength = resolvePositiveNumber(maxLength, DEFAULT_NOTIFICATION_MESSAGE_MAX_LENGTH);
|
|
36
|
+
if (text.length <= safeMaxLength) {
|
|
37
|
+
return text;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return `${text.slice(0, safeMaxLength)}...`;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const prepareNotificationLastMessage = async ({ message, settings }) => {
|
|
44
|
+
const originalMessage = typeof message === 'string' ? message : '';
|
|
45
|
+
if (!originalMessage) {
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const maxLastMessageLength = resolvePositiveNumber(settings?.maxLastMessageLength, DEFAULT_NOTIFICATION_MESSAGE_MAX_LENGTH);
|
|
50
|
+
const plainTextMessage = normalizeNotificationPlainText(originalMessage);
|
|
51
|
+
return truncateNotificationText(plainTextMessage, maxLastMessageLength);
|
|
52
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { prepareNotificationLastMessage, truncateNotificationText } from './message.js';
|
|
4
|
+
|
|
5
|
+
describe('notification message helpers', () => {
|
|
6
|
+
it('truncates oversized notification text', () => {
|
|
7
|
+
expect(truncateNotificationText('abcdef', 3)).toBe('abc...');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('ignores retired summarization settings and truncates original message', async () => {
|
|
11
|
+
const result = await prepareNotificationLastMessage({
|
|
12
|
+
message: '0123456789',
|
|
13
|
+
settings: {
|
|
14
|
+
summarizeLastMessage: true,
|
|
15
|
+
summaryThreshold: 5,
|
|
16
|
+
summaryLength: 3,
|
|
17
|
+
maxLastMessageLength: 4,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
expect(result).toBe('0123...');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('normalizes markdown message to plain text', async () => {
|
|
25
|
+
const result = await prepareNotificationLastMessage({
|
|
26
|
+
message: "**Committed.**\n\n- Commit: `85924b9d`\n- Message: `fix desktop notifications`",
|
|
27
|
+
settings: {
|
|
28
|
+
maxLastMessageLength: 200,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(result).toBe('Committed. Commit: 85924b9d Message: fix desktop notifications');
|
|
33
|
+
});
|
|
34
|
+
});
|