Skip to content

Commit a9124c3

Browse files
committed
real email
1 parent 7bd0185 commit a9124c3

File tree

11 files changed

+854
-39
lines changed

11 files changed

+854
-39
lines changed

Cargo.lock

Lines changed: 279 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ members = [
1818
]
1919

2020
[workspace.package]
21-
version = "0.1.7"
21+
version = "0.1.8"
2222
edition = "2021"
2323
license = "Apache-2.0 OR MIT"
2424
repository = "https://github.com/RightNow-AI/openfang"
@@ -122,6 +122,12 @@ argon2 = "0.5"
122122
# Lightweight regex
123123
regex-lite = "0.1"
124124

125+
# Email (SMTP + IMAP)
126+
lettre = { version = "0.11", default-features = false, features = ["builder", "hostname", "smtp-transport", "tokio1", "tokio1-rustls-tls"] }
127+
imap = "2"
128+
native-tls = "0.2"
129+
mailparse = "0.15"
130+
125131
# Testing
126132
tokio-test = "0.4"
127133
tempfile = "3"

crates/openfang-api/src/channel_bridge.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,12 +1580,18 @@ pub async fn start_channel_bridge_with_config(
15801580
let mut started_names = Vec::new();
15811581
for (adapter, _) in adapters {
15821582
let name = adapter.name().to_string();
1583+
// Register adapter in kernel so agents can use `channel_send` tool
1584+
kernel
1585+
.channel_adapters
1586+
.insert(name.clone(), adapter.clone());
15831587
match manager.start_adapter(adapter).await {
15841588
Ok(()) => {
15851589
info!("{name} channel bridge started");
15861590
started_names.push(name);
15871591
}
15881592
Err(e) => {
1593+
// Remove from kernel map if start failed
1594+
kernel.channel_adapters.remove(&name);
15891595
error!("Failed to start {name} bridge: {e}");
15901596
}
15911597
}

crates/openfang-api/src/routes.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7690,10 +7690,42 @@ pub async fn serve_upload(Path(file_id): Path<String>) -> impl IntoResponse {
76907690
// ---------------------------------------------------------------------------
76917691

76927692
/// GET /api/approvals — List pending approval requests.
7693+
///
7694+
/// Transforms field names to match the dashboard template expectations:
7695+
/// `action_summary` → `action`, `agent_id` → `agent_name`, `requested_at` → `created_at`.
76937696
pub async fn list_approvals(State(state): State<Arc<AppState>>) -> impl IntoResponse {
76947697
let pending = state.kernel.approval_manager.list_pending();
76957698
let total = pending.len();
7696-
Json(serde_json::json!({"approvals": pending, "total": total}))
7699+
7700+
// Resolve agent names for display
7701+
let registry_agents = state.kernel.registry.list();
7702+
7703+
let approvals: Vec<serde_json::Value> = pending
7704+
.into_iter()
7705+
.map(|a| {
7706+
let agent_name = registry_agents
7707+
.iter()
7708+
.find(|ag| ag.id.to_string() == a.agent_id || ag.name == a.agent_id)
7709+
.map(|ag| ag.name.as_str())
7710+
.unwrap_or(&a.agent_id);
7711+
serde_json::json!({
7712+
"id": a.id,
7713+
"agent_id": a.agent_id,
7714+
"agent_name": agent_name,
7715+
"tool_name": a.tool_name,
7716+
"description": a.description,
7717+
"action_summary": a.action_summary,
7718+
"action": a.action_summary,
7719+
"risk_level": a.risk_level,
7720+
"requested_at": a.requested_at,
7721+
"created_at": a.requested_at,
7722+
"timeout_secs": a.timeout_secs,
7723+
"status": "pending"
7724+
})
7725+
})
7726+
.collect();
7727+
7728+
Json(serde_json::json!({"approvals": approvals, "total": total}))
76977729
}
76987730

76997731
/// POST /api/approvals — Create a manual approval request (for external systems).

crates/openfang-api/static/index_body.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
<body x-data="app" :data-theme="theme">
22

3+
<!-- API Key Auth Prompt -->
4+
<div x-show="$store.app.showAuthPrompt" style="position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.6);backdrop-filter:blur(4px)" x-data="{ apiKeyInput: '' }">
5+
<div style="background:var(--bg-card,#1e1e2e);border:1px solid var(--border,#333);border-radius:12px;padding:2rem;max-width:400px;width:90%">
6+
<h3 style="margin:0 0 0.5rem;font-size:1.1rem">API Key Required</h3>
7+
<p style="color:var(--text-dim,#888);font-size:0.85rem;margin:0 0 1rem">This instance requires an API key. Enter the key from your <code>config.toml</code>.</p>
8+
<input type="password" x-model="apiKeyInput" placeholder="Enter API key..." @keydown.enter="$store.app.submitApiKey(apiKeyInput)" style="width:100%;padding:0.6rem;border-radius:6px;border:1px solid var(--border,#333);background:var(--bg-input,#151520);color:var(--text,#e0e0e0);font-size:0.9rem;box-sizing:border-box;margin-bottom:0.75rem">
9+
<button @click="$store.app.submitApiKey(apiKeyInput)" style="width:100%;padding:0.6rem;border-radius:6px;border:none;background:var(--accent,#7c3aed);color:#fff;font-weight:600;cursor:pointer;font-size:0.9rem">Unlock Dashboard</button>
10+
</div>
11+
</div>
12+
313
<div class="app-layout" :class="{ 'focus-mode': $store.app.focusMode }">
414
<!-- Sidebar -->
515
<nav class="sidebar" :class="{ collapsed: sidebarCollapsed, 'mobile-open': mobileMenuOpen }">

crates/openfang-api/static/js/app.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ function toolIcon(toolName) {
8787

8888
// Alpine.js global store
8989
document.addEventListener('alpine:init', function() {
90+
// Restore saved API key on load
91+
var savedKey = localStorage.getItem('openfang-api-key');
92+
if (savedKey) OpenFangAPI.setAuthToken(savedKey);
93+
9094
Alpine.store('app', {
9195
agents: [],
9296
connected: false,
@@ -99,6 +103,7 @@ document.addEventListener('alpine:init', function() {
99103
pendingAgent: null,
100104
focusMode: localStorage.getItem('openfang-focus') === 'true',
101105
showOnboarding: false,
106+
showAuthPrompt: false,
102107

103108
toggleFocusMode() {
104109
this.focusMode = !this.focusMode;
@@ -146,6 +151,30 @@ document.addEventListener('alpine:init', function() {
146151
dismissOnboarding() {
147152
this.showOnboarding = false;
148153
localStorage.setItem('openfang-onboarded', 'true');
154+
},
155+
156+
async checkAuth() {
157+
try {
158+
await OpenFangAPI.get('/api/providers');
159+
this.showAuthPrompt = false;
160+
} catch(e) {
161+
if (e.message && e.message.indexOf('401') >= 0) {
162+
this.showAuthPrompt = true;
163+
}
164+
}
165+
},
166+
167+
submitApiKey(key) {
168+
if (!key || !key.trim()) return;
169+
OpenFangAPI.setAuthToken(key.trim());
170+
localStorage.setItem('openfang-api-key', key.trim());
171+
this.showAuthPrompt = false;
172+
this.refreshAgents();
173+
},
174+
175+
clearApiKey() {
176+
OpenFangAPI.setAuthToken('');
177+
localStorage.removeItem('openfang-api-key');
149178
}
150179
});
151180
});
@@ -237,6 +266,7 @@ function app() {
237266
// Initial data load
238267
this.pollStatus();
239268
Alpine.store('app').checkOnboarding();
269+
Alpine.store('app').checkAuth();
240270
setInterval(function() { self.pollStatus(); }, 5000);
241271
},
242272

crates/openfang-channels/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,10 @@ sha2 = { workspace = true }
2727
base64 = { workspace = true }
2828
hex = { workspace = true }
2929

30+
lettre = { workspace = true }
31+
imap = { workspace = true }
32+
native-tls = { workspace = true }
33+
mailparse = { workspace = true }
34+
3035
[dev-dependencies]
3136
tokio-test = { workspace = true }

0 commit comments

Comments
 (0)