mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 12:18:39 +00:00
Ouroboros advised a desktop-app owner to reload 'the browser tab' because the
runtime had no fact about the client UI: the launcher never exported the
retired OUROBOROS_DESKTOP_MODE flag, and messages carried no sending-surface
provenance at all.
Two additive facts, no device taxonomy (the model classifies raw observables):
- launcher.py exports OUROBOROS_PRESENTATION (desktop_window|browser_fallback;
absent=web) — the process posture, rendered as runtime_env.presentation.
- The SPA measures raw observables AT SEND TIME (pywebview bridge, ua,
viewport, matchMedia booleans, captured_at) and attaches client_surface to
each chat frame; the gateway normalizes it through the closed-key bounded
ouroboros/client_surface.py SSOT, stamps received_at, carries it in
task_metadata, persists it as an optional chat.jsonl column, and renders it
as the owner_client context fact. Non-web ingress gets a host-stamped
{channel: <source>} fallback; promotion/steering/mailbox carry it, and the
loop notes a mid-task surface change only when the surface IDENTITY differs
(viewport resize is not a device change), with a neutral note for the first
observed fact. SYSTEM.md documents the semantics and the pywebview product
facts (no Cmd+R, SHA auto-reload).
Adversarial waves 1-2 are folded in: Infinity viewport crash at ws ingress,
strict booleans, no-identity facts never mint change notes, provenance-honest
prompt wording, behavioral producer tests, send-site and received_at pins.
client_surface helpers live in their own module (message_bus/loop/chat.js stay
inside their ratchet sizes).
24 lines
1.1 KiB
JavaScript
24 lines
1.1 KiB
JavaScript
/** Owner Surface Fact: raw sending-surface observables, measured AT SEND TIME
|
|
* (the pywebview JS bridge appears asynchronously after page load, so a
|
|
* load-time snapshot could misclassify the desktop shell as a browser tab).
|
|
* Deliberately no device taxonomy — the model classifies from raw facts. */
|
|
/** Spread-ready chat-frame field: `{}` when the snapshot failed. */
|
|
export function clientSurfaceField() {
|
|
const snap = clientSurfaceSnapshot();
|
|
return snap ? { client_surface: snap } : {};
|
|
}
|
|
|
|
export function clientSurfaceSnapshot() {
|
|
try {
|
|
return {
|
|
pywebview: Boolean(window.pywebview?.api),
|
|
ua: String(navigator.userAgent || ''),
|
|
viewport: { w: Number(window.innerWidth) || 0, h: Number(window.innerHeight) || 0 },
|
|
narrow_layout: window.matchMedia('(max-width: 980px)').matches,
|
|
coarse_pointer: window.matchMedia('(pointer: coarse)').matches,
|
|
captured_at: new Date().toISOString(),
|
|
};
|
|
} catch {
|
|
return null; // absence is an honest gap; never a guessed default
|
|
}
|
|
}
|