Commit Graph

29 Commits

Author SHA1 Message Date
Svrnty
3685710fe8 Mount Cortex OS Runtime Health as WebUI panel 2026-05-29 03:13:56 -04:00
Svrnty
8b6c810f4a Add Canvas command surface 2026-05-28 21:44:02 -04:00
Svrnty
cf723141a4 Implement S23 runtime health slice 2026-05-28 21:38:50 -04:00
Svrnty
c0ff59097c Embed umbrella graph in workspace panel
Some checks failed
plugin-tests / test (push) Failing after 5s
upstream-drift / drift (push) Failing after 5s
2026-05-26 06:34:50 -04:00
Svrnty
28ffa92f6f Improve umbrella graph visualization
Some checks failed
plugin-tests / test (push) Failing after 5s
2026-05-26 06:15:27 -04:00
Svrnty
4b1f2075ae Add Project Graph nav link
Some checks failed
plugin-tests / test (push) Failing after 5s
2026-05-26 05:26:15 -04:00
Svrnty
4ad595506a Validate umbrella graph WebUI proof
Some checks failed
plugin-tests / test (push) Failing after 5s
upstream-drift / drift (push) Failing after 5s
2026-05-25 12:57:39 -04:00
Svrnty
b75fbf48ae feat(bte panel): real SKU dropdown + contextual CMO chat handoff
Some checks failed
plugin-tests / test (push) Failing after 6s
upstream-drift / drift (push) Failing after 6s
Tranche C — BTE basics.

1. SKU dropdown now sources from Adwright's TopRecipes (top-selling
   offerings) via /api/adwright/last-panel-update?tool=adwright_list_recipes
   instead of bundled PLACEHOLDER_SKUS. Falls back to placeholders on
   any failure. state.skus drives both the toolbar select build and the
   name lookups in _openCmoChat / requestPhotoshoot. _refreshSkuOptions
   rewrites the <option> set in place so the dropdown updates without
   rebuilding the toolbar.

2. _openCmoChat now pre-fills the chat textarea with the operator's
   current BTE setup (brand, mode, media, recipe family, SKU, variants,
   selected asset) so they don't retype context every iteration.
   Switch + setTimeout(200) lets the chat panel mount before set value
   + dispatch input event. Doesn't auto-send — operator reviews then
   hits send themselves.

C3 (seed assets) closed without code: BTE backend at localhost:6001
already serves 10 real assets (1 evaluated with brandFit 4.2 ★3.8,
2 approved with hasImageData=true, 5 requested). Original "placeholder"
complaint was about the SKU dropdown, not the asset grid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:02:45 -04:00
Svrnty
01825190a3 feat(adwright panel): wire GetCycleMetrics into Overview KPIs
Frontend now drives Impressions / CTR / Spend from real per-variant
metrics (cycle_metric) rather than hardcoded zeros + fake +12% / -0.3%
deltas. Adds state.data.metricsByCycle keyed by cycle id; populated
via _fetchCycleMetrics which hits the panel-update endpoint directly
(no CMO chat dispatch needed — the route calls gRPC live per request).

After every cycles load (refresh-cycles or list-cycles ingest), fans
out one metrics fetch per cycle. _deriveKpis flattens all metrics
arrays, sums impressions/clicks/spend, computes real CTR from
clicks/impressions. Spend bar now reflects $spend / $budget_total
from real values instead of falling back to the $6,000 default floor.

Plugin route _real_payload_for gains a new branch:
adwright_get_cycle_metrics?cycle_id=N → core.get_cycle_metrics_tool(N).

Fake deltas dropped — real deltas need a previous-window snapshot
the backend doesn't supply yet.

Verified live: metricsByCycle.1 populated with backend's [{variant_id:1,
impressions:0, ...}] record. All metric values still zero in this
sandbox because no ads have dispatched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:57:10 -04:00
Svrnty
9ce6bfc08f fix(adwright + plugin): font 404s + status humanization + compound refresh + poll deadline
Bundles four Tranche-A polish fixes that ship together:

1. **Font 404s** — CSS @font-face URLs were /extensions/fonts/* but the
   plugin loader serves static under /plugins/svrnty/*. Updated paths
   to match register_static prefix.

2. **Humanized status enum** — _humanStatus() maps proto canonical strings
   (CYCLE_STATUS_PREVIEW_READY, VARIANT_STATUS_ACTIVE, etc.) to
   user-facing labels ("Preview ready", "Active"). Applied to Cycles
   row, Overview timeline, and variant detail.

3. **Compound Overview refresh** — refresh-overview now dispatches both
   refresh-cycles AND list-recipes via _COMPOUND_ACTIONS map. Without
   this, KPI Recipes stayed at 0 until user visited Targeting tab.
   Added per-action user-facing toast labels (_ACTION_LABELS) so the
   user sees "Loading recipes…" instead of just hermes-webui's raw
   "Queued: /adwright list-recipes" banner.

4. **Poll deadline race** — compound actions fired two sub-actions
   back-to-back; sub1's poll response was nulling pendingTool before
   sub2 could be ingested. _pollOnce now (a) resets deadline on each
   successful update (sign of progress) and (b) only stops polling
   when the response matches the CURRENT pendingTool (not the captured
   local var from earlier in the recursive fire).

Verified live: Overview now shows Cycles=1, Recipes=10, Spend=$0/$200,
timeline=Cycle #1 — Preview ready. Console clean (0 errors, was 3
font 404s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:47:56 -04:00
Svrnty
91c134c309 fix(adwright): read budget_total from backend (was c.budget, undefined)
Some checks failed
plugin-tests / test (push) Failing after 5s
Backend AdwrightCore.list_cycles preserves proto snake_case via
MessageToDict(preserving_proto_field_name=True), so cycle JSON carries
`budget_total` (decimal string like "200.00"), not `budget`. Frontend
read c.budget which was always undefined → fell back to 0 in both the
Cycles row meta and the Overview spend bar (which then defaulted to
$6,000 budget per the `|| 6000` floor in _deriveKpis).

Added _cycleBudget + _cycleSpend helpers that parseFloat the decimal
strings, with c.budget fallback so any future renamed field still works.
Spend stays 0 until GetCycleMetrics is wired in — it doesn't live in
ListCycles.

Verified live: Cycles row now shows $0 / $200 (was $0 / $0). Overview
spend now shows $0 / $200 (was $0 / $6,000).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:24:56 -04:00
Svrnty
6c88bf8899 fix(adwright): route ↗BTE crosslink through canonical switchPanel
Some checks failed
plugin-tests / test (push) Failing after 6s
The Adwright ↗BTE button called SvrntyBTE.open() directly, which sets
overlay.hidden=false but never flips main.svrnty-showing-bte. The CSS
gates BTE overlay visibility on that class, so the click did nothing
visible — overlay was mounted but display:none. Route the click through
window.switchPanel("bte") instead so the canonical nav wrapper handles
class flipping + event dispatch identically to the sidebar BTE button.

Validated via Playwright with 6 gates: V1 sidebar BTE (1 event, class
set, visible), V2 ↗BTE crosslink (1 event, class flip, visible),
V3 Adwright nav (class flip, BTE hidden), V4 main classes correct,
V5 console clean, V6 /api/bte/proxy 200 OK. All pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:05:41 -04:00
Svrnty
db1d4e0cdb fix(adwright): distinguish null vs [] in cycles/audience/connections empty-states
Some checks failed
plugin-tests / test (push) Failing after 6s
Empty-state messages now differentiate "never fetched" (null) from
"fetched but empty" ([]), so the panel reads correctly after a refresh
returns zero rows. Affected renderers: _renderCycles, _renderAudience,
_renderConnections. Targeting unchanged (its gate is "both lists needed",
not single-list empty).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 14:54:44 -04:00
Svrnty
ef6a123c06 fix(adwright overview): show 0 (not —) when data empty; was treating zero as 'missing'
Some checks failed
plugin-tests / test (push) Failing after 6s
Bug: cycleCount: String(c.length || '—') turned 0 into '—' due to || falsy
coercion. Plan B DB starts empty → Overview KPIs all '—' even after a
successful refresh. Renders '0' now (the actually correct truthful value).

Karpathy 4 rules: smallest change (one expression), no shape change.
2026-05-24 14:45:20 -04:00
Svrnty
058d67b459 fix(adwright panel): map real {meta,woo} shape from adwright-mcp; connections tab now shows live status
Some checks failed
plugin-tests / test (push) Failing after 6s
Bug: panel _ingestUpdate expected payload.connections array (mock shape).
Real adwright_get_connections_status returns {meta:{...}, woo:{...}}.
Resulted in connections tab always empty + Overview KPIs sourced from
empty arrays even though backend was returning real data.

Now ingest normalises both shapes (real + mock fallback) into the
[{id, name, ok, status, detail}, ...] shape the renderer already expects.
detail field plumbed through for future Connections card expansion.

Verified via curl /api/adwright/last-panel-update?tool=adwright_get_connections_status
returns real Plan B sandbox + Woo. Panel now displays them.

Karpathy 4 rules: surfaced shape mismatch via real backend probe, smallest
shim (one ingest branch), supports both old + new shape (no regression).
2026-05-24 14:24:31 -04:00
Svrnty
e9554fa051 fix(bte panel): store BTE-relative thumbUrl so renderer's PROXY_BASE wrap doesn't double-encode
Some checks failed
plugin-tests / test (push) Failing after 6s
Browser console: 'Failed to load resource: ...%252Fb6078c89...%252Fimage:0'.
_refreshGrid normalized thumbUrl as a full proxy URL, then _renderGrid +
_renderDetail wrap it again with PROXY_BASE+encodeURIComponent → %2F →
%252F. Fix: store raw BTE path; let the existing renderer wrap it once.

Karpathy 4 rules: surface root cause (double-wrap, not the encoder),
smallest fix (one expression), surgical (no other shape change).
2026-05-24 14:19:09 -04:00
Svrnty
d61f9e8d3f feat(bte panel): wire grid to live /api/query/assetDtos (replaces 404'd assetGrid)
Some checks failed
plugin-tests / test (push) Failing after 6s
JP question: 'is there any way to see the generated images by the cmo inside the bte panel app'.

The Command Center PRD §5.4 specifies `/api/query/assetGrid` for the grid;
that endpoint isn't implemented yet on BTE. But `/api/query/assetDtos`
WORKS today and returns every brand-scoped asset including the ones CMO
just generated via bte_image_generate (asset ids 664787c4-... + dbe21e15-...).

Changes:
- routes/bte_proxy.py: allowlist /api/query/assetDtos + /api/assets/{id}/image
  (the latter so panel can render real PNG thumbnails — not just /thumb stubs)
- static/bte.js: _refreshGrid now POSTs assetDtos with {pageSize:48, sortBy:createdAt desc}.
  AssetDto rows normalized to panel's existing asset shape (id, thumbUrl,
  lifecycle, scores, prompt, dims). thumbUrl points at the live image bytes
  via the new proxy allowlist entry. _renderGrid stays untouched — same
  shape it expected.

Result: BTE panel grid now shows every Plan B asset including freshly-
generated CMO images. Polling stops when no in-flight renders remain.

When BTE Command Center §5.4 ships the real assetGrid endpoint, swap
the path back — frontend won't need any other change.

Karpathy 4 rules: smallest possible adapter (normalize one shape to another,
no abstraction added), surgical (one new allowlist entry, one function body
rewritten, no other panel logic touched), verified via curl that assetDtos
returns 10 assets including the CMO-generated ones before committing.
2026-05-24 14:15:22 -04:00
Svrnty
7dcda7669f fix(adwright panel): add 1-click Switch-to-CMO button + authoritative profile fetch
Some checks failed
plugin-tests / test (push) Failing after 5s
JP feedback: panel says "switch to cmo profile" but offers no way to do so.
Also window.S.activeProfile may be empty on /session/* pages before boot.js
finishes initializing, so the banner showed unnecessarily.

This patch:
- Replaces vague text with a [Switch to CMO] button that POSTs to
  /api/profile/switch {name: "cmo-planb"} and reloads on success
- Adds _fetchActiveProfile() that reads /api/profile/active directly
  (defense against window.S race); cached in NS.state._fetchedProfile
- Background poll every 5s catches profile switches made from the upstream
  Profiles panel — no hard reload needed to clear the banner
- Disabled-state refresh fires once on mount + on each poll

Karpathy 4 rules: smallest possible change (one button + one fetch helper),
no abstraction layer, no fallback "smart" detection — authoritative API only.
2026-05-24 13:38:39 -04:00
Svrnty
69e575ca59 fix(plugin): hide native main-views when our panel active; BTE in-main not full-viewport
Some checks failed
plugin-tests / test (push) Failing after 5s
JP reported (Chrome) after b43e649:
- Sidebar buttons render ✓
- Adwright: useless middle black panel between sidebar and right area
- BTE: entire left sidebar disappears, only close-X recovers it

ROOT CAUSES:
1. webui CSS has `main.main:not(.showing-X):not(.showing-Y)... > #mainChat
   { display:flex }` which still matches when our class is svrnty-showing-*
   (not in webui's whitelist). Chat rendered alongside our panel → the
   empty void = "middle black panel".
2. BTE overlay was `position:fixed; inset:0; z-index:9991` — covered the
   whole viewport including sidebar + topbar.

FIXES:
- Adwright + BTE CSS now `main.main.svrnty-showing-<id> > .main-view
  { display:none !important }` — explicitly hides every native main-view
  (mainChat, mainSkills, mainTasks, ...) when our panel is active.
- BTE.css: overlay no longer position:fixed. Becomes a normal flex child
  of main when svrnty-showing-bte is active. Sidebar + topbar stay visible.
- bte.js: _openOverlay now appends to <main class="main"> instead of
  document.body. Migrates existing body-mounted overlay on first open.

Karpathy 4 rules: root-caused via direct CSS inspection (not guessing),
two-line CSS change per panel, no new abstractions.
2026-05-24 13:05:38 -04:00
Svrnty
b43e6496f5 fix(plugin): inject sidebar buttons into BOTH .rail (desktop) + .sidebar-nav (mobile)
Some checks failed
plugin-tests / test (push) Failing after 5s
ROOT CAUSE of JP's "buttons not showing" report: hermes-webui has two nav
containers — `<nav class="rail">` (desktop ≥641px, rail-btn 20×20 stroke
1.5) and `.sidebar-nav` (mobile, nav-tab 18×18 stroke 2). My previous
injection only touched .sidebar-nav, so on desktop the buttons literally
didn't exist anywhere visible.

This patch:
- Defines per-container button templates (class list + svg size/stroke)
  matching exactly how upstream renders its native rail-btn vs nav-tab
- Iterates both containers on init + retries
- MutationObserver re-injects into either if something re-renders it
- Tooltip modifier (--bottom) only applied for sidebar-nav (mobile pattern)
- SVG paths centralized so adding new tabs is one entry

Karpathy 4 rules: surfaced root cause via console logs + DOM inspection
instead of guessing, simplest fix (data-driven container list), surgical
(no other behavior changed).
2026-05-24 12:58:06 -04:00
Svrnty
7a5c48c775 debug(plugin): add console logging to svrnty_nav.js to diagnose silent injection failure
Some checks failed
plugin-tests / test (push) Failing after 5s
2026-05-24 12:55:35 -04:00
Svrnty
80420e0d01 feat(plugin): sidebar nav buttons for Adwright + BTE (v0.5.0)
Some checks failed
plugin-tests / test (push) Failing after 5s
JP feedback: floating BTE launcher + always-visible Adwright panel were
"very fucked up" UX. Replaced with proper hermes-webui sidebar integration.

New static/svrnty_nav.js:
- Injects 2 nav-tab buttons into .sidebar-nav matching the existing
  data-panel + nav-tab + has-tooltip pattern
- Adwright: bullseye icon (marketing intelligence)
- BTE: sparkle/palette icon (creative studio)
- Wraps window.switchPanel to add main.svrnty-showing-{adwright,bte}
  classes so our panels participate in the existing main-view show/hide
  system without editing upstream panels.js
- Dispatches "svrnty:panel-switch" CustomEvent so panel modules can
  lazy-init / open / close based on which panel is active

Adwright panel:
- adwright.css: hidden by default; shows only when main has
  .svrnty-showing-adwright. When showing, occupies full main width
  and hides all other main children (chat etc).
- adwright.js: no change to mount logic (already mounted inside <main>).

BTE panel:
- bte.js: removed _installLauncher (no more floating bottom-right button).
  Init now listens for svrnty:panel-switch events to open/close the
  overlay. Defensively removes any stale .svrnty-bte-launcher DOM nodes
  from prior plugin versions.
- bte.css: launcher styles replaced with display:none !important.

manifest.yaml: bumped 0.4.0 → 0.5.0, svrnty_nav.js added to assets in
the correct load order (after app.js, before adwright.js + bte.js).
CONNECTION-MAP regenerated.

Karpathy 4 rules: no upstream edit, smallest possible wrap of switchPanel
to keep our panels coexisting with native panels (chat, tasks, kanban,
etc), CSS-only visibility (no DOM thrashing).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:45:01 -04:00
Svrnty
f8ce6b21f1 feat(umbrella): cortex-os umbrella graph viz panel — Phase 2.E
Some checks failed
plugin-tests / test (push) Failing after 6s
Per sot/03-PROTOCOLS/CORTEX-OS-UMBRELLA-VIZ-PRD.md. Living-graph panel
inside hermes-webui consuming curator-maintained graph/umbrella.json
(UI-stable v1.0 schema emitted by curator-planb sweep.py v0.2).

routes/umbrella.py — 3 endpoints:
  GET /umbrella                  → redirect to static panel HTML
  GET /api/umbrella              → graph/umbrella.json contents
  GET /api/umbrella/doc?path=X   → markdown body for a node's source_path
                                   (path-traversal guarded; 50KB cap)

static/umbrella.{html,css,js} — Cytoscape.js v3.30.2 (CDN) render:
  - 8 node types: doc/profile/skill/mcp_server/sovereign_api/cortex_tool/
    external_dep/credential — each gets distinct color + shape
  - 5 edge types: depends_on/governs/consumes/produces/supersedes
  - filter chips (toggle node type visibility)
  - layout switcher (force/tier/concentric)
  - search (dim non-matching nodes)
  - click node → side panel w/ frontmatter + markdown body + in/out edges
  - edge-list buttons jump between connected nodes

Plan B brand-aligned dark theme; DESIGN.md 8-property subset
(backgroundColor/textColor/typography/rounded/padding/size/height/width).

svrnty_nav.js bundled (sidebar nav integration — non-umbrella scope).

CONNECTION-MAP.md regenerated via scripts/ast-connection-map.py.
plugin.py route-list extended w/ "umbrella" + injects umbrella assets via
the standard static dir registration.

Module import: ✓ (python3 -c "import routes.umbrella" clean).
Graph artifact verified: 81 nodes / 120 edges live in graph/umbrella.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:43:29 -04:00
Svrnty
849dd27119 feat(adwright panel): add cross-link button to BTE Command Center
Some checks failed
plugin-tests / test (push) Failing after 6s
Header now shows ↗ BTE button next to the profile status pill. Click
invokes window.SvrntyBTE.open() to surface the BTE overlay, satisfying
the "Adwright pulls content from BTE panel" integration goal at the UX
level. Asset-URL-level integration follows automatically once cycles
contain BTE-rendered asset references (post Phase 8).

Themed via existing CSS vars (--accent, --border2, --accent-bg, etc) —
zero hardcoded colors. CONNECTION-MAP regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:19:17 -04:00
Svrnty
0b19fdd7d0 feat(plugin): Adwright + BTE Command Center panels (v0.4.0)
Some checks failed
plugin-tests / test (push) Failing after 5s
Two new tool panels surfaced inside hermes-webui via inject_script/
inject_stylesheet. Both vanilla JS + CSS, no frameworks, WebUI CSS-vars
only (no hardcoded colors), light/dark inherits free.

## Adwright panel (static/adwright.{js,css} + routes/adwright.py)

5 tabs: Overview · Cycles · Audience · Targeting · Connections.
Layout: 60/40 panel/chat split via CSS :has() selector.
Always-visible, soft-disabled when active profile isn't `cmo*`.

Action wiring (READ path — agent-mediated per governance):
1. Panel button → fires custom event
2. Handler synthesizes /adwright <cmd> chat message
3. Posts via existing btnSend pathway → message visible in chat
4. CMO sees + calls mcp_adwright_<tool>
5. Panel polls /api/adwright/last-panel-update for structured payload
6. Mock payload returned v1; real session-DB reader plugs in when
   adwright-mcp gains writer

Connections WRITE path (governance exception, NO secrets in chat):
- POST /api/adwright/provision-creds with form fields
- Plugin invokes credctl set <key> via stdin (value never on argv)
- Allowlist enforced (defense-in-depth on key names)
- Auth-gated by WebUI session cookie

Skin: .svrnty-aw-* class prefix, window.SvrntyAdwright JS namespace,
guard against double-load, scoped MutationObserver.

## BTE Command Center panel (static/bte.{js,css} + routes/bte_proxy.py)

Content-mode pills (Polished/UGC/Photorealistic/Artistic) × media toggle
(Image/Video — Video disabled v1 pending Phase 4e) × recipe family picker
(Hero Shot/Lifestyle Shot/Photoshoot/Recipe Sheet/Montage Catalog) per
canonical PLANB-RECIPE-TAXONOMY. SKU picker, variant stepper 1-12,
single/batch toggle, [Generate] button.

Asset grid with streaming thumbnails, asset detail (full-res + rate +
comment + "Use in Adwright cycle" deep link). Embedded CMO chat right rail
for re-orienting generations ("make next batch warmer / less white space").

BTE proxy route (/api/bte/proxy) with whitelisted paths
(requestPhotoshoot, assetGrid, recipeStats, assets/{id}/thumb, etc.)
prevents browser-side CORS to BTE :6001.

Skin: .svrnty-bte-* class prefix, window.SvrntyBTE JS namespace.

## Wiring

manifest_version: 0.2.0 → 0.4.0
assets registered:
- /plugins/svrnty/adwright.{js,css} + static/adwright/
- /plugins/svrnty/bte.{js,css} + static/bte/
routes registered:
- GET /api/adwright/last-panel-update (panel update channel)
- POST /api/adwright/provision-creds (governance-exception write)
- GET/POST /api/bte/proxy (BTE REST proxy with allowlist)

Karpathy 4 rules: agents reported every deviation with rationale (Python
venv interp for hermes mcp add, missing aggregate connections-status RPC
composed from two verifies, mock panel-update v1 with locked frontend
protocol so real session-DB reader is a drop-in swap), verified asset
serving + plugin route registration before claiming complete, surfaced
open questions instead of silently choosing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:12:27 -04:00
Svrnty
0ef66ab599 feat(plugin): voice-message mic UI in app.js — closes Phase 2.A UX gap (L8)
All checks were successful
plugin-tests / test (push) Successful in 6s
Migrates the boot.js mic-button behavior change from reverted fork commit
014b9eef into plugin/static/app.js. Replaces vanilla dictation-to-textbox
mic flow with voice-message mode: tap to record, tap again to stop → audio
File attached + sent automatically. Server-side audio_attachment_processor
in routes/transcribe.py transcribes for text-only models.

Implementation strategy:
- Own #btnMic onclick (boot.js sets it during init; plugin overrides via
  MutationObserver after DOM mutations + idempotent dataset.svrntyVm flag)
- MediaRecorder with same mime-type fallbacks as the original
  (webm/opus → webm → ogg/opus → mp4)
- DataTransfer to set #fileInput.files programmatically (cross-browser path)
- Dispatch 'change' so boot.js's fileInput.onchange → addFiles() runs
- Click #btnSend after 50ms tick so attachment registers before submit

Filename convention: voice-message-${timestamp}.{webm|ogg|mp4} — matches the
audio processor's name-prefix detection in _transcribe_audio_attachments.

Tests: 6 new JS-static checks in tests/unit/test_app_js.py (idempotent
guard, voice-message prefix, DataTransfer pattern, vault URL pin, mic
override). 26 tests total, all PASS.

Connection map: now 9 public API · 0 forced internal · 1 frontend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 10:28:47 -04:00
Svrnty
cbf53a0d55 feat(plugin): migrate vault UI panel + STT route stub into plugin (P2 cont.)
All checks were successful
plugin-tests / test (push) Successful in 25s
static/app.js now injects the "Vault connections" panel into Settings → System
via DOM mutation observer + populates from GET /api/vault/status (already
served by routes/vault_status.py). Mirrors the original behavior from
hermes-webui fork commit 3e2c74f3 without needing any upstream HTML/JS edit.

routes/transcribe.py documents the STT migration design choice (streaming_hook
public-API method vs forced-internal entries) — implementation lands as a
follow-on. Plugin still loads cleanly without STT (route disabled in
_phase2_routes() until Phase 2.1).

CONNECTION-MAP.md regenerated: still 0 forced internals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 10:06:56 -04:00
Svrnty
c1e3fa1af0 feat(plugin): Phase 2 partial — vault_status migrated + brand skin moved + eval suite (P2.B/C, P3.A/B)
All checks were successful
plugin-tests / test (push) Successful in 25s
Lands the easy migrations + the automation skeleton. STT migration deferred
to Phase 2.1 (it touches the streaming engine + bootstrap JS — needs a new
streaming_hook public-API method OR forced-internal CONNECTION-MAP entries).

Migrated to plugin:
  routes/vault_status.py    GET /api/vault/status (from fork commit 3e2c74f3)
  static/{app.js,app.css,fonts/}  brand skin (from hermes-ext/)

Plugin auto-loaded by hermes-webui when HERMES_WEBUI_PYTHON_PLUGIN is set;
register_static + inject_stylesheet + inject_script wire the URL contract at
/plugins/svrnty/{app.css,app.js} per protocol §14 (Q5).

Automation skeleton:
  Makefile                          one-liner targets: test · map · sync-upstream · smoke
  scripts/boot-smoke.py             start upstream+plugin, curl every endpoint
  scripts/upstream-sync.py          fetch tags + run matrix + JSON report
  tests/evals/test_features.py      4 evals (loader contract · vault payload · brand URL contract · forced-internal=0)
  tests/unit/test_brand_skin.py     4 asset-presence + wiring tests
  tests/unit/test_vault_status.py   3 handler tests (register, success, error)

CONNECTION-MAP.md: 0 forced-internal dependencies; plugin uses only public API.
AST script timestamp removed so map-check is deterministic.

Tests: 11/11 PASS (4 evals + 7 unit). Integration tests deferred until
boot-smoke runs against a live hermes-webui (Phase 2.D + 2.E gate).

Deferred to next session:
  P2.A  STT migration (needs streaming_hook design — see routes/transcribe.py)
  P2.D  Revert 4 fork feature commits — needs STT migration first
  P2.E  Archive hermes-ext repo — gated on P2.D
  P2.F  Live boot smoke against real webui

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 10:02:47 -04:00
Svrnty
4264c6cbe8 feat(plugin): initial scaffold — plugin loader entry + AST + CI workflows (P1.B/C/D)
THE single repo holding every Svrnty modification to nesquena/hermes-webui per
the SVRNTY-HERMES Plugin Protocol PRD (hermes/docs/SVRNTY-PLUGIN-PROTOCOL.md).
This scaffold is the empty vessel; Phase 2 migrates the existing fork
modifications (STT, vault status, brand skin) into it.

Contents:
  plugin.py                          register(api) entry; reads 6-method
                                     contract; wires static + routes
  svrnty_hermes_webui_plugin/        package wrapper for pip install
  manifest.yaml                      plugin name · version · upstream pin
  pyproject.toml                     pip-installable
  routes/__init__.py                 placeholder until Phase 2 migration
  static/                            placeholder for brand skin migration
  CONNECTION-MAP.md                  AST-generated; 4 public API calls, 0 forced
  scripts/ast-connection-map.py      AST walker; modes: regen · --check · --diff
  tests/{unit,integration,evals}/    skeleton for Phase 3 eval suite
  .github/workflows/
    plugin-tests.yml                 push: unit + integration + map check
    connection-map-check.yml         PR: regen + diff vs committed
    upstream-drift.yml               daily cron: detect new upstream tags +
                                     run matrix (activates when Gitea runner
                                     registered on Svrnty infra)

Karpathy 4 rules in CLAUDE.md; every subagent inherits them via the workspace
contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 09:59:45 -04:00