Skip to content
Closed

aaa #18

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
df9072d
feat(plugin): full context engine hooks
houko Apr 7, 2026
6b34667
fix(runtime): detect "[no reply needed]" as silent response (#2093)
f-liva Apr 8, 2026
ce6335b
feat(kernel): per-channel session isolation via deterministic UUID v5…
f-liva Apr 8, 2026
a4f8b27
feat(runtime): save channel images as files instead of inline base64 …
f-liva Apr 8, 2026
a42b92e
feat(approval): TOTP second-factor for critical tool approvals (#2131)
houko Apr 8, 2026
4dca830
feat(hands): proper resource composition for hand agents (#2133)
houko Apr 8, 2026
21fe314
fix(runtime): harden agent loop tool flow and trim handling (#2135)
leszek3737 Apr 8, 2026
1c89d68
fix(api,dashboard): timezone-aware schedule creation (#2138)
f-liva Apr 8, 2026
823988b
fix(kernel): glob-match declared tools and auto-promote shell_exec ex…
houko Apr 8, 2026
0200416
fix: persist mcp server updates in patch agent (#2151)
TechWizard9999 Apr 8, 2026
6477237
fix: use codex exec for codex cli driver (#2153)
TechWizard9999 Apr 8, 2026
c52f436
Fixed: improve Claude Code detection for keychain auth and non-login …
x86txt Apr 8, 2026
6f9c0fb
fix(dashboard): show active agent count instead of total in overview …
DaBlitzStein Apr 8, 2026
c78024e
fix(skills): handle SkillHub search response format with proper heade…
DaBlitzStein Apr 8, 2026
ac36e56
fix: resolve pre-existing clippy and test compile failures (#2180)
houko Apr 8, 2026
129442a
fix: suppress CMD window flash on Windows (#2159) (#2176)
houko Apr 8, 2026
cc20b68
fix: resolve hand.toml agent scan conflict (#2136) (#2177)
houko Apr 8, 2026
05937c9
fix: parameter errors now trigger self-correction instead of user rep…
houko Apr 8, 2026
9ba2cd0
feat: add extra_params support for openai compatible model (#2181)
houko Apr 8, 2026
6b160b4
fix: multi-bot Telegram routing uses account_id, not first-match on a…
houko Apr 8, 2026
cfbf397
fix(runtime): resolve build errors and clippy warnings (#2184)
houko Apr 8, 2026
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
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ taskkill //PID <pid> //F
| `/api/a2a/discover` | POST | Discover A2A agent at URL |
| `/api/a2a/send` | POST | Send task to external A2A agent |
| `/api/a2a/tasks/{id}/status` | GET | Check external A2A task status |
| `/api/approvals/{id}/approve` | POST | Approve (body: `{totp_code?}`) |
| `/api/approvals/totp/setup` | POST | Generate TOTP secret + URI |
| `/api/approvals/totp/confirm` | POST | Confirm TOTP enrollment |
| `/api/approvals/totp/status` | GET | Check TOTP enrollment status |
| `/api/approvals/totp` | DELETE | Revoke TOTP enrollment |

## Architecture Notes
- **Don't touch `librefang-cli`** — user is actively building the interactive CLI
Expand Down
89 changes: 89 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ rsa = "0.9"
rand = "0.10"
zeroize = { version = "1", features = ["derive"] }

# TOTP
totp-rs = { version = "5", features = ["gen_secret", "otpauth", "qr"] }

# JWT validation
jsonwebtoken = "10"

Expand Down
1 change: 1 addition & 0 deletions crates/librefang-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ axum = { workspace = true }
tower = { workspace = true }
tower-http = { workspace = true }
chrono = { workspace = true }
chrono-tz = { workspace = true }
uuid = { workspace = true }
futures = { workspace = true }
governor = { workspace = true }
Expand Down
48 changes: 46 additions & 2 deletions crates/librefang-api/dashboard/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ export interface ScheduleItem {
id: string;
name?: string;
cron?: string;
tz?: string | null;
description?: string;
message?: string;
enabled?: boolean;
Expand Down Expand Up @@ -1253,6 +1254,7 @@ export async function listSchedules(): Promise<ScheduleItem[]> {
export async function createSchedule(payload: {
name: string;
cron: string;
tz?: string;
agent_id?: string;
workflow_id?: string;
message?: string;
Expand All @@ -1267,6 +1269,7 @@ export async function updateSchedule(
enabled?: boolean;
name?: string;
cron?: string;
tz?: string;
agent_id?: string;
message?: string;
}
Expand Down Expand Up @@ -1478,8 +1481,49 @@ export async function listApprovals(): Promise<ApprovalItem[]> {
return data.approvals ?? [];
}

export async function approveApproval(id: string): Promise<ApiActionResponse> {
return post<ApiActionResponse>(`/api/approvals/${encodeURIComponent(id)}/approve`, {});
export async function approveApproval(id: string, totpCode?: string): Promise<ApiActionResponse> {
const body = totpCode ? { totp_code: totpCode } : {};
return post<ApiActionResponse>(`/api/approvals/${encodeURIComponent(id)}/approve`, body);
}

// ── TOTP second-factor management ──

export interface TotpSetupResponse {
otpauth_uri: string;
secret: string;
qr_code: string | null;
recovery_codes: string[];
message: string;
}

export interface TotpStatusResponse {
enrolled: boolean;
confirmed: boolean;
enforced: boolean;
remaining_recovery_codes: number;
}

export async function totpSetup(currentCode?: string): Promise<TotpSetupResponse> {
const body = currentCode ? { current_code: currentCode } : {};
return post<TotpSetupResponse>("/api/approvals/totp/setup", body);
}

export async function totpConfirm(code: string): Promise<ApiActionResponse> {
return post<ApiActionResponse>("/api/approvals/totp/confirm", { code });
}

export async function totpStatus(): Promise<TotpStatusResponse> {
return get<TotpStatusResponse>("/api/approvals/totp/status");
}

export async function totpRevoke(code: string): Promise<ApiActionResponse> {
const response = await fetch("/api/approvals/totp/revoke", {
method: "POST",
headers: buildHeaders({ "Content-Type": "application/json" }),
body: JSON.stringify({ code }),
});
if (!response.ok) throw await parseError(response);
return response.json();
}

export async function rejectApproval(id: string): Promise<ApiActionResponse> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Bell, Check, X, ExternalLink } from "lucide-react";
import { fetchApprovalCount, listApprovals, approveApproval, rejectApproval } from "../api";
import { fetchApprovalCount, listApprovals, approveApproval, rejectApproval, totpStatus } from "../api";
import { useTranslation } from "react-i18next";
import { useUIStore } from "../lib/store";
import { useNavigate } from "@tanstack/react-router";
Expand All @@ -26,12 +26,26 @@ export function NotificationCenter() {
refetchInterval: open ? 5000 : false,
});

const totpQuery = useQuery({
queryKey: ["totp", "status"],
queryFn: totpStatus,
staleTime: 60_000,
});
const totpEnforced = totpQuery.data?.enforced ?? false;

const pendingCount = countQuery.data ?? 0;
const pendingItems = (listQuery.data ?? []).filter(
(a) => !a.status || a.status === "pending"
);

const handleAction = async (id: string, action: "approve" | "reject") => {
// When TOTP is enforced, redirect to Approvals page for approve
if (action === "approve" && totpEnforced) {
setOpen(false);
navigate({ to: "/approvals" });
addToast(t("approvals.totpRequired", "TOTP code required. Use the Approvals page."), "info");
return;
}
try {
if (action === "approve") await approveApproval(id);
else await rejectApproval(id);
Expand Down
Loading
Loading