ompchamber 0.3.0 → 0.4.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 +9 -6
- package/dist/client/index.html +1 -1
- package/dist/client/static/css/index.5291f58223.css +2 -0
- package/dist/client/static/js/async/{1k.544adbf77d.js → 1k.5974df3b87.js} +2 -2
- package/dist/client/static/js/async/3j.357c80ff38.js +2 -0
- package/dist/client/static/js/async/{3y.22075aa079.js → 3y.ededeebde8.js} +5 -5
- package/dist/client/static/js/async/51.95bcae98af.js +2 -0
- package/dist/client/static/js/async/6.f034c9b528.js +1 -0
- package/dist/client/static/js/async/a.e05da304c8.js +10 -0
- package/dist/client/static/js/async/ar.1e14da5588.js +1 -0
- package/dist/client/static/js/async/art.62139ed479.js +2 -0
- package/dist/client/static/js/async/ct.b74928f7a3.js +1 -0
- package/dist/client/static/js/async/fw.6a51998a8e.js +6 -0
- package/dist/client/static/js/async/g.7a4bbd4798.js +2 -0
- package/dist/client/static/js/async/g.7a4bbd4798.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/{k.e878dd350f.js → k.7466330d1a.js} +2 -2
- package/dist/client/static/js/async/katex-vendor.de6a87c308.js +1 -0
- package/dist/client/static/js/async/kc.bbdb35b771.js +1 -0
- package/dist/client/static/js/async/l6.c56640c205.js +1 -0
- package/dist/client/static/js/async/{m.3fc017a055.js → m.4b29d34f93.js} +3 -3
- package/dist/client/static/js/async/{og.ae7acf03ac.js → og.634fba99ac.js} +2 -2
- package/dist/client/static/js/async/q.7fb4d2f374.js +21 -0
- package/dist/client/static/js/async/rd.cb6bf495bb.js +2 -0
- package/dist/client/static/js/async/rf.d626afc367.js +2 -0
- package/dist/client/static/js/async/s.413c2a4683.js +2 -0
- package/dist/client/static/js/async/s.413c2a4683.js.LICENSE.txt +6 -0
- package/dist/client/static/js/async/sy.d16be843a7.js +94 -0
- package/dist/client/static/js/async/t.cb3fcf8615.js +1 -0
- package/dist/client/static/js/async/ud.3065bfee31.js +2 -0
- package/dist/client/static/js/async/uf.46ad6209fa.js +2 -0
- package/dist/client/static/js/async/uf.46ad6209fa.js.LICENSE.txt +6 -0
- package/dist/client/static/js/async/v.7d28d3c241.js +34 -0
- package/dist/client/static/js/async/v0.394ed37c6b.js +2 -0
- package/dist/client/static/js/async/v0.394ed37c6b.js.LICENSE.txt +6 -0
- package/dist/client/static/js/async/vt.42416b707d.js +1 -0
- package/dist/client/static/js/async/vy.bbe114c4e0.js +2 -0
- package/dist/client/static/js/async/vy.bbe114c4e0.js.LICENSE.txt +6 -0
- package/dist/client/static/js/async/xw.ae67ba06fd.js +31 -0
- package/dist/client/static/js/async/xw.ae67ba06fd.js.LICENSE.txt +4 -0
- package/dist/client/static/js/async/yg.35bebd6d0c.js +149 -0
- package/dist/client/static/js/async/yv.2fa22f009a.js +1 -0
- package/dist/client/static/js/index.bf1b1fe988.js +33 -0
- package/dist/client/static/js/index.bf1b1fe988.js.LICENSE.txt +4 -0
- package/dist/client/static/js/{markdown-vendor.9da142c67b.js → markdown-vendor.71e43278b3.js} +1 -1
- package/dist/client/static/js/uy.5f791144ce.js +139 -0
- package/package.json +2 -1
- package/src/client/App.tsx +22 -5
- package/src/client/components/common/MarkdownRenderer.tsx +36 -0
- package/src/client/components/layout/desktop-layout/index.tsx +1 -1
- package/src/client/components/layout/session-sidebar/Modals.tsx +1 -1
- package/src/client/components/mobile/LayoutWrapper.tsx +1 -1
- package/src/client/components/settings/LazyModal.tsx +46 -0
- package/src/client/components/settings/Modal.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/EmptyWorkspacePrompt.tsx +43 -29
- package/src/client/components/workspace/chat-timeline/MessageItem.tsx +15 -69
- package/src/client/components/workspace/chat-timeline/MessageList.tsx +47 -66
- package/src/client/components/workspace/chat-timeline/RunFooter.tsx +60 -0
- package/src/client/components/workspace/chat-timeline/SubagentView.tsx +37 -21
- package/src/client/components/workspace/chat-timeline/ToolCallCard.tsx +22 -4
- package/src/client/components/workspace/chat-timeline/index.tsx +3 -6
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Edit.tsx +15 -19
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/EnvelopeHeader.tsx +46 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/FallbackOutput.tsx +18 -68
- package/src/client/components/workspace/file-explorer/TreeItem.tsx +8 -2
- package/src/client/hooks/chat/omp/index.ts +9 -10
- package/src/client/hooks/chat/omp/revalidation-throttle.ts +6 -5
- package/src/client/hooks/chat/timeline/actions.ts +1 -2
- package/src/client/hooks/chat/timeline/index.ts +5 -0
- package/src/client/hooks/chat/timeline/send.ts +1 -2
- package/src/client/hooks/chat/timeline/session-load.ts +3 -4
- package/src/client/hooks/ui/syntax-ready.ts +16 -3
- package/src/client/main.tsx +2 -5
- package/src/client/stubs/elkjs.ts +37 -0
- package/src/client/tailwind.css +10 -0
- package/src/server/lib/fs/git-ignore.ts +49 -0
- package/src/server/lib/omp/rpc/constants.ts +10 -0
- package/src/server/lib/omp/rpc/lifecycle.test.ts +138 -0
- package/src/server/lib/omp/rpc/manager.ts +32 -4
- package/src/server/lib/omp/rpc/session-commands.ts +32 -9
- package/src/server/lib/omp/rpc/session-registry.ts +3 -2
- package/src/server/lib/omp/rpc/subagent-liveness.test.ts +62 -0
- package/src/server/lib/omp/rpc/subagent-liveness.ts +100 -0
- package/src/server/lib/omp/session/messages.test.ts +112 -0
- package/src/server/lib/omp/session/messages.ts +6 -5
- package/src/server/routes/agent/command.ts +34 -16
- package/src/server/routes/fs/read.ts +15 -7
- package/src/shared/lib/chat/notice-row.test.ts +89 -0
- package/src/shared/lib/chat/notice-row.ts +78 -0
- package/src/shared/lib/chat/omp/abort.ts +41 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.test.ts +154 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.ts +36 -7
- package/src/shared/lib/chat/timeline/run-footer.test.ts +84 -0
- package/src/shared/lib/chat/timeline/run-footer.ts +96 -0
- package/src/shared/lib/chat/tool-output.test.ts +91 -0
- package/src/shared/lib/chat/tool-output.ts +54 -0
- package/src/shared/lib/chat/xml-envelope.ts +158 -0
- package/src/shared/lib/code/highlighter-lazy.ts +106 -0
- package/src/shared/lib/code/highlighter.ts +31 -52
- package/src/shared/lib/code/syntax-highlight.test.ts +36 -1
- package/src/shared/lib/code/syntax-highlight.ts +17 -3
- package/src/shared/lib/format/time.ts +19 -0
- package/src/shared/lib/markdown/fira-code-fonts.css +51 -0
- package/src/shared/lib/markdown/katex-fonts.css +20 -0
- package/src/shared/lib/markdown/katex.ts +101 -0
- package/src/shared/lib/markdown/marked.ts +23 -14
- package/src/shared/lib/markdown/math.test.ts +109 -0
- package/src/shared/lib/markdown/mermaid.ts +6 -0
- package/src/shared/lib/markdown/sanitize.ts +48 -2
- package/src/shared/lib/omp/session/mapper.ts +11 -3
- package/src/shared/lib/omp/session/messages-map.ts +12 -12
- package/src/shared/types/chat.ts +3 -1
- package/src/shared/types/fs.ts +3 -0
- package/dist/client/static/css/g.f36aa429fa.css +0 -1
- package/dist/client/static/css/index.a0f07455bd.css +0 -2
- package/dist/client/static/css/katex-vendor.ca3a0600e8.css +0 -1
- package/dist/client/static/font/KaTeX_AMS-Regular.1608a09b4a.woff +0 -0
- package/dist/client/static/font/KaTeX_AMS-Regular.4aafdb685c.ttf +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Bold.b67709187b.woff +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Bold.cce5b8ecea.ttf +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Regular.07ef19e7b0.ttf +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Regular.dad44a7fc6.woff +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Bold.9f256b8593.woff +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Bold.b18f59e1d1.ttf +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Regular.7c1871215e.woff +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Regular.ed38e79f57.ttf +0 -0
- package/dist/client/static/font/KaTeX_Main-Bold.b74a1a8b2c.ttf +0 -0
- package/dist/client/static/font/KaTeX_Main-Bold.d181c4650d.woff +0 -0
- package/dist/client/static/font/KaTeX_Main-BoldItalic.70d8b0a530.ttf +0 -0
- package/dist/client/static/font/KaTeX_Main-BoldItalic.e3f82f9d27.woff +0 -0
- package/dist/client/static/font/KaTeX_Main-Italic.47373d1e51.ttf +0 -0
- package/dist/client/static/font/KaTeX_Main-Italic.9024d815ba.woff +0 -0
- package/dist/client/static/font/KaTeX_Main-Regular.7f51fe0340.woff +0 -0
- package/dist/client/static/font/KaTeX_Main-Regular.b7f8fe9b5f.ttf +0 -0
- package/dist/client/static/font/KaTeX_Math-BoldItalic.a879cf8383.ttf +0 -0
- package/dist/client/static/font/KaTeX_Math-BoldItalic.f1035d8d5d.woff +0 -0
- package/dist/client/static/font/KaTeX_Math-Italic.5295ba483a.woff +0 -0
- package/dist/client/static/font/KaTeX_Math-Italic.939bc64440.ttf +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Bold.94e1e8dc5f.ttf +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Bold.bf59d231c3.woff +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Italic.7c9bc82b17.woff +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Italic.b4c20c84d8.ttf +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Regular.740484788f.woff +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Regular.d4d7ba4804.ttf +0 -0
- package/dist/client/static/font/KaTeX_Script-Regular.0750571032.woff +0 -0
- package/dist/client/static/font/KaTeX_Script-Regular.fe9cbbe1a0.ttf +0 -0
- package/dist/client/static/font/KaTeX_Size1-Regular.e1e279cbdd.woff +0 -0
- package/dist/client/static/font/KaTeX_Size1-Regular.fabc004aab.ttf +0 -0
- package/dist/client/static/font/KaTeX_Size2-Regular.577270225e.woff +0 -0
- package/dist/client/static/font/KaTeX_Size2-Regular.d6b476ecd3.ttf +0 -0
- package/dist/client/static/font/KaTeX_Size3-Regular.9acaf01c64.woff +0 -0
- package/dist/client/static/font/KaTeX_Size3-Regular.a144ef5840.ttf +0 -0
- package/dist/client/static/font/KaTeX_Size4-Regular.7a996c9da2.woff +0 -0
- package/dist/client/static/font/KaTeX_Size4-Regular.fbccdabe0a.ttf +0 -0
- package/dist/client/static/font/KaTeX_Typewriter-Regular.6258592bdc.woff +0 -0
- package/dist/client/static/font/KaTeX_Typewriter-Regular.d97aaf4a1e.ttf +0 -0
- package/dist/client/static/font/fira-code-cyrillic-400-normal.5971438a4a.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-400-normal.9bb7bfea9c.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-500-normal.5486265b18.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-500-normal.89f92bfb31.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-600-normal.2805b497e5.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-600-normal.86e0df22a3.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-700-normal.505a88f22a.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-700-normal.76f5e8d2e2.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-400-normal.3c4b15e57d.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-400-normal.61323debaa.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-500-normal.0f34210f2a.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-500-normal.2497cfa1aa.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-600-normal.40fce94af9.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-600-normal.6c8774b829.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-700-normal.4b742eb865.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-700-normal.e75eb4fccc.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-400-normal.bb900468b6.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-400-normal.be76e699ba.woff +0 -0
- package/dist/client/static/font/fira-code-greek-500-normal.176c9fb001.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-500-normal.d2c596136b.woff +0 -0
- package/dist/client/static/font/fira-code-greek-600-normal.490c3deed7.woff +0 -0
- package/dist/client/static/font/fira-code-greek-600-normal.7df14376d9.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-700-normal.c82b6072fc.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-700-normal.d5f2b2917b.woff +0 -0
- package/dist/client/static/font/fira-code-greek-ext-400-normal.4849a79781.woff +0 -0
- package/dist/client/static/font/fira-code-greek-ext-400-normal.7f5bfc4538.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-ext-500-normal.aaccca4075.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-ext-500-normal.e383181256.woff +0 -0
- package/dist/client/static/font/fira-code-greek-ext-600-normal.78cb4bff8e.woff +0 -0
- package/dist/client/static/font/fira-code-greek-ext-600-normal.d6c93d54f9.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-ext-700-normal.57ec7c92d9.woff +0 -0
- package/dist/client/static/font/fira-code-greek-ext-700-normal.8fafdae5e8.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-400-normal.973295532b.woff +0 -0
- package/dist/client/static/font/fira-code-latin-500-normal.7ae2be8fb8.woff +0 -0
- package/dist/client/static/font/fira-code-latin-600-normal.1dbbe5bc27.woff +0 -0
- package/dist/client/static/font/fira-code-latin-700-normal.497a70148b.woff +0 -0
- package/dist/client/static/font/fira-code-latin-ext-400-normal.9dfae29aaf.woff +0 -0
- package/dist/client/static/font/fira-code-latin-ext-400-normal.e534ae46e2.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-ext-500-normal.24c7b781b0.woff +0 -0
- package/dist/client/static/font/fira-code-latin-ext-500-normal.aad44ab072.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-ext-600-normal.7bed0f2f7e.woff +0 -0
- package/dist/client/static/font/fira-code-latin-ext-600-normal.c509a7c9f1.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-ext-700-normal.6c66c8b0a9.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-ext-700-normal.88f64e2822.woff +0 -0
- package/dist/client/static/font/fira-code-symbols2-400-normal.97d3f4b17c.woff +0 -0
- package/dist/client/static/font/fira-code-symbols2-500-normal.b06f1a06e4.woff +0 -0
- package/dist/client/static/font/fira-code-symbols2-600-normal.304b653b93.woff +0 -0
- package/dist/client/static/font/fira-code-symbols2-700-normal.d8ce0ad586.woff +0 -0
- package/dist/client/static/js/async/3j.a2fcea99be.js +0 -2
- package/dist/client/static/js/async/51.1938cec320.js +0 -2
- package/dist/client/static/js/async/6.7929428505.js +0 -1
- package/dist/client/static/js/async/69.a18a10882f.js +0 -1
- package/dist/client/static/js/async/a.bce4542392.js +0 -1
- package/dist/client/static/js/async/ar.9dc8b1992a.js +0 -2
- package/dist/client/static/js/async/bi.cbfe8eaa8e.js +0 -1
- package/dist/client/static/js/async/ct.38d9da8a5a.js +0 -1
- package/dist/client/static/js/async/fw.fb1a08b92d.js +0 -6
- package/dist/client/static/js/async/l6.45f3b62e15.js +0 -1
- package/dist/client/static/js/async/q.0b6fb9bf8d.js +0 -21
- package/dist/client/static/js/async/rd.b77011ab7f.js +0 -2
- package/dist/client/static/js/async/rf.cfbde7f744.js +0 -2
- package/dist/client/static/js/async/s.c7d521a8ae.js +0 -2
- package/dist/client/static/js/async/sy.7345abcd67.js +0 -94
- package/dist/client/static/js/async/t.0dcb01dfde.js +0 -1
- package/dist/client/static/js/async/v.5c7a4b087b.js +0 -1
- package/dist/client/static/js/async/vu.311f1d202c.js +0 -34
- package/dist/client/static/js/async/yg.9bd4e09ecb.js +0 -149
- package/dist/client/static/js/async/yv.593a3f7d54.js +0 -1
- package/dist/client/static/js/g.124fba0826.js +0 -139
- package/dist/client/static/js/index.907fd4670e.js +0 -68
- package/dist/client/static/js/katex-vendor.95e48cf65a.js +0 -1
- package/src/shared/lib/chat/order.ts +0 -40
- /package/dist/client/static/js/async/{1k.544adbf77d.js.LICENSE.txt → 1k.5974df3b87.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{3j.a2fcea99be.js.LICENSE.txt → 3j.357c80ff38.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{3y.22075aa079.js.LICENSE.txt → 3y.ededeebde8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{51.1938cec320.js.LICENSE.txt → 51.95bcae98af.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{ar.9dc8b1992a.js.LICENSE.txt → a.e05da304c8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{fw.fb1a08b92d.js.LICENSE.txt → art.62139ed479.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{m.3fc017a055.js.LICENSE.txt → fw.6a51998a8e.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.0b6fb9bf8d.js.LICENSE.txt → m.4b29d34f93.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{rf.cfbde7f744.js.LICENSE.txt → q.7fb4d2f374.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{rd.b77011ab7f.js.LICENSE.txt → rd.cb6bf495bb.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{s.c7d521a8ae.js.LICENSE.txt → rf.d626afc367.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{index.907fd4670e.js.LICENSE.txt → async/ud.3065bfee31.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{g.124fba0826.js.LICENSE.txt → uy.5f791144ce.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Demand-driven KaTeX rendering.
|
|
8
|
+
*
|
|
9
|
+
* `katex` is ~522 kB of JS that only matters when a message actually contains
|
|
10
|
+
* math. Importing it statically from `marked.ts` put it in the *synchronous*
|
|
11
|
+
* bundle: every page load paid for it, whether or not a single formula was ever
|
|
12
|
+
* displayed.
|
|
13
|
+
*
|
|
14
|
+
* The pipeline keeps its synchronous shape by emitting a placeholder instead:
|
|
15
|
+
* `marked.ts` renders `<span class="math-pending" data-math="…">` during parse,
|
|
16
|
+
* and this module swaps those placeholders for real KaTeX markup once the
|
|
17
|
+
* library lands — the same split `mermaid.ts` uses for ```mermaid fences, so
|
|
18
|
+
* the two features behave identically from the renderer's point of view.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { sanitizeKatexHtml } from '@/shared/lib/markdown/sanitize';
|
|
22
|
+
|
|
23
|
+
type KatexModule = typeof import('katex');
|
|
24
|
+
|
|
25
|
+
let katexPromise: Promise<KatexModule['default'] | null> | null = null;
|
|
26
|
+
|
|
27
|
+
/** Placeholder class emitted by `marked.ts` in place of rendered math. */
|
|
28
|
+
export const MATH_PENDING_CLASS = 'math-pending';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load KaTeX once per page. Never throws: on failure the promise resolves
|
|
32
|
+
* `null` and placeholders stay as visible source text, which is what the old
|
|
33
|
+
* synchronous `renderKatex` fallback did for invalid LaTeX.
|
|
34
|
+
*/
|
|
35
|
+
function loadKatex(): Promise<KatexModule['default'] | null> {
|
|
36
|
+
if (!katexPromise) {
|
|
37
|
+
katexPromise = import('katex')
|
|
38
|
+
.then((mod) => mod.default)
|
|
39
|
+
.catch((error) => {
|
|
40
|
+
console.error('[katex] load failed', error);
|
|
41
|
+
return null;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return katexPromise;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Start loading KaTeX without awaiting it. Called from the renderer so the
|
|
49
|
+
* library is already in flight while the rest of the message renders.
|
|
50
|
+
*/
|
|
51
|
+
export function preloadKatex(): void {
|
|
52
|
+
void loadKatex();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Render one placeholder element into real KaTeX markup. `displayMode` is
|
|
57
|
+
* carried on the placeholder as `data-math-display` ("1" for block math).
|
|
58
|
+
*
|
|
59
|
+
* The generated markup is passed through DOMPurify's MathML profile before it
|
|
60
|
+
* touches the DOM: `tex` is model output, so although KaTeX escapes its own
|
|
61
|
+
* output, the result still crosses an innerHTML boundary and gets sanitized
|
|
62
|
+
* like every other model-authored fragment in this pipeline.
|
|
63
|
+
*/
|
|
64
|
+
function renderPlaceholder(el: HTMLElement, katex: KatexModule['default']): void {
|
|
65
|
+
const tex = el.dataset.math ?? '';
|
|
66
|
+
const displayMode = el.dataset.mathDisplay === '1';
|
|
67
|
+
try {
|
|
68
|
+
el.innerHTML = sanitizeKatexHtml(katex.renderToString(tex, { throwOnError: false, displayMode }));
|
|
69
|
+
el.classList.remove(MATH_PENDING_CLASS);
|
|
70
|
+
el.removeAttribute('data-math');
|
|
71
|
+
el.removeAttribute('data-math-display');
|
|
72
|
+
} catch {
|
|
73
|
+
// Keep the source text visible rather than blanking the formula.
|
|
74
|
+
el.textContent = tex;
|
|
75
|
+
el.classList.remove(MATH_PENDING_CLASS);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Replace every pending math placeholder under `root` with KaTeX output.
|
|
81
|
+
* Loads the library on demand; resolves with how many were rendered.
|
|
82
|
+
*/
|
|
83
|
+
export async function hydrateMathBlocks(root: ParentNode): Promise<{ rendered: number; failed: number }> {
|
|
84
|
+
const pending = [...root.querySelectorAll<HTMLElement>(`.${MATH_PENDING_CLASS}`)];
|
|
85
|
+
if (pending.length === 0) return { rendered: 0, failed: 0 };
|
|
86
|
+
|
|
87
|
+
const katex = await loadKatex();
|
|
88
|
+
if (!katex) return { rendered: 0, failed: pending.length };
|
|
89
|
+
|
|
90
|
+
let rendered = 0;
|
|
91
|
+
for (const el of pending) {
|
|
92
|
+
// A streaming re-render may have replaced the node while awaiting.
|
|
93
|
+
if (!el.isConnected) continue;
|
|
94
|
+
renderPlaceholder(el, katex);
|
|
95
|
+
rendered += 1;
|
|
96
|
+
}
|
|
97
|
+
return { rendered, failed: 0 };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Sanitize KaTeX-generated markup (kept here so callers need one import). */
|
|
101
|
+
export { sanitizeKatexHtml };
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
|
|
20
20
|
import { Marked } from 'marked';
|
|
21
21
|
import markedLinkifyIt from 'marked-linkify-it';
|
|
22
|
-
import katex from 'katex';
|
|
23
22
|
import remend from 'remend';
|
|
24
23
|
import { highlightCode } from '@/shared/lib/code/syntax-highlight';
|
|
24
|
+
import { MATH_PENDING_CLASS } from '@/shared/lib/markdown/katex';
|
|
25
25
|
|
|
26
26
|
/** Escape HTML for safe interpolation inside highlighted output. */
|
|
27
27
|
function escapeHtml(value: string): string {
|
|
@@ -33,15 +33,24 @@ function escapeHtml(value: string): string {
|
|
|
33
33
|
.replace(/'/g, ''');
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Emit a math placeholder instead of rendering KaTeX inline.
|
|
38
|
+
*
|
|
39
|
+
* `katex` is ~522 kB of JS worth loading only when a message contains math, so
|
|
40
|
+
* it is not imported here. The placeholder carries the TeX source and the
|
|
41
|
+
* display flag; `katex.ts` swaps it for real markup once the library lands
|
|
42
|
+
* (see `hydrateMathBlocks`). Both attributes are HTML-escaped, and escaping
|
|
43
|
+
* `&` first keeps the entity encoding intact through the attribute round-trip.
|
|
44
|
+
*/
|
|
45
|
+
function renderMathPlaceholder(tex: string, displayMode: boolean): string {
|
|
46
|
+
const escaped = escapeHtml(tex);
|
|
47
|
+
const display = displayMode ? ' data-math-display="1"' : '';
|
|
48
|
+
const span = `<span class="${MATH_PENDING_CLASS}" data-math="${escaped}"${display}></span>`;
|
|
49
|
+
// Block math must stay inside a block element: DOMPurify drops a bare
|
|
50
|
+
// `<span>` that sits at the top level of the fragment, which silently deleted
|
|
51
|
+
// every `$$…$$` formula. KaTeX's own output is inline-level too, so the old
|
|
52
|
+
// synchronous renderer relied on the same wrapper being present.
|
|
53
|
+
return displayMode ? `<p>${span}</p>` : span;
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
interface TokenLike {
|
|
@@ -130,7 +139,7 @@ marked.use({
|
|
|
130
139
|
: undefined;
|
|
131
140
|
},
|
|
132
141
|
renderer(token: TokenLike) {
|
|
133
|
-
return
|
|
142
|
+
return renderMathPlaceholder(token.text ?? '', false);
|
|
134
143
|
},
|
|
135
144
|
},
|
|
136
145
|
// \[ ... \] block math
|
|
@@ -147,7 +156,7 @@ marked.use({
|
|
|
147
156
|
: undefined;
|
|
148
157
|
},
|
|
149
158
|
renderer(token: TokenLike) {
|
|
150
|
-
return
|
|
159
|
+
return renderMathPlaceholder(token.text ?? '', true);
|
|
151
160
|
},
|
|
152
161
|
},
|
|
153
162
|
// $$ ... $$ display math (block-level, must be its own line)
|
|
@@ -164,7 +173,7 @@ marked.use({
|
|
|
164
173
|
: undefined;
|
|
165
174
|
},
|
|
166
175
|
renderer(token: TokenLike) {
|
|
167
|
-
return
|
|
176
|
+
return renderMathPlaceholder(token.text ?? '', true);
|
|
168
177
|
},
|
|
169
178
|
},
|
|
170
179
|
// $ ... $ inline math, per remark-math flanking rules: the opening $ must
|
|
@@ -186,7 +195,7 @@ marked.use({
|
|
|
186
195
|
: undefined;
|
|
187
196
|
},
|
|
188
197
|
renderer(token: TokenLike) {
|
|
189
|
-
return
|
|
198
|
+
return renderMathPlaceholder(token.text ?? '', false);
|
|
190
199
|
},
|
|
191
200
|
},
|
|
192
201
|
],
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* End-to-end coverage for lazy math rendering.
|
|
8
|
+
*
|
|
9
|
+
* KaTeX is no longer rendered during markdown parsing (it is a ~522 kB async
|
|
10
|
+
* chunk), so the pipeline emits a `.math-pending` placeholder that
|
|
11
|
+
* `hydrateMathBlocks` later swaps for real markup. That split has two failure
|
|
12
|
+
* modes a unit test on either half alone would miss:
|
|
13
|
+
*
|
|
14
|
+
* 1. DOMPurify drops a bare root-level `<span>`, which silently deleted every
|
|
15
|
+
* block (`$$…$$`) formula and stripped KaTeX's outer `.katex` wrapper.
|
|
16
|
+
* 2. The placeholder attributes (`data-math`, `data-math-display`) must survive
|
|
17
|
+
* `sanitizeHtml`, or hydration has nothing to read.
|
|
18
|
+
*
|
|
19
|
+
* These run against a real DOM (happy-dom) because both paths are DOM-dependent;
|
|
20
|
+
* they are no-ops everywhere else.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { beforeAll, describe, expect, test } from 'bun:test';
|
|
24
|
+
import { Window } from 'happy-dom';
|
|
25
|
+
|
|
26
|
+
let hydrateMathBlocks: typeof import('@/shared/lib/markdown/katex').hydrateMathBlocks;
|
|
27
|
+
let renderMarkdown: typeof import('@/shared/lib/markdown/marked').renderMarkdown;
|
|
28
|
+
let sanitizeHtml: typeof import('@/shared/lib/markdown/sanitize').sanitizeHtml;
|
|
29
|
+
|
|
30
|
+
beforeAll(async () => {
|
|
31
|
+
const win = new Window({ url: 'http://localhost' });
|
|
32
|
+
Object.assign(globalThis, {
|
|
33
|
+
window: win,
|
|
34
|
+
document: win.document,
|
|
35
|
+
MutationObserver: win.MutationObserver,
|
|
36
|
+
Node: win.Node,
|
|
37
|
+
Element: win.Element,
|
|
38
|
+
HTMLElement: win.HTMLElement,
|
|
39
|
+
NodeFilter: win.NodeFilter,
|
|
40
|
+
});
|
|
41
|
+
// Dynamic import is required here, not stylistic: `sanitize.ts` binds
|
|
42
|
+
// DOMPurify at module scope, which captures the DOM globals present at
|
|
43
|
+
// evaluation time. A static import would run before the happy-dom globals
|
|
44
|
+
// above exist and bind the sanitizer to a window-less environment.
|
|
45
|
+
({ hydrateMathBlocks } = await import('@/shared/lib/markdown/katex'));
|
|
46
|
+
({ renderMarkdown } = await import('@/shared/lib/markdown/marked'));
|
|
47
|
+
({ sanitizeHtml } = await import('@/shared/lib/markdown/sanitize'));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** Render markdown and mount it exactly as `MarkdownRenderer` does. */
|
|
51
|
+
function mount(content: string): HTMLElement {
|
|
52
|
+
const host = document.createElement('div');
|
|
53
|
+
host.innerHTML = sanitizeHtml(renderMarkdown(content));
|
|
54
|
+
document.body.appendChild(host);
|
|
55
|
+
return host;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe('lazy math rendering', () => {
|
|
59
|
+
test('inline math survives sanitization and hydrates to KaTeX', async () => {
|
|
60
|
+
const host = mount('rumus $E = mc^2$ disini');
|
|
61
|
+
|
|
62
|
+
const placeholder = host.querySelector('.math-pending');
|
|
63
|
+
expect(placeholder).not.toBeNull();
|
|
64
|
+
expect(placeholder?.getAttribute('data-math')).toBe('E = mc^2');
|
|
65
|
+
|
|
66
|
+
expect(await hydrateMathBlocks(host)).toEqual({ rendered: 1, failed: 0 });
|
|
67
|
+
|
|
68
|
+
expect(host.querySelectorAll('.math-pending').length).toBe(0);
|
|
69
|
+
// The outer wrapper is what every katex.css layout rule targets.
|
|
70
|
+
expect(host.querySelector('.katex')).not.toBeNull();
|
|
71
|
+
expect(host.querySelectorAll('.katex math').length).toBeGreaterThan(0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('block math renders in display mode', async () => {
|
|
75
|
+
const host = mount('$$\\int_0^1 x^2 dx$$');
|
|
76
|
+
|
|
77
|
+
const placeholder = host.querySelector('.math-pending');
|
|
78
|
+
expect(placeholder).not.toBeNull();
|
|
79
|
+
expect(placeholder?.getAttribute('data-math-display')).toBe('1');
|
|
80
|
+
|
|
81
|
+
await hydrateMathBlocks(host);
|
|
82
|
+
expect(host.querySelector('.katex-display')).not.toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('multiple formulas each hydrate', async () => {
|
|
86
|
+
// `$$…$$` only parses as display math on its own line (see the flanking
|
|
87
|
+
// rules in marked.ts), so the block case is a separate line here.
|
|
88
|
+
const host = mount('$a$ dan $b$\n\n$$c$$');
|
|
89
|
+
expect(host.querySelectorAll('.math-pending').length).toBe(3);
|
|
90
|
+
|
|
91
|
+
expect((await hydrateMathBlocks(host)).rendered).toBe(3);
|
|
92
|
+
expect(host.querySelectorAll('.katex').length).toBe(3);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('no placeholders is a no-op', async () => {
|
|
96
|
+
const host = mount('plain text, no math');
|
|
97
|
+
expect(await hydrateMathBlocks(host)).toEqual({ rendered: 0, failed: 0 });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('invalid LaTeX degrades without throwing', async () => {
|
|
101
|
+
const host = mount('bad $\\frac{1}{$ here');
|
|
102
|
+
await expect(hydrateMathBlocks(host)).resolves.toBeDefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('escapes the tex source so it cannot break out of the attribute', () => {
|
|
106
|
+
const html = renderMarkdown('a $x" onmouseover="alert(1)$ b');
|
|
107
|
+
expect(html).not.toContain('onmouseover="alert');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -63,6 +63,12 @@ async function loadMermaid(themeKey: ThemeKey): Promise<MermaidModule['default']
|
|
|
63
63
|
startOnLoad: false,
|
|
64
64
|
securityLevel: 'strict',
|
|
65
65
|
theme: themeKey,
|
|
66
|
+
// Mermaid 12 defaults the global layout to `elk`, which is what pulled
|
|
67
|
+
// the ~1.4 MB elkjs bundle into the client build for every diagram.
|
|
68
|
+
// `dagre` ships inside mermaid's own core chunk and renders all the
|
|
69
|
+
// diagram types this app sees, so pin it here and alias `elkjs` to a
|
|
70
|
+
// stub in the build (`src/client/stubs/elkjs.ts`).
|
|
71
|
+
layout: 'dagre',
|
|
66
72
|
flowchart: { useMaxWidth: false, wrappingWidth: 100000 },
|
|
67
73
|
sequence: { useMaxWidth: false },
|
|
68
74
|
});
|
|
@@ -103,11 +103,23 @@ export function sanitizeHtml(html: string): string {
|
|
|
103
103
|
if (typeof window === 'undefined' || !window.document) return html;
|
|
104
104
|
|
|
105
105
|
const hasSvgIcon = /<svg[^>]*>/.test(html);
|
|
106
|
-
const
|
|
106
|
+
const hasMathPlaceholder = html.includes('math-pending');
|
|
107
|
+
const config = hasKatex(html) || hasSvgIcon || hasMathPlaceholder
|
|
107
108
|
? {
|
|
108
109
|
USE_PROFILES: { html: true, mathMl: true } as const,
|
|
109
110
|
ADD_TAGS: [...KATEX_MATHML_TAGS, ...(hasSvgIcon ? COPY_ICON_TAGS : [])],
|
|
110
|
-
ADD_ATTR: [
|
|
111
|
+
ADD_ATTR: [
|
|
112
|
+
'aria-hidden',
|
|
113
|
+
'aria-label',
|
|
114
|
+
'encoding',
|
|
115
|
+
'nonce',
|
|
116
|
+
// Math placeholders carry the TeX source and display flag for the
|
|
117
|
+
// lazy KaTeX pass; DOMPurify strips unknown data-* attributes unless
|
|
118
|
+
// they are listed here.
|
|
119
|
+
'data-math',
|
|
120
|
+
'data-math-display',
|
|
121
|
+
...(hasSvgIcon ? COPY_ICON_ATTRS : []),
|
|
122
|
+
],
|
|
111
123
|
}
|
|
112
124
|
: { USE_PROFILES: { html: true } as const };
|
|
113
125
|
|
|
@@ -136,3 +148,37 @@ export function sanitizeMermaidSvg(svg: string): string {
|
|
|
136
148
|
return svg;
|
|
137
149
|
}
|
|
138
150
|
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Sanitize a KaTeX-rendered HTML fragment before it is injected via innerHTML.
|
|
154
|
+
*
|
|
155
|
+
* `sanitizeHtml` already allows the MathML profile when the *parsed markdown*
|
|
156
|
+
* contains KaTeX classes, but lazy math rendering injects after that pass, so
|
|
157
|
+
* the generated fragment is sanitized separately.
|
|
158
|
+
*
|
|
159
|
+
* `class` MUST be allowed: KaTeX's visual layout is entirely class-driven
|
|
160
|
+
* (`.katex`, `.katex-mathml`, `.katex-html`), and DOMPurify drops attributes it
|
|
161
|
+
* has not been told about. Without it the wrapper spans are stripped and only
|
|
162
|
+
* bare MathML survives, which renders unstyled.
|
|
163
|
+
*/
|
|
164
|
+
export function sanitizeKatexHtml(html: string): string {
|
|
165
|
+
if (!html) return html;
|
|
166
|
+
if (typeof window === 'undefined' || !window.document) return html;
|
|
167
|
+
|
|
168
|
+
// DOMPurify drops a bare root-level <span>, which would strip KaTeX's outer
|
|
169
|
+
// `.katex` wrapper — the element every layout rule in katex.css hangs off.
|
|
170
|
+
// Wrapping the fragment keeps the parser from discarding it; the wrapper is
|
|
171
|
+
// removed again afterwards so only the KaTeX markup is injected.
|
|
172
|
+
const wrapped = `<div data-katex-wrap>${html}</div>`;
|
|
173
|
+
try {
|
|
174
|
+
const clean = DOMPurify.sanitize(wrapped, {
|
|
175
|
+
USE_PROFILES: { html: true, mathMl: true },
|
|
176
|
+
ADD_ATTR: ['class', 'aria-hidden', 'encoding', 'style', 'data-katex-wrap'],
|
|
177
|
+
} as Config);
|
|
178
|
+
const host = document.createElement('div');
|
|
179
|
+
host.innerHTML = clean;
|
|
180
|
+
return host.querySelector('[data-katex-wrap]')?.innerHTML ?? clean;
|
|
181
|
+
} catch {
|
|
182
|
+
return html;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import type { ChatMessageData, ToolCallData } from '@/shared/types';
|
|
16
16
|
import { extractText, extractUserImageAttachments, parseMessageBlocks, stripInlinedTextAttachments, toToolCallData } from '@/shared/lib/omp/session/parse-message-blocks';
|
|
17
|
+
import { reminderPartIndex } from '@/shared/lib/chat/notice-row';
|
|
17
18
|
import { deriveTurnError } from '@/shared/lib/omp/session/turn-error';
|
|
18
19
|
import { formatClock } from '@/shared/lib/format/time';
|
|
19
20
|
import { toEpochMs } from '@/shared/lib/omp/session/timestamps';
|
|
@@ -72,9 +73,16 @@ export function toChatMessage(raw: Record<string, unknown>, streaming = true): C
|
|
|
72
73
|
?? (startedAt !== undefined && durationMs !== undefined ? startedAt + durationMs : undefined);
|
|
73
74
|
const provider = typeof raw.provider === 'string' ? raw.provider : undefined;
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
const reminderIdx = reminderPartIndex(parsed.textParts);
|
|
77
|
+
if (raw.role === 'developer' || raw.role === 'system' || reminderIdx !== undefined) {
|
|
78
|
+
// A reminder envelope keeps its wrapper — `SystemNotice` reads that tag to
|
|
79
|
+
// label the card and strips it before markdown, and the JSONL path stores
|
|
80
|
+
// it the same way so a live row and its reloaded twin are identical.
|
|
81
|
+
// Developer/system turns are notices by role and have no wrapper to keep.
|
|
82
|
+
const source = reminderIdx !== undefined ? parsed.textParts[reminderIdx] : parsed.textParts.join('\n');
|
|
83
|
+
const notice = reminderIdx !== undefined
|
|
84
|
+
? source.trim()
|
|
85
|
+
: source.replace(/<\/?system-reminder[^>]*>/g, '').trim();
|
|
78
86
|
if (!notice) return null;
|
|
79
87
|
return {
|
|
80
88
|
id,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { ChatMessageData } from '@/shared/types/chat';
|
|
7
7
|
import { extractText, extractUserImageAttachments, parseAssistantContent, resultOutput, roleFor, stripInlinedTextAttachments, type OmpMessageEntry } from '@/shared/lib/omp/session/messages-parse';
|
|
8
8
|
import { isRecord } from '@/shared/lib/util/guards';
|
|
9
|
+
import { reminderPartIndex } from '@/shared/lib/chat/notice-row';
|
|
9
10
|
import { deriveTurnError } from '@/shared/lib/omp/session/turn-error';
|
|
10
11
|
import { toEpochMs } from '@/shared/lib/omp/session/timestamps';
|
|
11
12
|
|
|
@@ -74,19 +75,18 @@ export function toChatMessage(entry: OmpMessageEntry): ChatMessageData | null {
|
|
|
74
75
|
durationMs,
|
|
75
76
|
usage,
|
|
76
77
|
};
|
|
77
|
-
// Live-path parity (mapper.toChatMessage):
|
|
78
|
-
// <system-reminder>
|
|
78
|
+
// Live-path parity (mapper.toChatMessage): a text block that IS a
|
|
79
|
+
// <system-reminder> envelope renders as a SystemNotice alert, never as raw
|
|
79
80
|
// content — otherwise the reminder leaks into the timeline as markdown.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
81
|
+
// Only a wrapper counts: prose that merely quotes the tag is content, and
|
|
82
|
+
// moving it into `notice` would hide the answer and delete the quoted tags.
|
|
83
|
+
const reminderIdx = reminderPartIndex(parsed.textParts);
|
|
84
|
+
if (reminderIdx !== undefined) {
|
|
85
|
+
message.notice = parsed.textParts[reminderIdx].trim();
|
|
86
|
+
message.content = parsed.textParts
|
|
87
|
+
.filter((_, index) => index !== reminderIdx)
|
|
88
|
+
.join('\n')
|
|
89
|
+
.trim();
|
|
90
90
|
}
|
|
91
91
|
if (turnError) message.error = turnError;
|
|
92
92
|
if (parsed.thinking) message.thinking = { thought: parsed.thinking, isGenerating: false };
|
package/src/shared/types/chat.ts
CHANGED
|
@@ -125,7 +125,9 @@ export interface ChatMessageData {
|
|
|
125
125
|
/** Short human intent for tool calls (omp arguments.i) shown before the tools. */
|
|
126
126
|
intent?: string;
|
|
127
127
|
/** System notice surfaced by omp (e.g. ultrathink-notice) — rendered as an
|
|
128
|
-
* alert row, not as assistant content.
|
|
128
|
+
* alert row, not as assistant content. A row that ALSO carries the turn's
|
|
129
|
+
* own metadata (model/provider/usage/…) is an assistant answer omp diverted
|
|
130
|
+
* into this field and renders as text — see chat/notice-row.ts. */
|
|
129
131
|
notice?: string;
|
|
130
132
|
toolCalls?: ToolCallData[];
|
|
131
133
|
actions?: (AgentActionData | ToolCallData)[];
|
package/src/shared/types/fs.ts
CHANGED
|
@@ -6,6 +6,9 @@ export interface FsNode {
|
|
|
6
6
|
modified?: number;
|
|
7
7
|
children?: FsNode[];
|
|
8
8
|
forceExpanded?: boolean;
|
|
9
|
+
/** Matches a git ignore rule (repo `.gitignore`, `.git/info/exclude`, or the
|
|
10
|
+
* global `core.excludesFile`) — rendered faded in the Files panel. */
|
|
11
|
+
ignored?: boolean;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
export interface OpenedFile {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:400;src:url(/static/font/fira-code-cyrillic-ext-400-normal.3c4b15e57d.woff2)format("woff2"),url(/static/font/fira-code-cyrillic-ext-400-normal.61323debaa.woff)format("woff");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:400;src:url(/static/font/fira-code-cyrillic-400-normal.5971438a4a.woff2)format("woff2"),url(/static/font/fira-code-cyrillic-400-normal.9bb7bfea9c.woff)format("woff");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:400;src:url(/static/font/fira-code-greek-ext-400-normal.7f5bfc4538.woff2)format("woff2"),url(/static/font/fira-code-greek-ext-400-normal.4849a79781.woff)format("woff");unicode-range:U+1F??}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:400;src:url(/static/font/fira-code-greek-400-normal.bb900468b6.woff2)format("woff2"),url(/static/font/fira-code-greek-400-normal.be76e699ba.woff)format("woff");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:400;src:url(data:font/woff2;base64,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)format("woff2"),url(/static/font/fira-code-symbols2-400-normal.97d3f4b17c.woff)format("woff");unicode-range:U+2000-2001,U+2004-2008,U+200A,U+23B8-23BD,U+2500-259F}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:400;src:url(/static/font/fira-code-latin-ext-400-normal.e534ae46e2.woff2)format("woff2"),url(/static/font/fira-code-latin-ext-400-normal.9dfae29aaf.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:400;src:url(/static/font/fira-code-latin-400-normal.2655b10c55.woff2)format("woff2"),url(/static/font/fira-code-latin-400-normal.973295532b.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:500;src:url(/static/font/fira-code-cyrillic-ext-500-normal.0f34210f2a.woff2)format("woff2"),url(/static/font/fira-code-cyrillic-ext-500-normal.2497cfa1aa.woff)format("woff");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:500;src:url(/static/font/fira-code-cyrillic-500-normal.5486265b18.woff2)format("woff2"),url(/static/font/fira-code-cyrillic-500-normal.89f92bfb31.woff)format("woff");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:500;src:url(/static/font/fira-code-greek-ext-500-normal.aaccca4075.woff2)format("woff2"),url(/static/font/fira-code-greek-ext-500-normal.e383181256.woff)format("woff");unicode-range:U+1F??}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:500;src:url(/static/font/fira-code-greek-500-normal.176c9fb001.woff2)format("woff2"),url(/static/font/fira-code-greek-500-normal.d2c596136b.woff)format("woff");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:500;src:url(data:font/woff2;base64,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)format("woff2"),url(/static/font/fira-code-symbols2-500-normal.b06f1a06e4.woff)format("woff");unicode-range:U+2000-2001,U+2004-2008,U+200A,U+23B8-23BD,U+2500-259F}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:500;src:url(/static/font/fira-code-latin-ext-500-normal.aad44ab072.woff2)format("woff2"),url(/static/font/fira-code-latin-ext-500-normal.24c7b781b0.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:500;src:url(/static/font/fira-code-latin-500-normal.92a991f204.woff2)format("woff2"),url(/static/font/fira-code-latin-500-normal.7ae2be8fb8.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:600;src:url(/static/font/fira-code-cyrillic-ext-600-normal.40fce94af9.woff2)format("woff2"),url(/static/font/fira-code-cyrillic-ext-600-normal.6c8774b829.woff)format("woff");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:600;src:url(/static/font/fira-code-cyrillic-600-normal.86e0df22a3.woff2)format("woff2"),url(/static/font/fira-code-cyrillic-600-normal.2805b497e5.woff)format("woff");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:600;src:url(/static/font/fira-code-greek-ext-600-normal.d6c93d54f9.woff2)format("woff2"),url(/static/font/fira-code-greek-ext-600-normal.78cb4bff8e.woff)format("woff");unicode-range:U+1F??}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:600;src:url(/static/font/fira-code-greek-600-normal.7df14376d9.woff2)format("woff2"),url(/static/font/fira-code-greek-600-normal.490c3deed7.woff)format("woff");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:600;src:url(data:font/woff2;base64,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)format("woff2"),url(/static/font/fira-code-symbols2-600-normal.304b653b93.woff)format("woff");unicode-range:U+2000-2001,U+2004-2008,U+200A,U+23B8-23BD,U+2500-259F}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:600;src:url(/static/font/fira-code-latin-ext-600-normal.c509a7c9f1.woff2)format("woff2"),url(/static/font/fira-code-latin-ext-600-normal.7bed0f2f7e.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:600;src:url(/static/font/fira-code-latin-600-normal.43788eb7f1.woff2)format("woff2"),url(/static/font/fira-code-latin-600-normal.1dbbe5bc27.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:700;src:url(/static/font/fira-code-cyrillic-ext-700-normal.e75eb4fccc.woff2)format("woff2"),url(/static/font/fira-code-cyrillic-ext-700-normal.4b742eb865.woff)format("woff");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:700;src:url(/static/font/fira-code-cyrillic-700-normal.76f5e8d2e2.woff2)format("woff2"),url(/static/font/fira-code-cyrillic-700-normal.505a88f22a.woff)format("woff");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:700;src:url(/static/font/fira-code-greek-ext-700-normal.8fafdae5e8.woff2)format("woff2"),url(/static/font/fira-code-greek-ext-700-normal.57ec7c92d9.woff)format("woff");unicode-range:U+1F??}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:700;src:url(/static/font/fira-code-greek-700-normal.c82b6072fc.woff2)format("woff2"),url(/static/font/fira-code-greek-700-normal.d5f2b2917b.woff)format("woff");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:700;src:url(data:font/woff2;base64,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)format("woff2"),url(/static/font/fira-code-symbols2-700-normal.d8ce0ad586.woff)format("woff");unicode-range:U+2000-2001,U+2004-2008,U+200A,U+23B8-23BD,U+2500-259F}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:700;src:url(/static/font/fira-code-latin-ext-700-normal.6c66c8b0a9.woff2)format("woff2"),url(/static/font/fira-code-latin-ext-700-normal.88f64e2822.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Fira Code;font-style:normal;font-display:swap;font-weight:700;src:url(/static/font/fira-code-latin-700-normal.2667215d33.woff2)format("woff2"),url(/static/font/fira-code-latin-700-normal.497a70148b.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|