@agent-native/core 0.101.10 → 0.101.11

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 (79) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/client.ts +23 -1
  5. package/corpus/core/src/a2a/handlers.ts +65 -1
  6. package/corpus/core/src/a2a/server.ts +124 -0
  7. package/corpus/core/src/a2a/task-store.ts +253 -0
  8. package/corpus/core/src/a2a/types.ts +26 -0
  9. package/corpus/core/src/agent/production-agent.ts +5 -2
  10. package/corpus/core/src/mcp/builtin-tools.ts +25 -1
  11. package/corpus/core/src/scripts/call-agent.ts +22 -3
  12. package/corpus/core/src/server/agent-chat-plugin.ts +86 -1
  13. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
  14. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +23 -0
  15. package/corpus/templates/content/app/components/editor/MathRenderer.tsx +43 -0
  16. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +260 -4
  17. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +78 -0
  18. package/corpus/templates/content/app/global.css +53 -0
  19. package/corpus/templates/content/app/i18n/zh-TW.ts +13 -0
  20. package/corpus/templates/content/app/i18n-data.ts +163 -0
  21. package/corpus/templates/content/app/root.tsx +2 -0
  22. package/corpus/templates/content/changelog/2026-07-14-latex-equations-render-in-documents-public-pages-and-exports.md +6 -0
  23. package/corpus/templates/content/package.json +1 -0
  24. package/corpus/templates/content/shared/document-export.ts +296 -25
  25. package/corpus/templates/content/shared/inline-math.ts +128 -0
  26. package/corpus/templates/content/shared/math-rendering.ts +50 -0
  27. package/corpus/templates/content/shared/nfm.ts +17 -14
  28. package/corpus/templates/content/vite.config.ts +0 -1
  29. package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +17 -0
  30. package/corpus/templates/mail/AGENTS.md +21 -19
  31. package/corpus/templates/mail/actions/create-attachment-upload.ts +49 -0
  32. package/corpus/templates/mail/changelog/2026-07-14-connected-agents-can-now-securely-upload-local-files-for-mai.md +6 -0
  33. package/corpus/templates/mail/server/handlers/media.ts +84 -61
  34. package/corpus/templates/mail/server/lib/attachment-upload-ticket.ts +234 -0
  35. package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -4
  36. package/corpus/templates/mail/server/lib/media-upload.ts +114 -0
  37. package/corpus/templates/mail/server/plugins/auth.ts +9 -1
  38. package/corpus/templates/mail/server/routes/api/media/attachment-upload/[uploadId].put.ts +1 -0
  39. package/dist/a2a/client.d.ts +5 -1
  40. package/dist/a2a/client.d.ts.map +1 -1
  41. package/dist/a2a/client.js +18 -1
  42. package/dist/a2a/client.js.map +1 -1
  43. package/dist/a2a/handlers.d.ts.map +1 -1
  44. package/dist/a2a/handlers.js +57 -8
  45. package/dist/a2a/handlers.js.map +1 -1
  46. package/dist/a2a/server.d.ts.map +1 -1
  47. package/dist/a2a/server.js +87 -1
  48. package/dist/a2a/server.js.map +1 -1
  49. package/dist/a2a/task-store.d.ts +27 -0
  50. package/dist/a2a/task-store.d.ts.map +1 -1
  51. package/dist/a2a/task-store.js +216 -0
  52. package/dist/a2a/task-store.js.map +1 -1
  53. package/dist/a2a/types.d.ts +24 -0
  54. package/dist/a2a/types.d.ts.map +1 -1
  55. package/dist/a2a/types.js.map +1 -1
  56. package/dist/agent/production-agent.d.ts +1 -0
  57. package/dist/agent/production-agent.d.ts.map +1 -1
  58. package/dist/agent/production-agent.js +5 -2
  59. package/dist/agent/production-agent.js.map +1 -1
  60. package/dist/collab/struct-routes.d.ts +1 -1
  61. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  62. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  63. package/dist/mcp/builtin-tools.js +22 -3
  64. package/dist/mcp/builtin-tools.js.map +1 -1
  65. package/dist/notifications/routes.d.ts +3 -3
  66. package/dist/observability/routes.d.ts +5 -5
  67. package/dist/resources/handlers.d.ts +3 -3
  68. package/dist/scripts/call-agent.d.ts +1 -1
  69. package/dist/scripts/call-agent.d.ts.map +1 -1
  70. package/dist/scripts/call-agent.js +19 -1
  71. package/dist/scripts/call-agent.js.map +1 -1
  72. package/dist/secrets/routes.d.ts +9 -9
  73. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  74. package/dist/server/agent-chat-plugin.js +71 -2
  75. package/dist/server/agent-chat-plugin.js.map +1 -1
  76. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  77. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
  78. package/package.json +1 -1
  79. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
@@ -2729,6 +2729,8 @@ const editorSlashMessages = {
2729
2729
  audio: "Audio",
2730
2730
  audioDescription: "Upload or embed audio",
2731
2731
  basicBlocks: "Basic blocks",
2732
+ blockEquation: "Block equation",
2733
+ blockEquationDescription: "Display a LaTeX equation on its own line",
2732
2734
  blocks: "Blocks",
2733
2735
  bulletedList: "Bulleted list",
2734
2736
  bulletedListDescription: "Unordered list",
@@ -2739,6 +2741,7 @@ const editorSlashMessages = {
2739
2741
  collapsibleBlockDescription: "Collapsible block",
2740
2742
  database: "Database",
2741
2743
  databaseDescription: "Inline database in this page",
2744
+ cancelEquation: "Cancel",
2742
2745
  divider: "Divider",
2743
2746
  dividerDescription: "Horizontal rule",
2744
2747
  generate: "Generate",
@@ -2749,6 +2752,9 @@ const editorSlashMessages = {
2749
2752
  heading4Description: "Subheading",
2750
2753
  image: "Image",
2751
2754
  imageDescription: "Upload or embed image",
2755
+ inlineEquation: "Inline equation",
2756
+ inlineEquationDescription: "Add a LaTeX equation within text",
2757
+ insertEquation: "Insert",
2752
2758
  localComponents: "Local components",
2753
2759
  media: "Media",
2754
2760
  numberedList: "Numbered list",
@@ -2756,6 +2762,14 @@ const editorSlashMessages = {
2756
2762
  page: "Page",
2757
2763
  pageDescription: "Create a child page",
2758
2764
  pages: "Pages",
2765
+ equationInputLabel: "LaTeX equation",
2766
+ equationInsertFailed: "Could not insert the equation.",
2767
+ equationNeedsRepair: "Fix the LaTeX to see a preview.",
2768
+ equationPlaceholder: String.raw`Type LaTeX, for example \frac{a}{b}`,
2769
+ equationPreview: "Preview",
2770
+ equationPreviewEmpty: "Your equation will appear here.",
2771
+ equationSubmitHint:
2772
+ "Preview updates as you type. Press Cmd/Ctrl+Enter to insert.",
2759
2773
  quote: "Quote",
2760
2774
  quoteDescription: "Block quote",
2761
2775
  table: "Table",
@@ -10293,6 +10307,21 @@ const contentExactEnglishTranslations = {
10293
10307
  "zh-CN": {
10294
10308
  editor: {
10295
10309
  failedToCreatePage: "创建页面失败",
10310
+ slash: {
10311
+ blockEquation: "块级公式",
10312
+ blockEquationDescription: "在单独一行显示 LaTeX 公式",
10313
+ cancelEquation: "取消",
10314
+ inlineEquation: "行内公式",
10315
+ inlineEquationDescription: "在文本中添加 LaTeX 公式",
10316
+ insertEquation: "插入",
10317
+ equationInputLabel: "LaTeX 公式",
10318
+ equationInsertFailed: "无法插入公式。",
10319
+ equationNeedsRepair: "请修正 LaTeX 以查看预览。",
10320
+ equationPlaceholder: String.raw`输入 LaTeX,例如 \frac{a}{b}`,
10321
+ equationPreview: "预览",
10322
+ equationPreviewEmpty: "公式将在此处显示。",
10323
+ equationSubmitHint: "输入时会实时更新预览。按 Cmd/Ctrl+Enter 插入。",
10324
+ },
10296
10325
  toolbar: {
10297
10326
  copiedPageLink: "已复制页面链接",
10298
10327
  copyPageLink: "复制页面链接",
@@ -10306,6 +10335,23 @@ const contentExactEnglishTranslations = {
10306
10335
  "es-ES": {
10307
10336
  editor: {
10308
10337
  failedToCreatePage: "No se pudo crear la página",
10338
+ slash: {
10339
+ blockEquation: "Ecuación en bloque",
10340
+ blockEquationDescription:
10341
+ "Muestra una ecuación LaTeX en su propia línea",
10342
+ cancelEquation: "Cancelar",
10343
+ inlineEquation: "Ecuación en línea",
10344
+ inlineEquationDescription: "Añade una ecuación LaTeX dentro del texto",
10345
+ insertEquation: "Insertar",
10346
+ equationInputLabel: "Ecuación LaTeX",
10347
+ equationInsertFailed: "No se pudo insertar la ecuación.",
10348
+ equationNeedsRepair: "Corrige el LaTeX para ver la vista previa.",
10349
+ equationPlaceholder: String.raw`Escribe LaTeX, por ejemplo \frac{a}{b}`,
10350
+ equationPreview: "Vista previa",
10351
+ equationPreviewEmpty: "Tu ecuación aparecerá aquí.",
10352
+ equationSubmitHint:
10353
+ "La vista previa se actualiza mientras escribes. Pulsa Cmd/Ctrl+Enter para insertar.",
10354
+ },
10309
10355
  toolbar: {
10310
10356
  copiedPageLink: "Enlace de página copiado",
10311
10357
  copyPageLink: "Copiar enlace de página",
@@ -10320,6 +10366,23 @@ const contentExactEnglishTranslations = {
10320
10366
  "fr-FR": {
10321
10367
  editor: {
10322
10368
  failedToCreatePage: "Impossible de créer la page",
10369
+ slash: {
10370
+ blockEquation: "Équation en bloc",
10371
+ blockEquationDescription:
10372
+ "Afficher une équation LaTeX sur sa propre ligne",
10373
+ cancelEquation: "Annuler",
10374
+ inlineEquation: "Équation en ligne",
10375
+ inlineEquationDescription: "Ajouter une équation LaTeX dans le texte",
10376
+ insertEquation: "Insérer",
10377
+ equationInputLabel: "Équation LaTeX",
10378
+ equationInsertFailed: "Impossible d’insérer l’équation.",
10379
+ equationNeedsRepair: "Corrigez le LaTeX pour afficher un aperçu.",
10380
+ equationPlaceholder: String.raw`Saisissez du LaTeX, par exemple \frac{a}{b}`,
10381
+ equationPreview: "Aperçu",
10382
+ equationPreviewEmpty: "Votre équation apparaîtra ici.",
10383
+ equationSubmitHint:
10384
+ "L’aperçu se met à jour pendant la saisie. Appuyez sur Cmd/Ctrl+Entrée pour insérer.",
10385
+ },
10323
10386
  toolbar: {
10324
10387
  copiedPageLink: "Lien de la page copié",
10325
10388
  copyPageLink: "Copier le lien de la page",
@@ -10334,6 +10397,24 @@ const contentExactEnglishTranslations = {
10334
10397
  "de-DE": {
10335
10398
  editor: {
10336
10399
  failedToCreatePage: "Seite konnte nicht erstellt werden",
10400
+ slash: {
10401
+ blockEquation: "Blockgleichung",
10402
+ blockEquationDescription:
10403
+ "Eine LaTeX-Gleichung in einer eigenen Zeile anzeigen",
10404
+ cancelEquation: "Abbrechen",
10405
+ inlineEquation: "Inline-Gleichung",
10406
+ inlineEquationDescription: "Eine LaTeX-Gleichung in Text einfügen",
10407
+ insertEquation: "Einfügen",
10408
+ equationInputLabel: "LaTeX-Gleichung",
10409
+ equationInsertFailed: "Die Gleichung konnte nicht eingefügt werden.",
10410
+ equationNeedsRepair:
10411
+ "Korrigieren Sie das LaTeX, um eine Vorschau zu sehen.",
10412
+ equationPlaceholder: String.raw`LaTeX eingeben, zum Beispiel \frac{a}{b}`,
10413
+ equationPreview: "Vorschau",
10414
+ equationPreviewEmpty: "Ihre Gleichung wird hier angezeigt.",
10415
+ equationSubmitHint:
10416
+ "Die Vorschau wird während der Eingabe aktualisiert. Mit Cmd/Ctrl+Enter einfügen.",
10417
+ },
10337
10418
  toolbar: {
10338
10419
  copiedPageLink: "Seitenlink kopiert",
10339
10420
  copyPageLink: "Seitenlink kopieren",
@@ -10348,6 +10429,23 @@ const contentExactEnglishTranslations = {
10348
10429
  "ja-JP": {
10349
10430
  editor: {
10350
10431
  failedToCreatePage: "ページを作成できませんでした",
10432
+ slash: {
10433
+ blockEquation: "ブロック数式",
10434
+ blockEquationDescription: "LaTeX 数式を独立した行に表示します",
10435
+ cancelEquation: "キャンセル",
10436
+ inlineEquation: "インライン数式",
10437
+ inlineEquationDescription: "テキスト内に LaTeX 数式を追加します",
10438
+ insertEquation: "挿入",
10439
+ equationInputLabel: "LaTeX 数式",
10440
+ equationInsertFailed: "数式を挿入できませんでした。",
10441
+ equationNeedsRepair:
10442
+ "プレビューを表示するには LaTeX を修正してください。",
10443
+ equationPlaceholder: String.raw`LaTeX を入力(例:\frac{a}{b})`,
10444
+ equationPreview: "プレビュー",
10445
+ equationPreviewEmpty: "ここに数式が表示されます。",
10446
+ equationSubmitHint:
10447
+ "入力中にプレビューが更新されます。Cmd/Ctrl+Enter で挿入します。",
10448
+ },
10351
10449
  toolbar: {
10352
10450
  copiedPageLink: "ページリンクをコピーしました",
10353
10451
  copyPageLink: "ページリンクをコピー",
@@ -10362,6 +10460,22 @@ const contentExactEnglishTranslations = {
10362
10460
  "ko-KR": {
10363
10461
  editor: {
10364
10462
  failedToCreatePage: "페이지를 만들지 못했습니다",
10463
+ slash: {
10464
+ blockEquation: "블록 수식",
10465
+ blockEquationDescription: "LaTeX 수식을 별도 줄에 표시합니다",
10466
+ cancelEquation: "취소",
10467
+ inlineEquation: "인라인 수식",
10468
+ inlineEquationDescription: "텍스트 안에 LaTeX 수식을 추가합니다",
10469
+ insertEquation: "삽입",
10470
+ equationInputLabel: "LaTeX 수식",
10471
+ equationInsertFailed: "수식을 삽입하지 못했습니다.",
10472
+ equationNeedsRepair: "미리 보려면 LaTeX를 수정하세요.",
10473
+ equationPlaceholder: String.raw`LaTeX를 입력하세요. 예: \frac{a}{b}`,
10474
+ equationPreview: "미리보기",
10475
+ equationPreviewEmpty: "여기에 수식이 표시됩니다.",
10476
+ equationSubmitHint:
10477
+ "입력하는 동안 미리보기가 업데이트됩니다. Cmd/Ctrl+Enter로 삽입하세요.",
10478
+ },
10365
10479
  toolbar: {
10366
10480
  copiedPageLink: "페이지 링크를 복사했습니다",
10367
10481
  copyPageLink: "페이지 링크 복사",
@@ -10376,6 +10490,23 @@ const contentExactEnglishTranslations = {
10376
10490
  "pt-BR": {
10377
10491
  editor: {
10378
10492
  failedToCreatePage: "Não foi possível criar a página",
10493
+ slash: {
10494
+ blockEquation: "Equação em bloco",
10495
+ blockEquationDescription:
10496
+ "Exiba uma equação LaTeX em uma linha própria",
10497
+ cancelEquation: "Cancelar",
10498
+ inlineEquation: "Equação em linha",
10499
+ inlineEquationDescription: "Adicione uma equação LaTeX dentro do texto",
10500
+ insertEquation: "Inserir",
10501
+ equationInputLabel: "Equação LaTeX",
10502
+ equationInsertFailed: "Não foi possível inserir a equação.",
10503
+ equationNeedsRepair: "Corrija o LaTeX para ver uma prévia.",
10504
+ equationPlaceholder: String.raw`Digite LaTeX, por exemplo \frac{a}{b}`,
10505
+ equationPreview: "Prévia",
10506
+ equationPreviewEmpty: "Sua equação aparecerá aqui.",
10507
+ equationSubmitHint:
10508
+ "A prévia é atualizada enquanto você digita. Pressione Cmd/Ctrl+Enter para inserir.",
10509
+ },
10379
10510
  toolbar: {
10380
10511
  copiedPageLink: "Link da página copiado",
10381
10512
  copyPageLink: "Copiar link da página",
@@ -10390,6 +10521,22 @@ const contentExactEnglishTranslations = {
10390
10521
  "hi-IN": {
10391
10522
  editor: {
10392
10523
  failedToCreatePage: "पेज नहीं बनाया जा सका",
10524
+ slash: {
10525
+ blockEquation: "ब्लॉक समीकरण",
10526
+ blockEquationDescription: "LaTeX समीकरण को अलग पंक्ति में दिखाएँ",
10527
+ cancelEquation: "रद्द करें",
10528
+ inlineEquation: "इनलाइन समीकरण",
10529
+ inlineEquationDescription: "टेक्स्ट के भीतर LaTeX समीकरण जोड़ें",
10530
+ insertEquation: "सम्मिलित करें",
10531
+ equationInputLabel: "LaTeX समीकरण",
10532
+ equationInsertFailed: "समीकरण सम्मिलित नहीं किया जा सका।",
10533
+ equationNeedsRepair: "पूर्वावलोकन देखने के लिए LaTeX ठीक करें।",
10534
+ equationPlaceholder: String.raw`LaTeX लिखें, जैसे \frac{a}{b}`,
10535
+ equationPreview: "पूर्वावलोकन",
10536
+ equationPreviewEmpty: "आपका समीकरण यहाँ दिखाई देगा।",
10537
+ equationSubmitHint:
10538
+ "लिखते समय पूर्वावलोकन अपडेट होता है। सम्मिलित करने के लिए Cmd/Ctrl+Enter दबाएँ।",
10539
+ },
10393
10540
  toolbar: {
10394
10541
  copiedPageLink: "पेज लिंक कॉपी किया गया",
10395
10542
  copyPageLink: "पेज लिंक कॉपी करें",
@@ -10403,6 +10550,22 @@ const contentExactEnglishTranslations = {
10403
10550
  "ar-SA": {
10404
10551
  editor: {
10405
10552
  failedToCreatePage: "تعذر إنشاء الصفحة",
10553
+ slash: {
10554
+ blockEquation: "معادلة مستقلة",
10555
+ blockEquationDescription: "اعرض معادلة LaTeX في سطر مستقل",
10556
+ cancelEquation: "إلغاء",
10557
+ inlineEquation: "معادلة ضمن السطر",
10558
+ inlineEquationDescription: "أضف معادلة LaTeX داخل النص",
10559
+ insertEquation: "إدراج",
10560
+ equationInputLabel: "معادلة LaTeX",
10561
+ equationInsertFailed: "تعذر إدراج المعادلة.",
10562
+ equationNeedsRepair: "صحح LaTeX لعرض المعاينة.",
10563
+ equationPlaceholder: String.raw`اكتب LaTeX، مثل \frac{a}{b}`,
10564
+ equationPreview: "معاينة",
10565
+ equationPreviewEmpty: "ستظهر معادلتك هنا.",
10566
+ equationSubmitHint:
10567
+ "تتحدث المعاينة أثناء الكتابة. اضغط Cmd/Ctrl+Enter للإدراج.",
10568
+ },
10406
10569
  toolbar: {
10407
10570
  copiedPageLink: "تم نسخ رابط الصفحة",
10408
10571
  copyPageLink: "نسخ رابط الصفحة",
@@ -62,6 +62,7 @@ import {
62
62
  } from "./lib/content-command-search";
63
63
 
64
64
  import stylesheet from "./global.css?url";
65
+ import katexStylesheet from "katex/dist/katex.min.css?url";
65
66
  configureTracking({
66
67
  getDefaultProps: (_name, properties) => ({
67
68
  ...properties,
@@ -71,6 +72,7 @@ configureTracking({
71
72
 
72
73
  export const links: LinksFunction = () => [
73
74
  { rel: "stylesheet", href: stylesheet },
75
+ { rel: "stylesheet", href: katexStylesheet },
74
76
  ];
75
77
 
76
78
  interface RootLoaderData {
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-07-14
4
+ ---
5
+
6
+ Create inline or block LaTeX equations from the slash menu or by typing `$…$`, with live validation and preview; existing GitHub-style equation syntax remains compatible, and equations render on public pages and in exports.
@@ -55,6 +55,7 @@
55
55
  "h3": "catalog:",
56
56
  "highlight.js": "^11.11.1",
57
57
  "isbot": "^5",
58
+ "katex": "0.17.0",
58
59
  "lowlight": "^3.3.0",
59
60
  "prosemirror-markdown": "^1.13.4",
60
61
  "remark-mdx": "^3.1.1",
@@ -1,3 +1,6 @@
1
+ import { matchInlineMathAt } from "./inline-math.js";
2
+ import { KATEX_STYLESHEET_URL, renderMathToHtml } from "./math-rendering.js";
3
+
1
4
  export type DocumentExportFormat = "pdf" | "markdown" | "html";
2
5
 
3
6
  export interface DocumentExportInput {
@@ -100,18 +103,37 @@ export function markdownWithTitle(
100
103
  return `${`# ${safeTitle}`}${body ? `\n\n${body}` : ""}\n`;
101
104
  }
102
105
 
103
- function inlineMarkdownToHtml(text: string): string {
106
+ interface InlineExportToken {
107
+ marker: string;
108
+ html: string;
109
+ source: string;
110
+ }
111
+
112
+ function restoreTokenSources(
113
+ text: string,
114
+ tokens: InlineExportToken[],
115
+ ): string {
116
+ return tokens.reduce(
117
+ (restored, token) =>
118
+ restored.split(token.marker).join(escapeHtml(token.source)),
119
+ text,
120
+ );
121
+ }
122
+
123
+ function inlineMarkdownSegmentToHtml(
124
+ text: string,
125
+ tokens: InlineExportToken[],
126
+ ): string {
104
127
  return escapeHtml(text)
105
128
  .replace(
106
129
  /!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^&]*")?\)/g,
107
130
  (_m, alt, src) => {
108
- return `<img src="${safeExportUrl(src, "image")}" alt="${alt}" />`;
131
+ return `<img src="${safeExportUrl(src, "image")}" alt="${restoreTokenSources(alt, tokens)}" />`;
109
132
  },
110
133
  )
111
134
  .replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_m, label, href) => {
112
135
  return `<a href="${safeExportUrl(href, "link")}">${label}</a>`;
113
136
  })
114
- .replace(/`([^`]+)`/g, "<code>$1</code>")
115
137
  .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
116
138
  .replace(/__([^_]+)__/g, "<strong>$1</strong>")
117
139
  .replace(/~~([^~]+)~~/g, "<s>$1</s>")
@@ -119,24 +141,229 @@ function inlineMarkdownToHtml(text: string): string {
119
141
  .replace(/(^|[^_])_([^_]+)_/g, "$1<em>$2</em>");
120
142
  }
121
143
 
122
- function listItemsToHtml(lines: string[], ordered: boolean): string {
123
- const items = lines
124
- .map((line) => {
125
- const text = ordered
126
- ? line.replace(/^\s*\d+[.)]\s+/, "")
127
- : line.replace(/^\s*[-*+]\s+/, "");
128
- const task = text.match(/^\[( |x|X)\]\s+(.*)$/);
144
+ function isEscapedDelimiter(text: string, index: number): boolean {
145
+ let backslashes = 0;
146
+ for (let cursor = index - 1; cursor >= 0 && text[cursor] === "\\"; cursor--) {
147
+ backslashes++;
148
+ }
149
+ return backslashes % 2 === 1;
150
+ }
151
+
152
+ function mathErrorHtml(
153
+ source: string,
154
+ error: string,
155
+ displayMode: boolean,
156
+ ): string {
157
+ const className = displayMode
158
+ ? "math-error math-error-block"
159
+ : "math-error math-error-inline";
160
+ const tagName = displayMode ? "pre" : "code";
161
+ return `<${tagName} class="${className}" title="${escapeHtml(error)}">${escapeHtml(source)}</${tagName}>`;
162
+ }
163
+
164
+ function inlineMarkdownToHtml(text: string): string {
165
+ const tokens: InlineExportToken[] = [];
166
+ let markerStart = "\uE000agent-native-inline-";
167
+ while (text.includes(markerStart)) markerStart += "-";
168
+
169
+ const protectedText: string[] = [];
170
+ let cursor = 0;
171
+
172
+ while (cursor < text.length) {
173
+ if (text.startsWith("$`", cursor) && isEscapedDelimiter(text, cursor)) {
174
+ const close = text.indexOf("`$", cursor + 2);
175
+ if (close !== -1) {
176
+ protectedText.push(text.slice(cursor, close + 2));
177
+ cursor = close + 2;
178
+ continue;
179
+ }
180
+ }
181
+
182
+ if (text[cursor] === "$") {
183
+ const inlineMath = matchInlineMathAt(text, cursor);
184
+ if (inlineMath) {
185
+ const { latex } = inlineMath;
186
+ const source = text.slice(cursor, inlineMath.to);
187
+ const math = renderMathToHtml(latex, false);
188
+ const marker = `${markerStart}${tokens.length}\uE001`;
189
+ tokens.push({
190
+ marker,
191
+ source,
192
+ html: math.ok
193
+ ? `<span class="math-inline">${math.html}</span>`
194
+ : mathErrorHtml(source, math.error, false),
195
+ });
196
+ protectedText.push(marker);
197
+ cursor = inlineMath.to;
198
+ continue;
199
+ }
200
+ }
201
+
202
+ if (text[cursor] === "`" && !isEscapedDelimiter(text, cursor)) {
203
+ let delimiterLength = 1;
204
+ while (text[cursor + delimiterLength] === "`") delimiterLength++;
205
+ const delimiter = "`".repeat(delimiterLength);
206
+ let close = text.indexOf(delimiter, cursor + delimiterLength);
207
+ while (
208
+ close !== -1 &&
209
+ (text[close - 1] === "`" || text[close + delimiterLength] === "`")
210
+ ) {
211
+ close = text.indexOf(delimiter, close + delimiterLength);
212
+ }
213
+
214
+ if (close !== -1) {
215
+ const source = text.slice(cursor, close + delimiterLength);
216
+ let code = text
217
+ .slice(cursor + delimiterLength, close)
218
+ .replace(/\r?\n/g, " ");
219
+ if (
220
+ code.startsWith(" ") &&
221
+ code.endsWith(" ") &&
222
+ code.trim().length > 0
223
+ ) {
224
+ code = code.slice(1, -1);
225
+ }
226
+ const marker = `${markerStart}${tokens.length}\uE001`;
227
+ tokens.push({
228
+ marker,
229
+ source,
230
+ html: `<code>${escapeHtml(code)}</code>`,
231
+ });
232
+ protectedText.push(marker);
233
+ cursor = close + delimiterLength;
234
+ continue;
235
+ }
236
+ }
237
+
238
+ protectedText.push(text[cursor]);
239
+ cursor++;
240
+ }
241
+
242
+ return tokens.reduce(
243
+ (html, token) => html.split(token.marker).join(token.html),
244
+ inlineMarkdownSegmentToHtml(protectedText.join(""), tokens),
245
+ );
246
+ }
247
+
248
+ function listItemsToHtml(items: string[][], ordered: boolean): string {
249
+ const renderedItems = items
250
+ .map((lines) => {
251
+ const task = lines[0].match(/^\[( |x|X)\]\s+(.*)$/);
129
252
  if (task) {
130
253
  const checked = task[1].toLowerCase() === "x";
131
254
  return `<li class="task"><input type="checkbox" disabled${
132
255
  checked ? " checked" : ""
133
- } /> <span>${inlineMarkdownToHtml(task[2])}</span></li>`;
256
+ } /> <span>${inlineMarkdownToHtml(task[2])}</span>${
257
+ lines.length > 1 ? markdownToHtml(lines.slice(1).join("\n")) : ""
258
+ }</li>`;
134
259
  }
135
- return `<li>${inlineMarkdownToHtml(text)}</li>`;
260
+ return lines.length === 1
261
+ ? `<li>${inlineMarkdownToHtml(lines[0])}</li>`
262
+ : `<li>${markdownToHtml(lines.join("\n"))}</li>`;
136
263
  })
137
264
  .join("\n");
138
265
 
139
- return ordered ? `<ol>\n${items}\n</ol>` : `<ul>\n${items}\n</ul>`;
266
+ return ordered
267
+ ? `<ol>\n${renderedItems}\n</ol>`
268
+ : `<ul>\n${renderedItems}\n</ul>`;
269
+ }
270
+
271
+ interface ListMarkerMatch {
272
+ baseIndent: number;
273
+ contentIndent: number;
274
+ content: string;
275
+ }
276
+
277
+ function indentationWidth(value: string): number {
278
+ let width = 0;
279
+ for (const character of value) {
280
+ width += character === "\t" ? 4 - (width % 4) : 1;
281
+ }
282
+ return width;
283
+ }
284
+
285
+ function matchListMarker(
286
+ line: string,
287
+ ordered: boolean,
288
+ ): ListMarkerMatch | null {
289
+ const match = line.match(
290
+ ordered
291
+ ? /^([ \t]*)(?:\d+[.)])([ \t]+)(.*)$/
292
+ : /^([ \t]*)(?:[-*+])([ \t]+)(.*)$/,
293
+ );
294
+ if (!match) return null;
295
+ const contentStart = match[0].length - match[3].length;
296
+ return {
297
+ baseIndent: indentationWidth(match[1]),
298
+ contentIndent: indentationWidth(line.slice(0, contentStart)),
299
+ content: match[3],
300
+ };
301
+ }
302
+
303
+ function leadingIndentWidth(line: string): number {
304
+ return indentationWidth(line.match(/^[ \t]*/)?.[0] ?? "");
305
+ }
306
+
307
+ function stripLeadingIndent(line: string, targetWidth: number): string {
308
+ let width = 0;
309
+ let index = 0;
310
+ while (index < line.length && width < targetWidth) {
311
+ const character = line[index];
312
+ if (character !== " " && character !== "\t") break;
313
+ width += character === "\t" ? 4 - (width % 4) : 1;
314
+ index++;
315
+ }
316
+ return line.slice(index);
317
+ }
318
+
319
+ function collectListItems(
320
+ lines: string[],
321
+ start: number,
322
+ ordered: boolean,
323
+ ): { items: string[][]; nextIndex: number } {
324
+ const firstMarker = matchListMarker(lines[start], ordered);
325
+ if (!firstMarker) return { items: [], nextIndex: start };
326
+
327
+ const baseIndent = firstMarker.baseIndent;
328
+ const items: string[][] = [];
329
+ let index = start;
330
+
331
+ while (index < lines.length) {
332
+ const itemMarker = matchListMarker(lines[index], ordered);
333
+ if (!itemMarker || itemMarker.baseIndent !== baseIndent) break;
334
+
335
+ const item = [itemMarker.content];
336
+ index++;
337
+
338
+ while (index < lines.length) {
339
+ const nextMarker = matchListMarker(lines[index], ordered);
340
+ if (nextMarker?.baseIndent === baseIndent) break;
341
+ if (leadingIndentWidth(lines[index]) >= itemMarker.contentIndent) {
342
+ item.push(stripLeadingIndent(lines[index], itemMarker.contentIndent));
343
+ index++;
344
+ continue;
345
+ }
346
+ if (!lines[index].trim()) {
347
+ let nextContent = index + 1;
348
+ while (nextContent < lines.length && !lines[nextContent].trim()) {
349
+ nextContent++;
350
+ }
351
+ if (
352
+ nextContent < lines.length &&
353
+ leadingIndentWidth(lines[nextContent]) >= itemMarker.contentIndent
354
+ ) {
355
+ item.push("");
356
+ index++;
357
+ continue;
358
+ }
359
+ }
360
+ break;
361
+ }
362
+
363
+ items.push(item);
364
+ }
365
+
366
+ return { items, nextIndex: index };
140
367
  }
141
368
 
142
369
  function isEmptyBlockLine(trimmed: string): boolean {
@@ -163,6 +390,42 @@ function markdownToHtml(markdown: string): string {
163
390
  continue;
164
391
  }
165
392
 
393
+ if (trimmed === "$$") {
394
+ const source = [line];
395
+ const latex: string[] = [];
396
+ index++;
397
+ while (index < lines.length && lines[index].trim() !== "$$") {
398
+ source.push(lines[index]);
399
+ latex.push(lines[index]);
400
+ index++;
401
+ }
402
+
403
+ const closed = index < lines.length;
404
+ if (closed) {
405
+ source.push(lines[index]);
406
+ index++;
407
+ }
408
+
409
+ if (!closed) {
410
+ blocks.push(
411
+ mathErrorHtml(
412
+ source.join("\n"),
413
+ "This block equation is missing its closing delimiter.",
414
+ true,
415
+ ),
416
+ );
417
+ continue;
418
+ }
419
+
420
+ const math = renderMathToHtml(latex.join("\n"), true);
421
+ blocks.push(
422
+ math.ok
423
+ ? `<div class="math-block">${math.html}</div>`
424
+ : mathErrorHtml(source.join("\n"), math.error, true),
425
+ );
426
+ continue;
427
+ }
428
+
166
429
  const codeFence = trimmed.match(/^```(\w+)?/);
167
430
  if (codeFence) {
168
431
  const code: string[] = [];
@@ -208,22 +471,16 @@ function markdownToHtml(markdown: string): string {
208
471
  }
209
472
 
210
473
  if (/^\s*[-*+]\s+/.test(line)) {
211
- const items: string[] = [];
212
- while (index < lines.length && /^\s*[-*+]\s+/.test(lines[index])) {
213
- items.push(lines[index]);
214
- index++;
215
- }
216
- blocks.push(listItemsToHtml(items, false));
474
+ const list = collectListItems(lines, index, false);
475
+ index = list.nextIndex;
476
+ blocks.push(listItemsToHtml(list.items, false));
217
477
  continue;
218
478
  }
219
479
 
220
480
  if (/^\s*\d+[.)]\s+/.test(line)) {
221
- const items: string[] = [];
222
- while (index < lines.length && /^\s*\d+[.)]\s+/.test(lines[index])) {
223
- items.push(lines[index]);
224
- index++;
225
- }
226
- blocks.push(listItemsToHtml(items, true));
481
+ const list = collectListItems(lines, index, true);
482
+ index = list.nextIndex;
483
+ blocks.push(listItemsToHtml(list.items, true));
227
484
  continue;
228
485
  }
229
486
 
@@ -233,6 +490,7 @@ function markdownToHtml(markdown: string): string {
233
490
  index < lines.length &&
234
491
  lines[index].trim() &&
235
492
  !isEmptyBlockLine(lines[index].trim()) &&
493
+ lines[index].trim() !== "$$" &&
236
494
  !/^(#{1,6})\s+/.test(lines[index].trim()) &&
237
495
  !/^```/.test(lines[index].trim()) &&
238
496
  !/^>\s?/.test(lines[index].trim()) &&
@@ -273,6 +531,7 @@ function buildHtmlDocument(input: {
273
531
  <meta charset="utf-8" />
274
532
  <meta name="viewport" content="width=device-width, initial-scale=1" />
275
533
  <title>${escapeHtml(input.title)}</title>
534
+ <link rel="stylesheet" href="${KATEX_STYLESHEET_URL}" crossorigin="anonymous" />
276
535
  <style>
277
536
  :root { color-scheme: light; }
278
537
  body {
@@ -329,6 +588,18 @@ function buildHtmlDocument(input: {
329
588
  border-radius: 4px;
330
589
  padding: 0.12rem 0.3rem;
331
590
  }
591
+ .math-inline { display: inline-block; max-width: 100%; vertical-align: -0.08em; }
592
+ .math-block { max-width: 100%; margin: 18px 0; overflow-x: auto; padding: 4px 0; }
593
+ .math-error {
594
+ border: 1px solid #fecaca;
595
+ border-radius: 6px;
596
+ background: #fef2f2;
597
+ color: #262626;
598
+ font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
599
+ white-space: pre-wrap;
600
+ }
601
+ .math-error-inline { padding: 0.05rem 0.25rem; font-size: 0.9em; }
602
+ .math-error-block { overflow-wrap: anywhere; padding: 12px 14px; }
332
603
  a { color: #2563eb; }
333
604
  img {
334
605
  display: block;