feat(electrobun): final 5% — full integration, real data, polish
1. Claude CLI: additionalDirectories + worktreeName passthrough 2. Agent-store: reads settings (default_cwd, provider model, permission) 3. Project hydration: SQLite replaces hardcoded PROJECTS, add/remove UI 4. Group hydration: SQLite groups, add/delete in sidebar 5. Terminal auto-spawn: reads default_cwd from settings 6. Context tab: real tokens from agent-store, file refs, turn count 7. Memory tab: Memora DB integration (read-only, graceful if missing) 8. Docs tab: markdown viewer (files.list + files.read + inline renderer) 9. SSH tab: CRUD connections, spawn PTY with ssh command 10. Error handling: global unhandledrejection → toast notifications 11. Notifications: agent done/error/stall → toasts, 15min stall timer 12. Command palette: all 18 commands (was 10) +1,198 lines, 13 files. Electrobun now 100% feature-complete vs Tauri v3.
This commit is contained in:
parent
4826b9dffa
commit
8e756d3523
13 changed files with 1199 additions and 239 deletions
|
|
@ -43,6 +43,48 @@ interface StartOptions {
|
|||
permissionMode?: string;
|
||||
claudeConfigDir?: string;
|
||||
extraEnv?: Record<string, string>;
|
||||
additionalDirectories?: string[];
|
||||
worktreeName?: string;
|
||||
}
|
||||
|
||||
// ── Toast callback (set by App.svelte) ────────────────────────────────────────
|
||||
|
||||
type ToastFn = (message: string, variant: 'success' | 'warning' | 'error' | 'info') => void;
|
||||
let _toastFn: ToastFn | null = null;
|
||||
|
||||
/** Register a toast callback for agent notifications. */
|
||||
export function setAgentToastFn(fn: ToastFn): void { _toastFn = fn; }
|
||||
|
||||
function emitToast(message: string, variant: 'success' | 'warning' | 'error' | 'info') {
|
||||
_toastFn?.(message, variant);
|
||||
}
|
||||
|
||||
// ── Stall detection ───────────────────────────────────────────────────────────
|
||||
|
||||
const stallTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
const DEFAULT_STALL_MS = 15 * 60 * 1000; // 15 minutes
|
||||
|
||||
function resetStallTimer(sessionId: string, projectId: string): void {
|
||||
const existing = stallTimers.get(sessionId);
|
||||
if (existing) clearTimeout(existing);
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
stallTimers.delete(sessionId);
|
||||
const session = sessions[sessionId];
|
||||
if (session && session.status === 'running') {
|
||||
emitToast(`Agent stalled on ${projectId} (no activity for 15 min)`, 'warning');
|
||||
}
|
||||
}, DEFAULT_STALL_MS);
|
||||
|
||||
stallTimers.set(sessionId, timer);
|
||||
}
|
||||
|
||||
function clearStallTimer(sessionId: string): void {
|
||||
const timer = stallTimers.get(sessionId);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
stallTimers.delete(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Env var validation (Fix #14) ─────────────────────────────────────────────
|
||||
|
|
@ -153,6 +195,8 @@ function ensureListeners() {
|
|||
if (converted.length > 0) {
|
||||
session.messages = [...session.messages, ...converted];
|
||||
persistMessages(session);
|
||||
// Reset stall timer on activity
|
||||
resetStallTimer(payload.sessionId, session.projectId);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -171,6 +215,15 @@ function ensureListeners() {
|
|||
// Persist on every status change
|
||||
persistSession(session);
|
||||
|
||||
// Emit toast notification on completion
|
||||
if (session.status === 'done') {
|
||||
clearStallTimer(payload.sessionId);
|
||||
emitToast(`Agent completed on ${session.projectId}`, 'success');
|
||||
} else if (session.status === 'error') {
|
||||
clearStallTimer(payload.sessionId);
|
||||
emitToast(`Agent error on ${session.projectId}: ${payload.error ?? 'unknown'}`, 'error');
|
||||
}
|
||||
|
||||
// Schedule cleanup after done/error (Fix #2)
|
||||
if (session.status === 'done' || session.status === 'error') {
|
||||
// Flush any pending message persistence immediately
|
||||
|
|
@ -366,6 +419,8 @@ export async function startAgent(
|
|||
// Read settings defaults if not explicitly provided (Fix #5)
|
||||
let permissionMode = options.permissionMode;
|
||||
let systemPrompt = options.systemPrompt;
|
||||
let defaultModel = options.model;
|
||||
let cwd = options.cwd;
|
||||
try {
|
||||
const { settings } = await appRpc.request['settings.getAll']({});
|
||||
if (!permissionMode && settings['permission_mode']) {
|
||||
|
|
@ -374,6 +429,17 @@ export async function startAgent(
|
|||
if (!systemPrompt && settings['system_prompt_template']) {
|
||||
systemPrompt = settings['system_prompt_template'];
|
||||
}
|
||||
if (!cwd && settings['default_cwd']) {
|
||||
cwd = settings['default_cwd'];
|
||||
}
|
||||
// Read default model from provider_settings if not specified
|
||||
if (!defaultModel && settings['provider_settings']) {
|
||||
try {
|
||||
const providerSettings = JSON.parse(settings['provider_settings']);
|
||||
const provConfig = providerSettings[provider];
|
||||
if (provConfig?.defaultModel) defaultModel = provConfig.defaultModel;
|
||||
} catch { /* ignore parse errors */ }
|
||||
}
|
||||
} catch { /* use provided or defaults */ }
|
||||
|
||||
// Create reactive session state
|
||||
|
|
@ -391,17 +457,18 @@ export async function startAgent(
|
|||
costUsd: 0,
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
model: options.model ?? 'claude-opus-4-5',
|
||||
model: defaultModel ?? 'claude-opus-4-5',
|
||||
};
|
||||
|
||||
projectSessionMap.set(projectId, sessionId);
|
||||
resetStallTimer(sessionId, projectId);
|
||||
|
||||
const result = await appRpc.request['agent.start']({
|
||||
sessionId,
|
||||
provider: provider as 'claude' | 'codex' | 'ollama',
|
||||
prompt,
|
||||
cwd: options.cwd,
|
||||
model: options.model,
|
||||
cwd,
|
||||
model: defaultModel,
|
||||
systemPrompt: systemPrompt,
|
||||
maxTurns: options.maxTurns,
|
||||
permissionMode: permissionMode,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue