From eb33bd86044bffcdb5af6b4bcdef642f14a5873a Mon Sep 17 00:00:00 2001 From: root Date: Thu, 4 Jun 2026 21:11:11 +1000 Subject: [PATCH] =?UTF-8?q?feat(ui):=20Sentinel=20view=20=E2=80=94=20Yerin?= =?UTF-8?q?=20global=20security=20chat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- public/app.js | 1 + public/components/sidebar.js | 2 +- public/router.js | 2 ++ public/views/sentinel.js | 25 +++++++++++++++++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 public/views/sentinel.js diff --git a/public/app.js b/public/app.js index 70ca9ff..4236018 100644 --- a/public/app.js +++ b/public/app.js @@ -22,6 +22,7 @@ const VIEWS = { search: () => import('./views/search.js'), inbox: () => import('./views/inbox.js'), 'sacred-valley': () => import('./views/sacred_valley.js'), + sentinel: () => import('./views/sentinel.js'), jobs: () => import('./views/jobs.js') }; diff --git a/public/components/sidebar.js b/public/components/sidebar.js index c584e79..9269bf2 100644 --- a/public/components/sidebar.js +++ b/public/components/sidebar.js @@ -91,10 +91,10 @@ export function renderSidebar(root) { el('div', { class: 'sb-section' }, el('div', { class: 'sb-title' }, 'Navigate'), navItem('Sacred Valley', '/sacred-valley'), + navItem('Sentinel', '/sentinel'), navItem('Search', '/search'), inboxItem, navItem('Jobs', '/jobs'), - el('div', { class: 'sb-item muted', title: 'Ships post-Plan-2' }, 'Agents — later'), el('div', { class: 'sb-item muted', title: 'Ships post-Plan-2' }, 'Resources — later') ) ); diff --git a/public/router.js b/public/router.js index a68700a..d475705 100644 --- a/public/router.js +++ b/public/router.js @@ -8,6 +8,7 @@ // #/search?q= search results // #/inbox pending changes // #/sacred-valley dashboard placeholder +// #/sentinel Yerin security view // Anything unrecognized falls through to the home handler. const ROUTES = [ @@ -19,6 +20,7 @@ const ROUTES = [ { name: 'search', re: /^\/search$/, keys: [] }, { name: 'inbox', re: /^\/inbox$/, keys: [] }, { name: 'sacred-valley', re: /^\/sacred-valley$/, keys: [] }, + { name: 'sentinel', re: /^\/sentinel$/, keys: [] }, { name: 'jobs', re: /^\/jobs$/, keys: [] }, { name: 'home', re: /^\/?$/, keys: [] } ]; diff --git a/public/views/sentinel.js b/public/views/sentinel.js new file mode 100644 index 0000000..d831332 --- /dev/null +++ b/public/views/sentinel.js @@ -0,0 +1,25 @@ +// #/sentinel — Yerin's global, read-only security view. Uses the shared +// agent_chat panel pointed at /api/security/yerin (no draft cards). +import { el, mount } from '../dom.js'; +import { wireAgentChat } from '../components/agent_chat.js'; + +const YERIN_LABELS = { + audit_log: '🗒️ reading the audit trail', agent_inventory: '👁️ reviewing agents', + pending_review: '⏳ checking the approval queue', resource_exposure: '🛡️ checking exposure', + token_audit: '🔑 auditing tokens' +}; + +export async function render(main) { + const log = el('div', { class: 'rail-log sentinel-log' }); + const input = el('textarea', { class: 'rail-input', rows: 1, placeholder: 'Ask Yerin about the Void’s security…' }); + mount(main, + el('h1', { class: 'view-h1' }, '◆ Sentinel — Yerin'), + el('p', { class: 'view-sub' }, 'Read-only security & observability. She watches, reports, and warns — she never acts.'), + el('div', { class: 'sentinel-chat' }, log, el('div', { class: 'rail-inputwrap' }, input))); + const chat = wireAgentChat({ + logEl: log, inputEl: input, + historyUrl: '/api/security/yerin', turnUrl: '/api/security/yerin/turn', + agentName: 'Yerin', showDrafts: false, toolLabels: YERIN_LABELS + }); + await chat.load(); +}