Commit Graph

11 Commits

Author SHA1 Message Date
Svrnty
cf723141a4 Implement S23 runtime health slice 2026-05-28 21:38:50 -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
ab24ff9cdb fix(plugin): umbrella.py registration broke /api/umbrella — sprint 2026-05-25 Wave 7 D12
Root cause: routes/umbrella.py:31 registered /umbrella (non-/api/* path)
which the plugin loader rejects with ValueError per SVRNTY-PLUGIN-PROTOCOL
§5.1. The ValueError bubbled out of register() before the two valid
/api/umbrella* routes could be registered, so /api/umbrella returned 404.

Note: task description fingered vault_status.py:46 as a "recursion bug",
but L46 there is handler.wfile.write(body) — no recursion. The _plugin_h
calls in tracebacks come from hermes-webui/api/routes.py:3462 (the
dispatcher, correctly invoking plugin handlers). The vault_status
BrokenPipeErrors are unrelated client-disconnect noise from a slow
credctl subprocess, not what breaks /api/umbrella.

Fix:
- Drop api.register_route("/umbrella", ...) line that violated /api/* contract
- Remove now-orphaned _handle_panel_html (Karpathy rule 3 cleanup)
- Add docstring noting umbrella panel HTML is reached via
  /plugins/svrnty/umbrella.html (already served by register_static)

Verified: /api/umbrella returns 200 + umbrella.json after restart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 16:50:19 -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
c6d94462c4 feat(bte_proxy): Path A adapter — translate panel recipe-shape to BTE canonical
Some checks failed
plugin-tests / test (push) Failing after 2s
Panel's BTE Command Center posts {brandId(slug), recipeSlug, items[],
variantsPerScenario} which the current BTE backend doesn't accept (PRD §5.8
recipe-driven fan-out not shipped). Adapter intercepts POST
/api/command/requestPhotoshoot, resolves brand slug -> Guid via /api/query/brandDtos
(cached in-process), expands items × variants into per-shot entries, inlines the
StopgapFluxWorkflow FLUX.2 graph as workflowJson (LocalFluxImageProvider
rejects empty), parses mode from recipeLabel for rubricMode (polished|ugc;
photoreal/artistic fall back to polished per BTE validator). All other proxied
paths pass through unchanged. Stdlib-only.

Verified: real POST through adapter -> BTE returns 200 with saga-start envelope
(per-shot DB failure is BTE-internal, out of scope — adapter cleared schema).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 13:34:39 -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
4dac80b215 feat(adwright route): wire real Adwright data via adwright_core import
Some checks failed
plugin-tests / test (push) Failing after 6s
routes/adwright.py now lazy-imports adwright_core from adwright-mcp/ and
calls the 6 v1 tool functions directly. Real payloads flow through
last-panel-update; mock data remains as fallback if adwright_core import
fails (proto version mismatch, missing adwright-mcp dir, etc).

Verified in webui's hermes-agent venv (Python 3.11 + protobuf 6.x):
- get_connections_status → real Plan B sandbox act_967504505966162
- list_cycles / list_segments / list_recipes → real (empty) data
- refresh_cycles → real timestamp + cycle re-fetch

Workspace-internal dependency on adwright-mcp/ documented in
CONNECTION-MAP (regen).

Karpathy 4 rules: smallest possible change to lift mock→real (single
function add + handler tweak), no abstraction layer introduced, fallback
preserves uptime if adwright-mcp evolves incompatibly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:16:46 -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
37123f570b feat(plugin): STT migration via audio_attachment_processor hook (L1-L6)
All checks were successful
plugin-tests / test (push) Successful in 8s
Closes Phase 2.A. STT now lives entirely in the plugin via the new public-API
method `api.register_audio_attachment_processor` added to the loader hook
(Rule 1 — extended API, no forced-internal). The fork patch stays minimal
(streaming.py gains a small loop that calls registered processors; loader
adds the 1 new method).

Plugin additions:
  routes/transcribe.py            POST /api/transcribe + audio_attachment_processor
                                  - _external_stt_transcribe: multipart POST to STT endpoint
                                  - _handle_transcribe: one-shot transcription route
                                  - _transcribe_audio_attachments: voice-message processor
                                  - _parse_multipart_file: stdlib email-based multipart
                                    (Python 3.13 dropped cgi per PEP 594)
  tests/unit/test_transcribe.py   8 tests (register, processor, route, multipart parser)
  tests/evals/test_features.py    + 1 eval (audio processor signature contract)

Config (read at call time, never persisted):
  HERMES_WEBUI_STT_URL  external STT endpoint (OpenAI or WhisperX shape)
  HERMES_WEBUI_STT_KEY  optional bearer token

CONNECTION-MAP regenerated: 9 public-API · 0 forced-internal · 1 frontend.
20/20 tests PASS.

Loader API extended in hermes-webui (next commit there) — 7th method:
register_audio_attachment_processor. Streaming.py gets a small loop that
calls registered processors before _build_native_multimodal_message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 10:14:29 -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