Compare commits

..

1 Commits

Author SHA1 Message Date
vh 179a8dff6e feat(#17): web bind UI — plane selector + bound-state indicator (slice 3c UI)
Completes slice 3c: the browser-facing trigger for the web bind. A 'Bifrost
binding' <select> (none / memory / affect) on the setup panel; startSession
sends bifrost_plane in the create body (the consumer key stays server-held,
never sent from the browser). On a bound 201 the identity line renders the
bound-state indicator (plane + endpoint, never the key); bind failures surface
the error_code + bifrost_error in the setup error line.

Closes the persistent-memory caveat: web chat can now bind its own Tier-3
provider (memory persistence + affect telemetry), not persona+debug only.

Web suites 63 green (presentation-contract included); pure static HTML/JS.
2026-06-18 01:04:20 -07:00
3 changed files with 29 additions and 5 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "ratatoskr"
version = "0.17.12"
version = "0.17.13"
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
readme = "README.md"
requires-python = ">=3.12"
+27 -3
View File
@@ -510,6 +510,14 @@ body {
<div class="select-wrap">
<select id="agent-picker"><option>loading…</option></select>
</div>
<label class="field-label" for="bifrost-plane">Bifrost binding (Tier-3 provider)</label>
<div class="select-wrap">
<select id="bifrost-plane">
<option value="">none — observe only</option>
<option value="memory">memory (:8391) — durable recall</option>
<option value="affect">affect (:8390) — persona / PAD</option>
</select>
</div>
<button id="start-btn">open session</button>
<div class="setup-err" id="setup-err"></div>
</div>
@@ -734,19 +742,35 @@ async function startSession() {
try {
// end_user_id is server-configured (RATATOSKR_END_USER_ID) — not sent
// from the browser; the server ignores any end_user_id in this body.
// Issue #17: the browser selects only the PLANE; the consumer key + host
// are server-held (the key never reaches the browser).
const plane = $("bifrost-plane").value;
const reqBody = { agent_id: agentId };
if (plane) reqBody.bifrost_plane = plane;
const r = await fetch("/api/sessions", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ agent_id: agentId }),
body: JSON.stringify(reqBody),
});
if (r.status !== 201) {
$("setup-err").textContent = "create session failed: HTTP " + r.status;
let detail = "HTTP " + r.status;
try {
const err = await r.json();
if (err && err.error_code) {
detail = err.error_code + (err.bifrost_error ? " (" + err.bifrost_error + ")" : "");
}
} catch (_) { /* non-JSON body */ }
$("setup-err").textContent = "create session failed: " + detail;
$("start-btn").disabled = false;
return;
}
const info = await r.json();
state.sessionId = info.session_id;
// Issue #17 bound-state indicator: plane + endpoint (never the key).
const boundTag = info.bifrost
? ` · <span class="a">⇄ ${esc(info.bifrost.plane)}</span> ${esc(info.bifrost.endpoint)}`
: "";
$("identity").innerHTML =
`<span class="a">${esc(agentId)}</span> · …${esc(info.session_id.slice(-8))}`;
`<span class="a">${esc(agentId)}</span> · …${esc(info.session_id.slice(-8))}${boundTag}`;
setConn("idle", "connected");
$("setup").style.display = "none";
$("workspace").classList.add("live");
Generated
+1 -1
View File
@@ -1052,7 +1052,7 @@ wheels = [
[[package]]
name = "ratatoskr"
version = "0.17.12"
version = "0.17.13"
source = { editable = "." }
dependencies = [
{ name = "httpx" },