feat: add project-aware agent workspace
Generic Container CI/CD / test-build-publish (push) Successful in 2m9s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 2m39s

This commit is contained in:
2026-10-22 18:52:18 +08:00
parent c6a0e74e72
commit 2d8506fea0
191 changed files with 12373 additions and 3556 deletions
+75
View File
@@ -0,0 +1,75 @@
import { Buffer } from "node:buffer";
import { readFile, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { cwd, stdout } from "node:process";
import { URL } from "node:url";
import { chromium } from "@playwright/test";
import { build } from "esbuild";
const require = createRequire(import.meta.url);
const riveRequire = createRequire(require.resolve("@rive-app/react-webgl2"));
const wasm = await readFile(riveRequire.resolve("@rive-app/webgl2/rive.wasm"));
const resource = await readFile(new URL("../public/agent/obsidian-water-v1.riv", import.meta.url));
const harness = `import { RuntimeLoader } from '@rive-app/react-webgl2';
RuntimeLoader.setWasmUrl('/runtime.wasm');
window.stills = new Promise(resolve => RuntimeLoader.getInstance(resolve)).then(async runtime => {
const bytes = new Uint8Array(await (await fetch('/resource.riv')).arrayBuffer());
const file = await runtime.load(bytes, undefined, false);
const canvas = document.querySelector('canvas');
const renderer = runtime.makeRenderer(canvas, true);
const images = {};
try {
for (const state of ['idle', 'asleep']) {
const artboard = file.defaultArtboard();
const machine = new runtime.StateMachineInstance(artboard.stateMachineByName('default'), artboard);
try {
for (let i = 0; i < machine.inputCount(); i++) {
const input = machine.input(i);
input.asBool().value = input.name === state;
}
for (let frame = 0; frame < (state === 'idle' ? 90 : 180); frame++) {
machine.advance(1 / 60);
artboard.advance(1 / 60);
}
renderer.clear();
renderer.save();
renderer.align(runtime.Fit.contain, runtime.Alignment.center,
{ minX: 0, minY: 0, maxX: 192, maxY: 192 }, artboard.bounds);
artboard.draw(renderer);
renderer.restore();
renderer.flush();
runtime.resolveAnimationFrame();
images[state] = canvas.toDataURL('image/png');
} finally { machine.delete(); artboard.delete(); }
}
return images;
} finally { renderer.delete(); file.delete(); }
});`;
const bundled = await build({
stdin: { contents: harness, resolveDir: cwd() },
bundle: true, write: false, format: "iife"
});
const browser = await chromium.launch({ args: ["--enable-unsafe-swiftshader"] });
try {
const page = await browser.newPage();
await page.route("http://persona-render.local/**", async (route) => {
const path = new URL(route.request().url()).pathname;
if (path.endsWith(".wasm")) return route.fulfill({ body: wasm, contentType: "application/wasm" });
if (path.endsWith(".riv")) return route.fulfill({ body: resource });
if (path.endsWith(".js")) return route.fulfill({ body: bundled.outputFiles[0].text, contentType: "application/javascript" });
return route.fulfill({
contentType: "text/html",
body: '<canvas width="192" height="192"></canvas><script src="/render.js"></script>'
});
});
await page.goto("http://persona-render.local/");
const images = await page.evaluate("window.stills");
for (const [state, image] of Object.entries(images)) {
const name = state === "asleep" ? "offline" : state;
const path = new URL(`../public/agent/obsidian-water-${name}.png`, import.meta.url);
await writeFile(path, Buffer.from(image.split(",")[1], "base64"));
}
stdout.write("Rendered current Rive material into online and ring-free offline fallback images.\n");
} finally {
await browser.close();
}