Add security hardening, Ghidra, recreate skill, checkpoints, shells, web search and themes

- Sandbox (bubblewrap) for non-admin shell/git, MCP per-user rights, hard budgets and quotas,
  login rate limiting, audit log and backups
- Ghidra headless decompilation (ARM64 natives built from source), binary_info tool,
  clean room / deep workflow per session
- Recreate skill and commands for analysing and re-building old software
- File checkpoints with rewind/restore, background shells (bash_output, kill_shell)
- web_search tool (DuckDuckGo, SearXNG, Brave, Tavily)
- In-app dialogs and toasts instead of browser dialogs
- 15 base themes (trilium-themes palettes plus Nord, VS Code, GitHub, Catppuccin, One,
  Dracula, Rose Pine, Everforest, Kanagawa) with light/dark/auto switcher and license notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-08 19:12:30 +01:00
1 parent fab9add5d5
commit 5b569b6835
108 files changed
+6785 -279

No files matched your search

+14 -1
View File
@@ -39,6 +39,8 @@ export interface State {
questions: QuestionRequest[];
errors: Record<string, SessionError[]>;
mcp: Record<string, McpServerStatus>;
/** The model request currently running per session (activity line). */
steps: Record<string, { messageId: string; startedAt: number; promptTokens: number; estimated: boolean } | undefined>;
/** Messages waiting for a busy session. */
queue: Record<string, QueueItem[]>;
/** Why a session waits: "model:<spec>" or "subagent:<name>". */
@@ -60,6 +62,7 @@ let state: State = {
errors: {},
mcp: {},
queue: {},
steps: {},
waiting: {},
};
@@ -114,6 +117,7 @@ export function applyEvent(ev: ServerEvent) {
setState((s) => ({
busy: { ...s.busy, [ev.session_id]: ev.busy },
drafts: ev.busy ? s.drafts : { ...s.drafts, [ev.session_id]: undefined },
steps: ev.busy ? s.steps : { ...s.steps, [ev.session_id]: undefined },
}));
break;
case "message_added":
@@ -170,6 +174,11 @@ export function applyEvent(ev: ServerEvent) {
},
}));
break;
case "step_started":
setState((s) => ({
steps: { ...s.steps, [ev.session_id]: { messageId: ev.message_id, startedAt: ev.started_at, promptTokens: ev.prompt_tokens, estimated: ev.estimated } },
}));
break;
case "queue_updated":
setState((s) => ({ queue: { ...s.queue, [ev.session_id]: ev.items } }));
break;
@@ -207,7 +216,7 @@ export async function loadSessions() {
}
export async function loadSession(id: string) {
const data = await get<{ session: Session; messages: Message[]; busy: boolean; can_write: boolean; queue: QueueItem[] }>(`/sessions/${id}`);
const data = await get<{ session: Session; messages: Message[]; busy: boolean; can_write: boolean; queue: QueueItem[]; step?: any }>(`/sessions/${id}`);
setState((s) => ({
...upsertSession(s, data.session),
sessionById: { ...s.sessionById, [id]: data.session },
@@ -215,6 +224,10 @@ export async function loadSession(id: string) {
busy: { ...s.busy, [id]: data.busy },
readOnly: { ...s.readOnly, [id]: !data.can_write },
queue: { ...s.queue, [id]: data.queue ?? [] },
steps: {
...s.steps,
[id]: data.step ? { messageId: data.step.message_id, startedAt: data.step.started_at, promptTokens: data.step.prompt_tokens, estimated: data.step.estimated } : s.steps[id],
},
}));
}