diff --git a/biome.json b/biome.json
index 1fbc28920..f4361aeed 100644
--- a/biome.json
+++ b/biome.json
@@ -60,7 +60,6 @@
"!**/vendor/**/*",
"!**/node_modules/**/*",
"!**/test-sessions.ts",
- "!**/template.generated.ts",
"!**/docs-index.generated.ts",
"!**/agent_pb.ts",
"!.worktrees/**/*",
diff --git a/docs/porting-from-pi-mono.md b/docs/porting-from-pi-mono.md
index 23e4dfcd1..a7ac8dbab 100644
--- a/docs/porting-from-pi-mono.md
+++ b/docs/porting-from-pi-mono.md
@@ -100,7 +100,7 @@ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "myapp-"));
## 5) Prefer Bun embeds (no copying)
-Do not add new runtime asset copy steps. Keep assets in repo and prefer Bun embeds/imports; preserve existing explicit generation workflows such as `packages/coding-agent/src/export/html/template.generated.ts`.
+Do not add new runtime asset copy steps. Keep assets in repo and prefer Bun embeds/imports; preserve existing explicit generation workflows such as `packages/coding-agent/src/export/html/tool-views.generated.js` (built from collab-web sources via `bun run build-tool-views`).
- If upstream copies assets into a dist folder, replace with Bun-friendly embeds.
- Prompts are static `.md` files; use Bun text imports (`with { type: "text" }`) and Handlebars instead of inline prompt strings.
diff --git a/package.json b/package.json
index 451ef7ea3..c4117663e 100644
--- a/package.json
+++ b/package.json
@@ -157,13 +157,13 @@
"lint:py": "ruff check python && ruff format --check python",
"fix:py": "ruff check --fix python && ruff format python",
"prepublishOnly": "bun run check",
- "prepare": "bun --cwd=packages/coding-agent run generate-docs-index",
+ "prepare": "bun run generate-docs-index && bun run build-tool-views",
"publish": "bun run prepublishOnly && npm publish -ws --access public",
"publish:dry": "bun run prepublishOnly && npm publish -ws --access public --dry-run",
"release": "bun scripts/release.ts",
"generate-models": "bun --cwd=packages/catalog run generate-models",
"generate-docs-index": "bun --cwd=packages/coding-agent run generate-docs-index",
- "generate-template": "bun --cwd=packages/coding-agent run generate-template",
+ "build-tool-views": "bun --cwd=packages/collab-web run build:tool-views",
"check-spoofed-versions": "bun scripts/check-spoofed-versions.ts"
},
"devDependencies": {
diff --git a/packages/coding-agent/.gitignore b/packages/coding-agent/.gitignore
deleted file mode 100644
index a4a1c26c9..000000000
--- a/packages/coding-agent/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-src/core/export-html/template.generated.ts
diff --git a/packages/coding-agent/DEVELOPMENT.md b/packages/coding-agent/DEVELOPMENT.md
index 3f16ac804..caace8d82 100644
--- a/packages/coding-agent/DEVELOPMENT.md
+++ b/packages/coding-agent/DEVELOPMENT.md
@@ -1111,8 +1111,8 @@ Use only script names that exist in `packages/coding-agent/package.json`:
- `bun --cwd=packages/coding-agent run format-prompts`
- Regenerate docs index files for package docs:
- `bun --cwd=packages/coding-agent run generate-docs-index`
-- Regenerate template artifacts:
- - `bun --cwd=packages/coding-agent run generate-template`
+- Rebuild the embedded React tool renderers (after changing collab-web `src/tool-render/`):
+ - `bun --cwd=packages/collab-web run build:tool-views`
- Build compiled binary artifact (`dist/omp`):
- `bun --cwd=packages/coding-agent run build`
diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json
index 986c926f8..2cce3acd1 100644
--- a/packages/coding-agent/package.json
+++ b/packages/coding-agent/package.json
@@ -40,8 +40,7 @@
"fmt": "biome format --write . && bun run format-prompts",
"format-prompts": "bun scripts/format-prompts.ts",
"generate-docs-index": "bun scripts/generate-docs-index.ts",
- "prepack": "bun scripts/generate-docs-index.ts && bun scripts/bundle-dist.ts",
- "generate-template": "bun scripts/generate-template.ts",
+ "prepack": "bun scripts/generate-docs-index.ts && bun --cwd=../collab-web run build:tool-views && bun scripts/bundle-dist.ts",
"bench:guard": "bun scripts/bench-guard.ts"
},
"dependencies": {
diff --git a/packages/coding-agent/scripts/generate-template.ts b/packages/coding-agent/scripts/generate-template.ts
deleted file mode 100644
index 8d2ae3c4f..000000000
--- a/packages/coding-agent/scripts/generate-template.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env bun
-/**
- * Pre-compute the HTML template at publish time.
- * This avoids the Bun macro restriction in node_modules.
- */
-
-const dir = new URL("../src/export/html/", import.meta.url).pathname;
-
-// Read all files
-const html = await Bun.file(`${dir}template.html`).text();
-const css = await Bun.file(`${dir}template.css`).text();
-const js = await Bun.file(`${dir}template.js`).text();
-
-// Minify CSS
-const minifiedCss = css
- .replace(/\/\*[\s\S]*?\*\//g, "")
- .replace(/\s+/g, " ")
- .replace(/\s*([{}:;,])\s*/g, "$1")
- .trim();
-
-// Inline everything; use function replacements so `$'`, `$&`, `$$`, etc. inside
-// the embedded CSS/JS are not interpreted as substitution patterns.
-const template = html
- .replace("", () => ``)
- .replace("", () => ``);
-
-// Write generated file
-const output = `// Auto-generated by scripts/generate-template.ts - DO NOT EDIT
-export const TEMPLATE = ${JSON.stringify(template)};
-`;
-
-await Bun.write(`${dir}template.generated.ts`, output);
-console.log("Generated template.generated.ts");
diff --git a/packages/coding-agent/src/bun-imports.d.ts b/packages/coding-agent/src/bun-imports.d.ts
index f783792aa..90c3f7077 100644
--- a/packages/coding-agent/src/bun-imports.d.ts
+++ b/packages/coding-agent/src/bun-imports.d.ts
@@ -26,3 +26,20 @@ declare module "*.lark" {
const content: string;
export default content;
}
+
+// Session-export template assets imported as text.
+// No `*.html` declaration: bun-types claims that pattern as HTMLBundle, so the
+// text import in src/export/html/index.ts casts at the use site instead.
+declare module "*.css" {
+ const content: string;
+ export default content;
+}
+declare module "*/template.js" {
+ const content: string;
+ export default content;
+}
+
+declare module "*.generated.js" {
+ const content: string;
+ export default content;
+}
diff --git a/packages/coding-agent/src/export/html/index.ts b/packages/coding-agent/src/export/html/index.ts
index 5f1b010ff..b98d5e813 100644
--- a/packages/coding-agent/src/export/html/index.ts
+++ b/packages/coding-agent/src/export/html/index.ts
@@ -2,9 +2,39 @@ import * as path from "node:path";
import type { AgentState } from "@oh-my-pi/pi-agent-core";
import { APP_NAME, isEnoent } from "@oh-my-pi/pi-utils";
import { getResolvedThemeColors, getThemeExportColors } from "../../modes/theme/theme";
-import { type SessionEntry, type SessionHeader, SessionManager } from "../../session/session-manager";
-// Pre-generated template (created by scripts/generate-template.ts at publish time)
-import { TEMPLATE } from "./template.generated";
+import {
+ loadEntriesFromFile,
+ type SessionEntry,
+ type SessionHeader,
+ SessionManager,
+} from "../../session/session-manager";
+import templateCss from "./template.css" with { type: "text" };
+import templateHtml from "./template.html" with { type: "text" };
+import templateJs from "./template.js" with { type: "text" };
+// Pre-built React tool renderers: built by `bun --cwd=packages/collab-web run build:tool-views`,
+// run automatically by root `prepare` on install and by `prepack` at publish.
+import toolViewsJs from "./tool-views.generated.js" with { type: "text" };
+
+let cachedTemplate: string | undefined;
+
+/** Compose the standalone export template: minified CSS, tool renderers, and viewer JS inlined. */
+export function getTemplate(): string {
+ if (cachedTemplate) return cachedTemplate;
+ const minifiedCss = templateCss
+ .replace(/\/\*[\s\S]*?\*\//g, "")
+ .replace(/\s+/g, " ")
+ .replace(/\s*([{}:;,])\s*/g, "$1")
+ .trim();
+ // Function replacements so `$'`, `$&`, `$$`, etc. inside the embedded
+ // CSS/JS are not interpreted as substitution patterns. The cast is safe:
+ // `with { type: "text" }` yields a string at runtime; bun-types just types
+ // every *.html import as HTMLBundle (TS can't vary types by import attribute).
+ cachedTemplate = (templateHtml as unknown as string)
+ .replace("", () => ``)
+ .replace("", () => ``)
+ .replace("", () => ``);
+ return cachedTemplate;
+}
export interface ExportOptions {
outputPath?: string;
@@ -106,10 +136,9 @@ async function generateHtml(sessionData: SessionData, themeName?: string): Promi
// Use function replacements so `$'`, `$&`, `$$`, `$n`, etc. in the
// substituted CSS/base64 are not interpreted as substitution patterns
// (see https://mdn.io/String.replace).
- return TEMPLATE.replace("", () => ``).replace(
- "{{SESSION_DATA}}",
- () => sessionDataBase64,
- );
+ return getTemplate()
+ .replace("", () => ``)
+ .replace("{{SESSION_DATA}}", () => sessionDataBase64);
}
/** Export session to HTML using SessionManager and AgentState. */
diff --git a/packages/coding-agent/src/export/html/template.generated.ts b/packages/coding-agent/src/export/html/template.generated.ts
deleted file mode 100644
index 168182e87..000000000
--- a/packages/coding-agent/src/export/html/template.generated.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// Auto-generated by scripts/generate-template.ts - DO NOT EDIT
-export const TEMPLATE = "\n\n
\n \n \n Session Export\n \n \n\n\n \n \n \n \n \n
\n \n \n \n
\n

\n
\n
\n\n \n \n \n \n\n\n";
diff --git a/packages/coding-agent/src/export/html/template.macro.ts b/packages/coding-agent/src/export/html/template.macro.ts
deleted file mode 100644
index 26a532189..000000000
--- a/packages/coding-agent/src/export/html/template.macro.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Bun macro that inlines HTML template with CSS/JS at compile time.
- * This runs during `bun build` and embeds the result as a string.
- */
-export async function getTemplate(): Promise {
- const dir = new URL(".", import.meta.url).pathname;
-
- // Read all files
- const html = await Bun.file(`${dir}template.html`).text();
- const css = await Bun.file(`${dir}template.css`).text();
- const js = await Bun.file(`${dir}template.js`).text();
-
- // Minify CSS
- const minifiedCss = css
- .replace(/\/\*[\s\S]*?\*\//g, "")
- .replace(/\s+/g, " ")
- .replace(/\s*([{}:;,])\s*/g, "$1")
- .trim();
-
- // Inline everything; use function replacements so `$'`, `$&`, `$$`, etc.
- // inside the embedded CSS/JS are not interpreted as substitution patterns.
- return html
- .replace("", () => ``)
- .replace("", () => ``);
-}