fix(electrobun): 7 bug fixes + 3 partial features completed
Bugs fixed: - Agent retry: clear dead session on failed start (was non-recoverable) - seqId persist: save seq_id column in agent_messages, migrate existing DBs - Window frame save: wire resize event listener to saveWindowFrame() - Diagnostics counters: real RPC call counter via withRpcCounting() proxy Partial features completed: - Search auto-seed: rebuildIndex() on startup + indexMessage() on save - Notifications: removed 3 hardcoded demo entries, start empty - Agent cost streaming: emitCostIfChanged() on every message batch New: src/bun/rpc-stats.ts (RPC call + dropped event counters)
This commit is contained in:
parent
0dd402e282
commit
66dce7ebae
8 changed files with 139 additions and 47 deletions
|
|
@ -4,11 +4,13 @@
|
||||||
|
|
||||||
import type { SidecarManager } from "../sidecar-manager.ts";
|
import type { SidecarManager } from "../sidecar-manager.ts";
|
||||||
import type { SessionDb } from "../session-db.ts";
|
import type { SessionDb } from "../session-db.ts";
|
||||||
|
import type { SearchDb } from "../search-db.ts";
|
||||||
|
|
||||||
export function createAgentHandlers(
|
export function createAgentHandlers(
|
||||||
sidecarManager: SidecarManager,
|
sidecarManager: SidecarManager,
|
||||||
sessionDb: SessionDb,
|
sessionDb: SessionDb,
|
||||||
sendToWebview: { send: Record<string, (...args: unknown[]) => void> },
|
sendToWebview: { send: Record<string, (...args: unknown[]) => void> },
|
||||||
|
searchDb?: SearchDb,
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
"agent.start": ({ sessionId, provider, prompt, cwd, model, systemPrompt, maxTurns, permissionMode, claudeConfigDir, extraEnv, additionalDirectories, worktreeName }: Record<string, unknown>) => {
|
"agent.start": ({ sessionId, provider, prompt, cwd, model, systemPrompt, maxTurns, permissionMode, claudeConfigDir, extraEnv, additionalDirectories, worktreeName }: Record<string, unknown>) => {
|
||||||
|
|
@ -21,12 +23,38 @@ export function createAgentHandlers(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
|
// Partial 3: Track last-sent cost to emit live updates only on change
|
||||||
|
let lastCostUsd = 0;
|
||||||
|
let lastInputTokens = 0;
|
||||||
|
let lastOutputTokens = 0;
|
||||||
|
|
||||||
|
/** Emit agent.cost if cost data changed since last emit. */
|
||||||
|
function emitCostIfChanged(sid: string): void {
|
||||||
|
const sessions = sidecarManager.listSessions();
|
||||||
|
const session = sessions.find((s: Record<string, unknown>) => s.sessionId === sid);
|
||||||
|
if (!session) return;
|
||||||
|
const cost = (session.costUsd as number) ?? 0;
|
||||||
|
const inTok = (session.inputTokens as number) ?? 0;
|
||||||
|
const outTok = (session.outputTokens as number) ?? 0;
|
||||||
|
if (cost === lastCostUsd && inTok === lastInputTokens && outTok === lastOutputTokens) return;
|
||||||
|
lastCostUsd = cost;
|
||||||
|
lastInputTokens = inTok;
|
||||||
|
lastOutputTokens = outTok;
|
||||||
|
try {
|
||||||
|
(sendToWebview.send as Record<string, Function>)["agent.cost"]({
|
||||||
|
sessionId: sid, costUsd: cost, inputTokens: inTok, outputTokens: outTok,
|
||||||
|
});
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
sidecarManager.onMessage(sessionId as string, (sid: string, messages: unknown) => {
|
sidecarManager.onMessage(sessionId as string, (sid: string, messages: unknown) => {
|
||||||
try {
|
try {
|
||||||
(sendToWebview.send as Record<string, Function>)["agent.message"]({ sessionId: sid, messages });
|
(sendToWebview.send as Record<string, Function>)["agent.message"]({ sessionId: sid, messages });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[agent.message] forward error:", err);
|
console.error("[agent.message] forward error:", err);
|
||||||
}
|
}
|
||||||
|
// Partial 3: Stream cost on every message batch
|
||||||
|
emitCostIfChanged(sid);
|
||||||
});
|
});
|
||||||
|
|
||||||
sidecarManager.onStatus(sessionId as string, (sid: string, status: string, error?: string) => {
|
sidecarManager.onStatus(sessionId as string, (sid: string, status: string, error?: string) => {
|
||||||
|
|
@ -35,19 +63,7 @@ export function createAgentHandlers(
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[agent.status] forward error:", err);
|
console.error("[agent.status] forward error:", err);
|
||||||
}
|
}
|
||||||
|
emitCostIfChanged(sid);
|
||||||
const sessions = sidecarManager.listSessions();
|
|
||||||
const session = sessions.find((s: Record<string, unknown>) => s.sessionId === sid);
|
|
||||||
if (session) {
|
|
||||||
try {
|
|
||||||
(sendToWebview.send as Record<string, Function>)["agent.cost"]({
|
|
||||||
sessionId: sid,
|
|
||||||
costUsd: session.costUsd,
|
|
||||||
inputTokens: session.inputTokens,
|
|
||||||
outputTokens: session.outputTokens,
|
|
||||||
});
|
|
||||||
} catch { /* ignore */ }
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,9 +138,22 @@ export function createAgentHandlers(
|
||||||
sessionDb.saveMessages(messages.map((m) => ({
|
sessionDb.saveMessages(messages.map((m) => ({
|
||||||
sessionId: m.sessionId, msgId: m.msgId, role: m.role,
|
sessionId: m.sessionId, msgId: m.msgId, role: m.role,
|
||||||
content: m.content, toolName: m.toolName, toolInput: m.toolInput,
|
content: m.content, toolName: m.toolName, toolInput: m.toolInput,
|
||||||
timestamp: m.timestamp, costUsd: (m.costUsd as number) ?? 0,
|
timestamp: m.timestamp, seqId: (m.seqId as number) ?? 0,
|
||||||
|
costUsd: (m.costUsd as number) ?? 0,
|
||||||
inputTokens: (m.inputTokens as number) ?? 0, outputTokens: (m.outputTokens as number) ?? 0,
|
inputTokens: (m.inputTokens as number) ?? 0, outputTokens: (m.outputTokens as number) ?? 0,
|
||||||
})));
|
})));
|
||||||
|
// Partial 1: Index each new message in FTS5 search
|
||||||
|
if (searchDb) {
|
||||||
|
for (const m of messages) {
|
||||||
|
try {
|
||||||
|
searchDb.indexMessage(
|
||||||
|
String(m.sessionId ?? ""),
|
||||||
|
String(m.role ?? ""),
|
||||||
|
String(m.content ?? ""),
|
||||||
|
);
|
||||||
|
} catch { /* non-critical — don't fail the save */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[session.messages.save]", err);
|
console.error("[session.messages.save]", err);
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import type { RelayClient } from "../relay-client.ts";
|
||||||
import type { SidecarManager } from "../sidecar-manager.ts";
|
import type { SidecarManager } from "../sidecar-manager.ts";
|
||||||
import { checkForUpdates, getLastCheckTimestamp } from "../updater.ts";
|
import { checkForUpdates, getLastCheckTimestamp } from "../updater.ts";
|
||||||
import type { TelemetryManager } from "../telemetry.ts";
|
import type { TelemetryManager } from "../telemetry.ts";
|
||||||
|
import { getRpcCallCount, getDroppedEvents } from "../rpc-stats.ts";
|
||||||
|
|
||||||
// ── Helpers ─────────────────────────────────────────────────────────────────
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -199,8 +200,8 @@ export function createMiscHandlers(deps: MiscDeps) {
|
||||||
ptyConnected: ptyClient.isConnected,
|
ptyConnected: ptyClient.isConnected,
|
||||||
relayConnections: relayClient.listMachines().filter((m) => m.status === "connected").length,
|
relayConnections: relayClient.listMachines().filter((m) => m.status === "connected").length,
|
||||||
activeSidecars: sidecarManager.listSessions().filter((s) => s.status === "running").length,
|
activeSidecars: sidecarManager.listSessions().filter((s) => s.status === "running").length,
|
||||||
rpcCallCount: 0,
|
rpcCallCount: getRpcCallCount(),
|
||||||
droppedEvents: 0,
|
droppedEvents: getDroppedEvents(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// ── Telemetry ───────────────────────────────────────────────────────
|
// ── Telemetry ───────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import { createRemoteHandlers } from "./handlers/remote-handlers.ts";
|
||||||
import { createGitHandlers } from "./handlers/git-handlers.ts";
|
import { createGitHandlers } from "./handlers/git-handlers.ts";
|
||||||
import { createProviderHandlers } from "./handlers/provider-handlers.ts";
|
import { createProviderHandlers } from "./handlers/provider-handlers.ts";
|
||||||
import { createMiscHandlers } from "./handlers/misc-handlers.ts";
|
import { createMiscHandlers } from "./handlers/misc-handlers.ts";
|
||||||
|
import { incrementRpcCallCount, incrementDroppedEvents } from "./rpc-stats.ts";
|
||||||
|
|
||||||
/** Current app version — sourced from electrobun.config.ts at build time. */
|
/** Current app version — sourced from electrobun.config.ts at build time. */
|
||||||
const APP_VERSION = "0.0.1";
|
const APP_VERSION = "0.0.1";
|
||||||
|
|
@ -74,7 +75,7 @@ const rpcRef: { send: Record<string, (...args: unknown[]) => void> } = { send: {
|
||||||
const ptyHandlers = createPtyHandlers(ptyClient);
|
const ptyHandlers = createPtyHandlers(ptyClient);
|
||||||
const filesHandlers = createFilesHandlers();
|
const filesHandlers = createFilesHandlers();
|
||||||
const settingsHandlers = createSettingsHandlers(settingsDb);
|
const settingsHandlers = createSettingsHandlers(settingsDb);
|
||||||
const agentHandlers = createAgentHandlers(sidecarManager, sessionDb, rpcRef);
|
const agentHandlers = createAgentHandlers(sidecarManager, sessionDb, rpcRef, searchDb);
|
||||||
const btmsgHandlers = createBtmsgHandlers(btmsgDb, rpcRef);
|
const btmsgHandlers = createBtmsgHandlers(btmsgDb, rpcRef);
|
||||||
const bttaskHandlers = createBttaskHandlers(bttaskDb, rpcRef);
|
const bttaskHandlers = createBttaskHandlers(bttaskDb, rpcRef);
|
||||||
const searchHandlers = createSearchHandlers(searchDb);
|
const searchHandlers = createSearchHandlers(searchDb);
|
||||||
|
|
@ -89,24 +90,42 @@ const miscHandlers = createMiscHandlers({
|
||||||
// Window ref — handlers use closure; set after mainWindow creation
|
// Window ref — handlers use closure; set after mainWindow creation
|
||||||
let mainWindow: BrowserWindow;
|
let mainWindow: BrowserWindow;
|
||||||
|
|
||||||
|
// ── RPC counter wrapper ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Wrap each handler to increment the RPC call counter on every invocation. */
|
||||||
|
function withRpcCounting<T extends Record<string, (...args: unknown[]) => unknown>>(handlers: T): T {
|
||||||
|
const wrapped: Record<string, (...args: unknown[]) => unknown> = {};
|
||||||
|
for (const [key, fn] of Object.entries(handlers)) {
|
||||||
|
wrapped[key] = (...args: unknown[]) => {
|
||||||
|
incrementRpcCallCount();
|
||||||
|
return fn(...args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return wrapped as T;
|
||||||
|
}
|
||||||
|
|
||||||
// ── RPC definition ─────────────────────────────────────────────────────────
|
// ── RPC definition ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const allHandlers = withRpcCounting({
|
||||||
|
...ptyHandlers,
|
||||||
|
...filesHandlers,
|
||||||
|
...settingsHandlers,
|
||||||
|
...agentHandlers,
|
||||||
|
...btmsgHandlers,
|
||||||
|
...bttaskHandlers,
|
||||||
|
...searchHandlers,
|
||||||
|
...pluginHandlers,
|
||||||
|
...remoteHandlers,
|
||||||
|
...gitHandlers,
|
||||||
|
...providerHandlers,
|
||||||
|
...miscHandlers,
|
||||||
|
});
|
||||||
|
|
||||||
const rpc = BrowserView.defineRPC<PtyRPCSchema>({
|
const rpc = BrowserView.defineRPC<PtyRPCSchema>({
|
||||||
maxRequestTime: 120_000,
|
maxRequestTime: 120_000,
|
||||||
handlers: {
|
handlers: {
|
||||||
requests: {
|
requests: {
|
||||||
...ptyHandlers,
|
...allHandlers,
|
||||||
...filesHandlers,
|
|
||||||
...settingsHandlers,
|
|
||||||
...agentHandlers,
|
|
||||||
...btmsgHandlers,
|
|
||||||
...bttaskHandlers,
|
|
||||||
...searchHandlers,
|
|
||||||
...pluginHandlers,
|
|
||||||
...remoteHandlers,
|
|
||||||
...gitHandlers,
|
|
||||||
...providerHandlers,
|
|
||||||
...miscHandlers,
|
|
||||||
|
|
||||||
// GTK native drag/resize — delegates to window manager (zero CPU)
|
// GTK native drag/resize — delegates to window manager (zero CPU)
|
||||||
"window.beginResize": ({ edge }: { edge: string }) => {
|
"window.beginResize": ({ edge }: { edge: string }) => {
|
||||||
|
|
@ -202,13 +221,6 @@ relayClient.onStatus((machineId, status, error) => {
|
||||||
// ── App window ────────────────────────────────────────────────────────────
|
// ── App window ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function getMainViewUrl(): Promise<string> {
|
async function getMainViewUrl(): Promise<string> {
|
||||||
// TEMPORARY: load resize test stub via Vite dev server (keeps RPC bridge)
|
|
||||||
const RESIZE_TEST = false; // DISABLED — real app mode
|
|
||||||
if (RESIZE_TEST) {
|
|
||||||
const testUrl = DEV_SERVER_URL + "/resize-test.html";
|
|
||||||
console.log(`[RESIZE_TEST] Loading stub via Vite: ${testUrl}`);
|
|
||||||
return testUrl;
|
|
||||||
}
|
|
||||||
const channel = await Updater.localInfo.channel();
|
const channel = await Updater.localInfo.channel();
|
||||||
if (channel === "dev") {
|
if (channel === "dev") {
|
||||||
try {
|
try {
|
||||||
|
|
@ -224,6 +236,14 @@ async function getMainViewUrl(): Promise<string> {
|
||||||
|
|
||||||
connectToDaemon();
|
connectToDaemon();
|
||||||
|
|
||||||
|
// Partial 1: Seed FTS5 search index on startup
|
||||||
|
try {
|
||||||
|
searchDb.rebuildIndex();
|
||||||
|
console.log("[search] FTS5 index seeded on startup");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[search] Failed to seed FTS5 index:", err);
|
||||||
|
}
|
||||||
|
|
||||||
const url = await getMainViewUrl();
|
const url = await getMainViewUrl();
|
||||||
|
|
||||||
const savedX = Number(settingsDb.getSetting("win_x") ?? 100);
|
const savedX = Number(settingsDb.getSetting("win_x") ?? 100);
|
||||||
|
|
|
||||||
23
ui-electrobun/src/bun/rpc-stats.ts
Normal file
23
ui-electrobun/src/bun/rpc-stats.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Simple RPC call counter for diagnostics.
|
||||||
|
* Incremented on each RPC request, read by diagnostics.stats handler.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let _rpcCallCount = 0;
|
||||||
|
let _droppedEvents = 0;
|
||||||
|
|
||||||
|
export function incrementRpcCallCount(): void {
|
||||||
|
_rpcCallCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function incrementDroppedEvents(): void {
|
||||||
|
_droppedEvents++;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRpcCallCount(): number {
|
||||||
|
return _rpcCallCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDroppedEvents(): number {
|
||||||
|
return _droppedEvents;
|
||||||
|
}
|
||||||
|
|
@ -39,6 +39,7 @@ export interface StoredMessage {
|
||||||
toolName?: string;
|
toolName?: string;
|
||||||
toolInput?: string;
|
toolInput?: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
seqId: number;
|
||||||
costUsd: number;
|
costUsd: number;
|
||||||
inputTokens: number;
|
inputTokens: number;
|
||||||
outputTokens: number;
|
outputTokens: number;
|
||||||
|
|
@ -72,6 +73,7 @@ CREATE TABLE IF NOT EXISTS agent_messages (
|
||||||
tool_name TEXT,
|
tool_name TEXT,
|
||||||
tool_input TEXT,
|
tool_input TEXT,
|
||||||
timestamp INTEGER NOT NULL,
|
timestamp INTEGER NOT NULL,
|
||||||
|
seq_id INTEGER NOT NULL DEFAULT 0,
|
||||||
cost_usd REAL NOT NULL DEFAULT 0,
|
cost_usd REAL NOT NULL DEFAULT 0,
|
||||||
input_tokens INTEGER NOT NULL DEFAULT 0,
|
input_tokens INTEGER NOT NULL DEFAULT 0,
|
||||||
output_tokens INTEGER NOT NULL DEFAULT 0,
|
output_tokens INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
@ -91,6 +93,17 @@ export class SessionDb {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.db = openDb(DB_PATH, { foreignKeys: true });
|
this.db = openDb(DB_PATH, { foreignKeys: true });
|
||||||
this.db.exec(SESSION_SCHEMA);
|
this.db.exec(SESSION_SCHEMA);
|
||||||
|
this.migrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Run schema migrations for existing DBs. */
|
||||||
|
private migrate(): void {
|
||||||
|
// Add seq_id column if missing (for DBs created before this field existed)
|
||||||
|
try {
|
||||||
|
this.db.run("ALTER TABLE agent_messages ADD COLUMN seq_id INTEGER NOT NULL DEFAULT 0");
|
||||||
|
} catch {
|
||||||
|
// Column already exists — expected
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Sessions ─────────────────────────────────────────────────────────────
|
// ── Sessions ─────────────────────────────────────────────────────────────
|
||||||
|
|
@ -212,8 +225,8 @@ export class SessionDb {
|
||||||
.query(
|
.query(
|
||||||
`INSERT INTO agent_messages
|
`INSERT INTO agent_messages
|
||||||
(session_id, msg_id, role, content, tool_name, tool_input,
|
(session_id, msg_id, role, content, tool_name, tool_input,
|
||||||
timestamp, cost_usd, input_tokens, output_tokens)
|
timestamp, seq_id, cost_usd, input_tokens, output_tokens)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
|
||||||
ON CONFLICT(session_id, msg_id) DO NOTHING`
|
ON CONFLICT(session_id, msg_id) DO NOTHING`
|
||||||
)
|
)
|
||||||
.run(
|
.run(
|
||||||
|
|
@ -224,6 +237,7 @@ export class SessionDb {
|
||||||
m.toolName ?? null,
|
m.toolName ?? null,
|
||||||
m.toolInput ?? null,
|
m.toolInput ?? null,
|
||||||
m.timestamp,
|
m.timestamp,
|
||||||
|
m.seqId ?? 0,
|
||||||
m.costUsd,
|
m.costUsd,
|
||||||
m.inputTokens,
|
m.inputTokens,
|
||||||
m.outputTokens
|
m.outputTokens
|
||||||
|
|
@ -234,8 +248,8 @@ export class SessionDb {
|
||||||
const stmt = this.db.prepare(
|
const stmt = this.db.prepare(
|
||||||
`INSERT INTO agent_messages
|
`INSERT INTO agent_messages
|
||||||
(session_id, msg_id, role, content, tool_name, tool_input,
|
(session_id, msg_id, role, content, tool_name, tool_input,
|
||||||
timestamp, cost_usd, input_tokens, output_tokens)
|
timestamp, seq_id, cost_usd, input_tokens, output_tokens)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
|
||||||
ON CONFLICT(session_id, msg_id) DO NOTHING`
|
ON CONFLICT(session_id, msg_id) DO NOTHING`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -249,6 +263,7 @@ export class SessionDb {
|
||||||
m.toolName ?? null,
|
m.toolName ?? null,
|
||||||
m.toolInput ?? null,
|
m.toolInput ?? null,
|
||||||
m.timestamp,
|
m.timestamp,
|
||||||
|
m.seqId ?? 0,
|
||||||
m.costUsd,
|
m.costUsd,
|
||||||
m.inputTokens,
|
m.inputTokens,
|
||||||
m.outputTokens
|
m.outputTokens
|
||||||
|
|
@ -269,6 +284,7 @@ export class SessionDb {
|
||||||
tool_name: string | null;
|
tool_name: string | null;
|
||||||
tool_input: string | null;
|
tool_input: string | null;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
seq_id: number;
|
||||||
cost_usd: number;
|
cost_usd: number;
|
||||||
input_tokens: number;
|
input_tokens: number;
|
||||||
output_tokens: number;
|
output_tokens: number;
|
||||||
|
|
@ -289,6 +305,7 @@ export class SessionDb {
|
||||||
toolName: r.tool_name ?? undefined,
|
toolName: r.tool_name ?? undefined,
|
||||||
toolInput: r.tool_input ?? undefined,
|
toolInput: r.tool_input ?? undefined,
|
||||||
timestamp: r.timestamp,
|
timestamp: r.timestamp,
|
||||||
|
seqId: r.seq_id ?? 0,
|
||||||
costUsd: r.cost_usd,
|
costUsd: r.cost_usd,
|
||||||
inputTokens: r.input_tokens,
|
inputTokens: r.input_tokens,
|
||||||
outputTokens: r.output_tokens,
|
outputTokens: r.output_tokens,
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,9 @@
|
||||||
keybindingStore.on("group4", () => setActiveGroup(getGroups()[3]?.id));
|
keybindingStore.on("group4", () => setActiveGroup(getGroups()[3]?.id));
|
||||||
keybindingStore.on("minimize", () => handleMinimize());
|
keybindingStore.on("minimize", () => handleMinimize());
|
||||||
|
|
||||||
|
// Bug 3: Persist window frame on resize (debounced inside saveWindowFrame)
|
||||||
|
window.addEventListener("resize", saveWindowFrame);
|
||||||
|
|
||||||
function handleSearchShortcut(e: KeyboardEvent) {
|
function handleSearchShortcut(e: KeyboardEvent) {
|
||||||
if (e.ctrlKey && e.shiftKey && e.key === "F") {
|
if (e.ctrlKey && e.shiftKey && e.key === "F") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -316,7 +319,7 @@
|
||||||
clearInterval(sessionId);
|
clearInterval(sessionId);
|
||||||
document.removeEventListener("keydown", handleSearchShortcut);
|
document.removeEventListener("keydown", handleSearchShortcut);
|
||||||
window.removeEventListener("palette-command", handlePaletteCommand);
|
window.removeEventListener("palette-command", handlePaletteCommand);
|
||||||
// Native resize — no JS listeners to clean
|
window.removeEventListener("resize", saveWindowFrame);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -549,6 +549,9 @@ async function _startAgentInner(
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
sessions[sessionId].status = 'error';
|
sessions[sessionId].status = 'error';
|
||||||
sessions[sessionId].error = result.error;
|
sessions[sessionId].error = result.error;
|
||||||
|
// Bug 1: Clear the dead session so the next send starts fresh
|
||||||
|
projectSessionMap.delete(projectId);
|
||||||
|
clearStallTimer(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,9 @@ export interface Notification {
|
||||||
|
|
||||||
// ── State ─────────────────────────────────────────────────────────────────
|
// ── State ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
let notifications = $state<Notification[]>([
|
let notifications = $state<Notification[]>([]);
|
||||||
{ id: 1, message: 'Agent completed: wake scheduler implemented', type: 'success', time: '2m ago' },
|
|
||||||
{ id: 2, message: 'Context pressure: 78% on agent-orchestrator', type: 'warning', time: '5m ago' },
|
|
||||||
{ id: 3, message: 'PTY daemon connected', type: 'info', time: '12m ago' },
|
|
||||||
]);
|
|
||||||
|
|
||||||
let nextId = $state(100);
|
let nextId = $state(1);
|
||||||
|
|
||||||
// ── Public API ────────────────────────────────────────────────────────────
|
// ── Public API ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue