open-standards/wai/wai-app/web/session.js
Transaction Science 608cd843e9 wai-app: Create's crate, a library with no binary, whose export wai create writes
wai/wai-ui becomes wai/wai-app, the library `wai_app`, with no binary and no `cli`
feature: `wai`, built with its feature `app`, is its command line and its MCP
server. Its served files are named wai-app-* (the WebAssembly package wai_app),
the recordings wai-app-*.json.

- export.rs gives the files of a starter project for a program and the facts its
  project file holds (title, summary, `wai dev`'s mode for the app's room, a watch
  room's file, a show's audience); `wai create --from app.oui` writes them, the
  project file and the licence. The export no longer writes app.json, room.toml,
  relay.toml, package.json, check.mjs or serve.mjs: wai.json names the room, `wai
  dev` runs it and `wai app check` checks the project. Its README and AGENTS.md
  say so, and the README its licence.
- runtime.rs reads a Create runtime by its listing (create/wai-app-runtime.json),
  checking each file's SHA-256.
- bridge.rs keeps the bridge on 127.0.0.1 a coding agent streams a program into
  the page through, with a stream helper; the stdio server, its tools and the
  binary's test go (`wai mcp --toolset app` serves them).
- The page's Export hands over app.oui, the program in its canonical form, and the
  command that makes it a project; the ZIP writer and the page's export in
  WebAssembly go. The page names `wai mcp --toolset app` for the coding agent.
- joule-ui/docs/create.md §4 and §5: the export as `wai create --from app.oui`, and
  the agent's tools app_guide, app_check, app_stream and app_export.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 05:13:27 -04:00

58 lines
2.4 KiB
JavaScript

// The page's session: wai-app's WebAssembly build, the program as it streams, and the log
// every action a person takes on the page is sealed into (joule-ui SPEC §6, §7).
//
// `fire(action, inputs, handler)` is the interaction lifecycle around an asynchronous
// handler: authorized first (a denied action's handler never runs), the handler run and
// timed on the page's clock, then sealed as the next link of the log under the page's own
// key. A page has no energy meter: the figure is the estimator's, from the handler's wall
// time, and the log says so (`Estimator`).
import init, { Session, verifyLog, registry, guide, check } from "./pkg/wai_app.js";
export { verifyLog, registry, guide, check };
export async function openSession() {
await init();
const seed = crypto.getRandomValues(new Uint8Array(32));
const s = new Session(seed);
seed.fill(0);
return new PageSession(s);
}
export class PageSession {
constructor(wasm) {
this.w = wasm;
this.listeners = new Set();
this.actor = wasm.actor();
}
push(chunk) { return JSON.parse(this.w.push(chunk)); }
finish() { return JSON.parse(this.w.finish()); }
reset() { this.w.reset(); }
program() { return this.w.program(); }
canonical() { return this.w.canonical(); }
log() { return JSON.parse(this.w.log()); }
logRaw() { return this.w.logRaw(); }
verify() { return JSON.parse(this.w.verify()); }
totalUj() { return this.w.totalUj(); }
on(f) { this.listeners.add(f); return () => this.listeners.delete(f); }
note(entry) { for (const f of this.listeners) { try { f(entry); } catch { /* a view's own fault */ } } }
async fire(action, inputs, handler) {
const b = JSON.parse(this.w.beginAction(action, JSON.stringify(inputs === undefined ? null : inputs)));
if (b.sealed) {
this.note(b.sealed);
throw new Error(`${action} was denied: ${b.sealed.reason}`);
}
const t0 = performance.now();
let out;
let err = null;
try { out = await handler(); } catch (e) { err = e; }
const wall = performance.now() - t0;
const output = err ? { error: String((err && err.message) || err) } : out === undefined ? null : out;
let text;
try { text = JSON.stringify(output); } catch { text = JSON.stringify(String(output)); }
const entry = JSON.parse(this.w.finishAction(b.pending, text === undefined ? "null" : text, wall));
this.note(entry);
if (err) throw err;
return out;
}
}