feat(coding-agent): sealed /share behind encrypted links and embedded subagent transcripts in exports

- Added `src/export/share.ts`: `/share` now snapshots the session JSON, gzips and seals it with a fresh AES-256-GCM key, and pushes the blob to a secret gist or the share server (1 MB cap with image/string/entry truncation via `sealToFit`); links are `<serverUrl>/<id>#<key>` with the key only in the fragment.
- Added `share-loader.js` and `scripts/generate-share-viewer.ts` building the static viewer the relay serves at `GET /s/<id>`: it fetches the sealed blob, decrypts in-browser, and hands the JSON to the export template via `window.__OMP_SESSION_DATA__`.
- Reworked the `/share` command in `command-controller.ts`/`builtin-registry.ts` off the plaintext-gist HTML upload, exported `LoadedCustomShare`, and exposed the session `SecretObfuscator` getter on `AgentSession` for redaction.
- Added `share.serverUrl` and `share.redactSecrets` settings backed by `DEFAULT_SHARE_URL` from pi-wire.
- HTML exports now embed subagent transcripts: `collectSubSessions` walks `<session>/<AgentId>.jsonl` recursively into `SessionData.subSessions`, with `includeSubSessions` opt-out and the exported `buildSessionData` reused by share snapshots.
- Added `share.test.ts` (snapshot/seal/server-url contracts) and `export-subsessions.test.ts`.
This commit is contained in:
can1357
2026-06-12 15:00:41 +02:00
parent 9d159ced17
commit 162c9ca422
12 changed files with 805 additions and 134 deletions
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bun
/**
* Build the standalone share-viewer page the omp relay serves at `GET /s/<id>`.
*
* Same template as HTML exports, but with no embedded session: share-loader.js
* (injected right after the empty #session-data tag) fetches the sealed blob
* (gist or relay store), decrypts it with the `#<key>` fragment in-browser, and
* hands the JSON to template.js via `window.__OMP_SESSION_DATA__`.
*
* The relay repo's build script runs this and embeds the output via go:embed.
*/
import * as path from "node:path";
import { generateThemeVars, getTemplate } from "../src/export/html";
const outPath = process.argv[2];
if (!outPath) {
console.error("usage: bun scripts/generate-share-viewer.ts <output.html>");
process.exit(2);
}
const loaderJs = await Bun.file(new URL("../src/export/html/share-loader.js", import.meta.url).pathname).text();
// Pin a built-in theme: the viewer is a public artifact, not a per-user export.
const themeVars = await generateThemeVars("dark");
const html = getTemplate()
.replace("<theme-vars/>", () => `<style>:root { ${themeVars} }</style>`)
.replace("<title>Session Export</title>", () => "<title>omp session</title>")
.replace("{{SESSION_DATA}}</script>", () => `</script>\n <script>${loaderJs}</script>`);
if (html.includes("{{SESSION_DATA}}")) throw new Error("session-data placeholder survived substitution");
if (!html.includes("__OMP_SESSION_DATA__")) throw new Error("share loader not injected");
await Bun.write(outPath, html);
console.log(`Generated ${path.resolve(outPath)} (${(html.length / 1024).toFixed(0)} KB)`);