forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagentCards.ts
More file actions
100 lines (91 loc) · 4.33 KB
/
Copy pathagentCards.ts
File metadata and controls
100 lines (91 loc) · 4.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// Shared-thread agent cards (B4, INV-CHAT-1) — main-side materialization + IPC.
//
// A background agent spawned from a chat/voice surface leaves EXACTLY TWO durable
// artifacts on the producing surface's kernel conversation: an agentSpawn card at
// launch and one agentCompletion card at terminal. This module is the always-alive
// writer: it subscribes to the kernel event stream (mirroring mainChat.ts) and, on
// a background run's launch (`run.queued`) and terminal (`run.succeeded/failed/
// cancelled`), asks the kernel to materialize the corresponding card. Both writes
// are idempotent on runId (agentThreadCards.ts), so a duplicate/retried terminal
// still yields exactly one completion.
//
// Keeping the writer in main (not the bar renderer's pill poll) means the two
// authoritative cards land regardless of which windows are open — the renderer
// then projects them via `agentCards:get` on load and the live `agentCards:event`.
// Card projection is a pure observer: any failure is swallowed so it can never
// destabilize the kernel event loop or a chat turn.
import { BrowserWindow, ipcMain } from 'electron'
import { controlPlaneOwnerId, getAgentRuntimeKernel } from '../agentKernel/controlPlane'
import type { MaterializedAgentCard } from '../agentKernel/agentThreadCards'
import type { AgentThreadCardMsg } from '../../shared/types'
/** Kernel run-lifecycle event types that mean a background run reached terminal —
* ALL FIVE terminal states. `run.timed_out` / `run.orphaned` are handled live too;
* the load-time sweep below is what actually heals `run.orphaned` from startup
* reconciliation (which is emitted below the kernel and never reaches subscribers). */
const TERMINAL_RUN_EVENT_TYPES = new Set([
'run.succeeded',
'run.failed',
'run.cancelled',
'run.timed_out',
'run.orphaned'
])
function toMsg(card: MaterializedAgentCard): AgentThreadCardMsg {
return { chatId: card.chatId, createdAtMs: card.record.createdAtMs, block: card.record.block }
}
function broadcast(card: MaterializedAgentCard): void {
const msg = toMsg(card)
for (const win of BrowserWindow.getAllWindows()) {
if (!win.isDestroyed()) win.webContents.send('agentCards:event', msg)
}
}
let subscribed = false
export function registerAgentCardHandlers(): void {
const kernel = getAgentRuntimeKernel()
// Subscribe exactly once — the kernel singleton outlives this call.
if (!subscribed) {
subscribed = true
kernel.subscribe((event) => {
try {
const runId = event.runId
if (typeof runId !== 'string' || !runId) return
if (event.type === 'run.queued') {
const card = kernel.materializeAgentSpawnCard(runId)
if (card) broadcast(card)
} else if (TERMINAL_RUN_EVENT_TYPES.has(event.type)) {
const card = kernel.materializeAgentCompletionCard(runId)
if (card) broadcast(card)
}
} catch {
// Observer only — never let a card write destabilize the event loop.
}
})
// Load-time heal: a run that crashed mid-flight or was orphaned by startup
// reconciliation reaches a terminal db state with a spawn card but no
// completion card, and reconciliation's `run.orphaned` is written below the
// kernel (never through notifySubscribers), so the live subscriber above can
// never see it. Sweep those to a completion card once, here, so a stuck
// "Running" spawn card always resolves. Bounded + idempotent; fail-open.
try {
for (const card of kernel.sweepOrphanedAgentCompletionCards()) broadcast(card)
} catch {
// Observer only — a heal failure must never block boot.
}
}
// Renderer projection read: the shared-thread cards for a main_chat thread. Read
// on chat load so a completion that landed while this window was closed still
// shows. Owner is host state (control-plane owner), never renderer-asserted.
ipcMain.handle('agentCards:get', (_e, chatId: unknown): AgentThreadCardMsg[] => {
const resolvedChatId = typeof chatId === 'string' && chatId.trim() ? chatId.trim() : 'default'
try {
return getAgentRuntimeKernel()
.listAgentThreadCardsForMainChat(controlPlaneOwnerId(), resolvedChatId)
.map((record) => ({
chatId: resolvedChatId,
createdAtMs: record.createdAtMs,
block: record.block
}))
} catch {
return []
}
})
}