Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ Every significant task writes a line to `.trinity/experience/`.
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] TASK: description | result" >> .trinity/experience/trios_$(date +%Y%m%d).trinity
```

### L8: PUSH FIRST LAW
Every file change = immediate commit + push. There is no such thing as "done locally".

Checklist before saying "done":
```
□ git status — 0 untracked/modified files
□ git log --oneline -3 — commit is visible
□ github.com/gHashTag/trios — file visible in browser
```

If a file is not in the repo — the task is NOT complete.

## Architecture

```
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ members = [
"crates/trios-trinity-init",
"crates/trios-fpga",
"crates/trios-doctor",
"crates/trios-ext",
"crates/igla-trainer",
"contrib/anti-ban",
"contrib/oracle",
Expand Down
46 changes: 28 additions & 18 deletions crates/trinity-brain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! R2: Persistence / HSLM hook (planned)

use std::collections::HashMap;
use std::sync::{Mutex, LazyLock};
use std::sync::{LazyLock, Mutex};

/// Brain error types
#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -59,9 +59,7 @@ impl BrainStorage {
}

/// Global brain instance (thread-safe for R0)
static BRAIN: LazyLock<Mutex<BrainStorage>> = LazyLock::new(|| {
Mutex::new(BrainStorage::new())
});
static BRAIN: LazyLock<Mutex<BrainStorage>> = LazyLock::new(|| Mutex::new(BrainStorage::new()));

/// Store a value in brain memory
///
Expand All @@ -84,8 +82,7 @@ pub fn brain_remember(key: &str, value: &[u8]) -> Result<(), BrainError> {
return Err(BrainError::InvalidKey);
}

let mut brain = BRAIN.lock()
.map_err(|_| BrainError::StorageError)?;
let mut brain = BRAIN.lock().map_err(|_| BrainError::StorageError)?;

brain.remember(key, value);
Ok(())
Expand Down Expand Up @@ -114,11 +111,9 @@ pub fn brain_recall(key: &str) -> Result<Vec<u8>, BrainError> {
return Err(BrainError::InvalidKey);
}

let brain = BRAIN.lock()
.map_err(|_| BrainError::StorageError)?;
let brain = BRAIN.lock().map_err(|_| BrainError::StorageError)?;

brain.recall(key)
.ok_or(BrainError::KeyNotFound)
brain.recall(key).ok_or(BrainError::KeyNotFound)
}

/// Forget a value from brain memory
Expand All @@ -135,17 +130,14 @@ pub fn brain_forget(key: &str) -> Result<bool, BrainError> {
return Err(BrainError::InvalidKey);
}

let mut brain = BRAIN.lock()
.map_err(|_| BrainError::StorageError)?;
let mut brain = BRAIN.lock().map_err(|_| BrainError::StorageError)?;

Ok(brain.forget(key))
}

/// Get total number of stored memories
pub fn brain_count() -> usize {
BRAIN.lock()
.map(|brain| brain.count())
.unwrap_or(0)
BRAIN.lock().map(|brain| brain.count()).unwrap_or(0)
}

/// Clear all memories (for testing only)
Expand All @@ -158,10 +150,28 @@ pub fn brain_clear() {
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;

/// Setup: clear brain before each test
fn setup() {
brain_clear();
static TEST_LOCK: Mutex<()> = Mutex::new(());

struct TestGuard;

impl TestGuard {
fn new() -> Self {
let _guard = TEST_LOCK.lock().unwrap();
brain_clear();
TestGuard
}
}

impl Drop for TestGuard {
fn drop(&mut self) {
brain_clear();
}
}

fn setup() -> TestGuard {
TestGuard::new()
}

#[test]
Expand Down
18 changes: 18 additions & 0 deletions crates/trios-ext/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "trios-ext"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
description = "Trios Chrome Extension (Dioxus/Wasm)"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["Window", "Document", "Element", "HtmlElement", "WebSocket", "MessageEvent", "console"] }
js-sys = "0.3"
serde = { workspace = true }
serde_json = { workspace = true }
26 changes: 26 additions & 0 deletions crates/trios-ext/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use wasm_bindgen::prelude::*;

#[wasm_bindgen(start)]
pub fn run() {
console_log::console_log!("trios-ext loaded");
}

pub fn console_log(msg: &str) {
web_sys::console::log_1(&msg.into());
}

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Trios says: Hello, {}!", name)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn greet_returns_message() {
let result = greet("Agent");
assert!(result.contains("Agent"));
}
}
19 changes: 19 additions & 0 deletions extension/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "MCP_REQUEST") {
const ws = new WebSocket("ws://localhost:3333");
ws.onopen = () => {
ws.send(JSON.stringify(message.payload));
};
ws.onmessage = (event) => {
sendResponse({ type: "MCP_RESPONSE", data: JSON.parse(event.data) });
ws.close();
};
ws.onerror = (err) => {
sendResponse({ type: "MCP_ERROR", error: "WebSocket connection failed" });
ws.close();
};
return true;
}
});
Binary file added extension/icons/icon-128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions extension/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"manifest_version": 3,
"name": "Trios — Queen Trinity Sidebar",
"version": "0.1.0",
"description": "Trios agent orchestrator sidebar panel",
"permissions": ["sidePanel", "activeTab"],
"side_panel": {
"default_path": "sidepanel.html"
},
"background": {
"service_worker": "background.js"
},
"icons": {
"128": "icons/icon-128.png"
},
"action": {
"default_title": "Open Trios"
}
}
35 changes: 35 additions & 0 deletions extension/sidepanel.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Trios Sidebar</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="app">
<header class="header">
<img src="icons/icon-128.png" alt="Trios" class="logo" />
<h1>Trios</h1>
</header>
<nav class="tabs">
<button class="tab active" data-tab="chat">Chat</button>
<button class="tab" data-tab="agents">Agents</button>
<button class="tab" data-tab="tools">MCP Tools</button>
</nav>
<main id="content">
<section id="tab-chat" class="tab-content active">
<div id="messages"></div>
<input id="chat-input" type="text" placeholder="Send a message..." />
</section>
<section id="tab-agents" class="tab-content">
<div id="agent-list">Loading agents...</div>
</section>
<section id="tab-tools" class="tab-content">
<div id="tool-list">Loading tools...</div>
</section>
</main>
</div>
<script src="sidepanel.js"></script>
</body>
</html>
73 changes: 73 additions & 0 deletions extension/sidepanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
document.querySelectorAll(".tab").forEach((tab) => {
tab.addEventListener("click", () => {
document.querySelectorAll(".tab").forEach((t) => t.classList.remove("active"));
document.querySelectorAll(".tab-content").forEach((c) => c.classList.remove("active"));
tab.classList.add("active");
document.getElementById("tab-" + tab.dataset.tab).classList.add("active");
});
});

const chatInput = document.getElementById("chat-input");
const messagesEl = document.getElementById("messages");

chatInput.addEventListener("keydown", (e) => {
if (e.key === "Enter" && chatInput.value.trim()) {
const msg = chatInput.value.trim();
appendMessage("you", msg);
chatInput.value = "";
chrome.runtime.sendMessage(
{ type: "MCP_REQUEST", payload: { method: "agents/chat", params: { message: msg } } },
(response) => {
if (response && response.type === "MCP_RESPONSE") {
appendMessage("agent", response.data.response || JSON.stringify(response.data));
} else {
appendMessage("error", "Connection failed — is trios-server running?");
}
}
);
}
});

function appendMessage(role, text) {
const div = document.createElement("div");
div.className = "message " + role;
div.textContent = text;
messagesEl.appendChild(div);
messagesEl.scrollTop = messagesEl.scrollHeight;
}

loadAgents();
loadTools();

function loadAgents() {
chrome.runtime.sendMessage(
{ type: "MCP_REQUEST", payload: { method: "agents/list", params: {} } },
(response) => {
const el = document.getElementById("agent-list");
if (response && response.type === "MCP_RESPONSE") {
const agents = response.data;
if (Array.isArray(agents) && agents.length === 0) {
el.textContent = "No agents connected.";
} else {
el.textContent = JSON.stringify(agents, null, 2);
}
} else {
el.textContent = "Server not reachable.";
}
}
);
}

function loadTools() {
chrome.runtime.sendMessage(
{ type: "MCP_REQUEST", payload: { method: "tools/list", params: {} } },
(response) => {
const el = document.getElementById("tool-list");
if (response && response.type === "MCP_RESPONSE") {
el.textContent = JSON.stringify(response.data, null, 2);
} else {
el.textContent = "Server not reachable.";
}
}
);
}
78 changes: 78 additions & 0 deletions extension/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #1a1a2e;
color: #e0e0e0;
min-height: 100vh;
}

.header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background: #16213e;
border-bottom: 1px solid #0f3460;
}

.logo {
width: 32px;
height: 32px;
}

.tabs {
display: flex;
background: #16213e;
border-bottom: 1px solid #0f3460;
}

.tab {
flex: 1;
padding: 10px;
border: none;
background: transparent;
color: #a0a0a0;
cursor: pointer;
font-size: 13px;
transition: color 0.2s, border-bottom 0.2s;
}

.tab.active {
color: #e94560;
border-bottom: 2px solid #e94560;
}

.tab-content {
display: none;
padding: 16px;
}

.tab-content.active {
display: block;
}

#chat-input {
width: 100%;
padding: 10px;
border: 1px solid #0f3460;
border-radius: 6px;
background: #16213e;
color: #e0e0e0;
font-size: 14px;
margin-top: 12px;
}

#messages {
max-height: 400px;
overflow-y: auto;
}

#agent-list,
#tool-list {
font-size: 13px;
}