refactor: migrate all 7 bridge clusters to BackendAdapter (WIP)

70+ files changed, net -688 lines. Bridge files being replaced with
BackendAdapter calls. Clusters 2-8 in progress: theme, groups/workspace,
agent, PTY/terminal, files, orchestration, infrastructure.
This commit is contained in:
Hibryda 2026-03-22 04:28:12 +01:00
parent 579157f6da
commit 105107dd84
72 changed files with 1835 additions and 2523 deletions

View file

@ -1,9 +1,14 @@
// BackendAdapter — abstraction layer for Tauri and Electrobun backends
import type { AgentStartOptions, AgentMessage, AgentStatus } from './agent';
import type { FileEntry, FileContent, PtyCreateOptions } from './protocol';
import type { AgentStartOptions, AgentMessage, AgentStatus, ProviderId } from './agent';
import type { FileEntry, FileContent, PtyCreateOptions, SearchResult } from './protocol';
import type { SettingsMap } from './settings';
import type { GroupsFile } from './project';
import type { AgentId, GroupId, SessionId, ProjectId } from './ids';
import type {
BtmsgAgent, BtmsgMessage, BtmsgChannel, BtmsgChannelMessage, DeadLetterEntry, AuditEntry,
} from './btmsg';
import type { Task, TaskComment } from './bttask';
// ── Backend capabilities ─────────────────────────────────────────────────────
@ -31,9 +36,223 @@ export interface BackendCapabilities {
/** Call to remove an event listener */
export type UnsubscribeFn = () => void;
// ── Domain-specific sub-interfaces ──────────────────────────────────────────
export interface SessionPersistenceAdapter {
listSessions(): Promise<PersistedSession[]>;
saveSession(session: PersistedSession): Promise<void>;
deleteSession(id: string): Promise<void>;
updateSessionTitle(id: string, title: string): Promise<void>;
touchSession(id: string): Promise<void>;
updateSessionGroup(id: string, groupName: string): Promise<void>;
saveLayout(layout: PersistedLayout): Promise<void>;
loadLayout(): Promise<PersistedLayout>;
}
export interface AgentPersistenceAdapter {
saveAgentMessages(
sessionId: SessionId, projectId: ProjectId, sdkSessionId: string | undefined,
messages: AgentMessageRecord[],
): Promise<void>;
loadAgentMessages(projectId: ProjectId): Promise<AgentMessageRecord[]>;
saveProjectAgentState(state: ProjectAgentState): Promise<void>;
loadProjectAgentState(projectId: ProjectId): Promise<ProjectAgentState | null>;
saveSessionMetric(metric: Omit<SessionMetricRecord, 'id'>): Promise<void>;
loadSessionMetrics(projectId: ProjectId, limit?: number): Promise<SessionMetricRecord[]>;
getCliGroup(): Promise<string | null>;
discoverMarkdownFiles(cwd: string): Promise<MdFileEntry[]>;
}
export interface BtmsgAdapter {
btmsgGetAgents(groupId: GroupId): Promise<BtmsgAgent[]>;
btmsgUnreadCount(agentId: AgentId): Promise<number>;
btmsgUnreadMessages(agentId: AgentId): Promise<BtmsgMessage[]>;
btmsgHistory(agentId: AgentId, otherId: AgentId, limit?: number): Promise<BtmsgMessage[]>;
btmsgSend(fromAgent: AgentId, toAgent: AgentId, content: string): Promise<string>;
btmsgSetStatus(agentId: AgentId, status: string): Promise<void>;
btmsgEnsureAdmin(groupId: GroupId): Promise<void>;
btmsgAllFeed(groupId: GroupId, limit?: number): Promise<BtmsgFeedMessage[]>;
btmsgMarkRead(readerId: AgentId, senderId: AgentId): Promise<void>;
btmsgGetChannels(groupId: GroupId): Promise<BtmsgChannel[]>;
btmsgChannelMessages(channelId: string, limit?: number): Promise<BtmsgChannelMessage[]>;
btmsgChannelSend(channelId: string, fromAgent: AgentId, content: string): Promise<string>;
btmsgCreateChannel(name: string, groupId: GroupId, createdBy: AgentId): Promise<string>;
btmsgAddChannelMember(channelId: string, agentId: AgentId): Promise<void>;
btmsgRegisterAgents(config: GroupsFile): Promise<void>;
btmsgUnseenMessages(agentId: AgentId, sessionId: string): Promise<BtmsgMessage[]>;
btmsgMarkSeen(sessionId: string, messageIds: string[]): Promise<void>;
btmsgPruneSeen(): Promise<number>;
btmsgRecordHeartbeat(agentId: AgentId): Promise<void>;
btmsgGetStaleAgents(groupId: GroupId, thresholdSecs?: number): Promise<string[]>;
btmsgGetDeadLetters(groupId: GroupId, limit?: number): Promise<DeadLetterEntry[]>;
btmsgClearDeadLetters(groupId: GroupId): Promise<void>;
btmsgClearAllComms(groupId: GroupId): Promise<void>;
}
export interface BttaskAdapter {
bttaskList(groupId: GroupId): Promise<Task[]>;
bttaskComments(taskId: string): Promise<TaskComment[]>;
bttaskUpdateStatus(taskId: string, status: string, version: number): Promise<number>;
bttaskAddComment(taskId: string, agentId: AgentId, content: string): Promise<string>;
bttaskCreate(
title: string, description: string, priority: string,
groupId: GroupId, createdBy: AgentId, assignedTo?: AgentId,
): Promise<string>;
bttaskDelete(taskId: string): Promise<void>;
bttaskReviewQueueCount(groupId: GroupId): Promise<number>;
}
export interface AnchorsAdapter {
saveSessionAnchors(anchors: SessionAnchorRecord[]): Promise<void>;
loadSessionAnchors(projectId: string): Promise<SessionAnchorRecord[]>;
deleteSessionAnchor(id: string): Promise<void>;
clearProjectAnchors(projectId: string): Promise<void>;
updateAnchorType(id: string, anchorType: string): Promise<void>;
}
export interface SearchAdapter {
searchInit(): Promise<void>;
searchAll(query: string, limit?: number): Promise<SearchResult[]>;
searchRebuild(): Promise<void>;
searchIndexMessage(sessionId: string, role: string, content: string): Promise<void>;
}
export interface AuditAdapter {
logAuditEvent(agentId: AgentId, eventType: AuditEventType, detail: string): Promise<void>;
getAuditLog(groupId: GroupId, limit?: number, offset?: number): Promise<AuditEntry[]>;
getAuditLogForAgent(agentId: AgentId, limit?: number): Promise<AuditEntry[]>;
}
export interface NotificationsAdapter {
sendDesktopNotification(title: string, body: string, urgency?: NotificationUrgency): void;
}
export interface TelemetryAdapter {
telemetryLog(level: TelemetryLevel, message: string, context?: Record<string, unknown>): void;
}
export interface SecretsAdapter {
storeSecret(key: string, value: string): Promise<void>;
getSecret(key: string): Promise<string | null>;
deleteSecret(key: string): Promise<void>;
listSecrets(): Promise<string[]>;
hasKeyring(): Promise<boolean>;
knownSecretKeys(): Promise<string[]>;
}
export interface FsWatcherAdapter {
fsWatchProject(projectId: string, cwd: string): Promise<void>;
fsUnwatchProject(projectId: string): Promise<void>;
onFsWriteDetected(callback: (event: FsWriteEvent) => void): UnsubscribeFn;
fsWatcherStatus(): Promise<FsWatcherStatus>;
}
export interface CtxAdapter {
ctxInitDb(): Promise<void>;
ctxRegisterProject(name: string, description: string, workDir?: string): Promise<void>;
ctxGetContext(project: string): Promise<CtxEntry[]>;
ctxGetShared(): Promise<CtxEntry[]>;
ctxGetSummaries(project: string, limit?: number): Promise<CtxSummary[]>;
ctxSearch(query: string): Promise<CtxEntry[]>;
}
export interface MemoraAdapter {
memoraAvailable(): Promise<boolean>;
memoraList(options?: { tags?: string[]; limit?: number; offset?: number }): Promise<MemoraSearchResult>;
memoraSearch(query: string, options?: { tags?: string[]; limit?: number }): Promise<MemoraSearchResult>;
memoraGet(id: number): Promise<MemoraNode | null>;
}
export interface SshAdapter {
listSshSessions(): Promise<SshSessionRecord[]>;
saveSshSession(session: SshSessionRecord): Promise<void>;
deleteSshSession(id: string): Promise<void>;
}
export interface PluginsAdapter {
discoverPlugins(): Promise<PluginMeta[]>;
readPluginFile(pluginId: string, filename: string): Promise<string>;
}
export interface ClaudeProviderAdapter {
listProfiles(): Promise<ClaudeProfile[]>;
listSkills(): Promise<ClaudeSkill[]>;
readSkill(path: string): Promise<string>;
}
export interface RemoteMachineAdapter {
listRemoteMachines(): Promise<RemoteMachineInfo[]>;
addRemoteMachine(config: RemoteMachineConfig): Promise<string>;
removeRemoteMachine(machineId: string): Promise<void>;
connectRemoteMachine(machineId: string): Promise<void>;
disconnectRemoteMachine(machineId: string): Promise<void>;
probeSpki(url: string): Promise<string>;
addSpkiPin(machineId: string, pin: string): Promise<void>;
removeSpkiPin(machineId: string, pin: string): Promise<void>;
onRemoteSidecarMessage(callback: (msg: RemoteSidecarMessage) => void): UnsubscribeFn;
onRemotePtyData(callback: (msg: RemotePtyData) => void): UnsubscribeFn;
onRemotePtyExit(callback: (msg: RemotePtyExit) => void): UnsubscribeFn;
onRemoteMachineReady(callback: (msg: RemoteMachineEvent) => void): UnsubscribeFn;
onRemoteMachineDisconnected(callback: (msg: RemoteMachineEvent) => void): UnsubscribeFn;
onRemoteStateSync(callback: (msg: RemoteMachineEvent) => void): UnsubscribeFn;
onRemoteError(callback: (msg: RemoteMachineEvent) => void): UnsubscribeFn;
onRemoteMachineReconnecting(callback: (msg: RemoteReconnectingEvent) => void): UnsubscribeFn;
onRemoteMachineReconnectReady(callback: (msg: RemoteMachineEvent) => void): UnsubscribeFn;
onRemoteSpkiTofu(callback: (msg: RemoteSpkiTofuEvent) => void): UnsubscribeFn;
}
export interface FileWatcherAdapter {
watchFile(paneId: string, path: string): Promise<string>;
unwatchFile(paneId: string): Promise<void>;
readWatchedFile(path: string): Promise<string>;
onFileChanged(callback: (payload: FileChangedPayload) => void): UnsubscribeFn;
}
export interface AgentBridgeAdapter {
/** Direct agent IPC — supports remote machines and resume */
queryAgent(options: AgentQueryOptions): Promise<void>;
stopAgentDirect(sessionId: string, remoteMachineId?: string): Promise<void>;
isAgentReady(): Promise<boolean>;
restartAgentSidecar(): Promise<void>;
setSandbox(projectCwds: string[], worktreeRoots: string[], enabled: boolean): Promise<void>;
onSidecarMessage(callback: (msg: SidecarMessagePayload) => void): UnsubscribeFn;
onSidecarExited(callback: () => void): UnsubscribeFn;
}
export interface PtyBridgeAdapter {
/** Per-session PTY — supports remote machines */
spawnPty(options: PtySpawnOptions): Promise<string>;
writePtyDirect(id: string, data: string, remoteMachineId?: string): Promise<void>;
resizePtyDirect(id: string, cols: number, rows: number, remoteMachineId?: string): Promise<void>;
killPty(id: string, remoteMachineId?: string): Promise<void>;
onPtyData(id: string, callback: (data: string) => void): UnsubscribeFn;
onPtyExit(id: string, callback: () => void): UnsubscribeFn;
}
// ── Backend adapter interface ────────────────────────────────────────────────
export interface BackendAdapter {
export interface BackendAdapter extends
SessionPersistenceAdapter,
AgentPersistenceAdapter,
BtmsgAdapter,
BttaskAdapter,
AnchorsAdapter,
SearchAdapter,
AuditAdapter,
NotificationsAdapter,
TelemetryAdapter,
SecretsAdapter,
FsWatcherAdapter,
CtxAdapter,
MemoraAdapter,
SshAdapter,
PluginsAdapter,
ClaudeProviderAdapter,
RemoteMachineAdapter,
FileWatcherAdapter,
AgentBridgeAdapter,
PtyBridgeAdapter {
readonly capabilities: BackendCapabilities;
// ── Lifecycle ────────────────────────────────────────────────────────────
@ -55,13 +274,13 @@ export interface BackendAdapter {
loadGroups(): Promise<GroupsFile>;
saveGroups(groups: GroupsFile): Promise<void>;
// ── Agent ────────────────────────────────────────────────────────────────
// ── Agent (simplified) ────────────────────────────────────────────────────
startAgent(options: AgentStartOptions): Promise<{ ok: boolean; error?: string }>;
stopAgent(sessionId: string): Promise<{ ok: boolean; error?: string }>;
sendPrompt(sessionId: string, prompt: string): Promise<{ ok: boolean; error?: string }>;
// ── PTY ──────────────────────────────────────────────────────────────────
// ── PTY (simplified) ──────────────────────────────────────────────────────
createPty(options: PtyCreateOptions): Promise<string>;
writePty(sessionId: string, data: string): Promise<void>;
@ -82,3 +301,273 @@ export interface BackendAdapter {
onPtyOutput(callback: (sessionId: string, data: string) => void): UnsubscribeFn;
onPtyClosed(callback: (sessionId: string, exitCode: number | null) => void): UnsubscribeFn;
}
// ── Shared record types ─────────────────────────────────────────────────────
export interface PersistedSession {
id: string;
type: string;
title: string;
shell?: string;
cwd?: string;
args?: string[];
group_name?: string;
created_at: number;
last_used_at: number;
}
export interface PersistedLayout {
preset: string;
pane_ids: string[];
}
export interface AgentMessageRecord {
id: number;
session_id: SessionId;
project_id: ProjectId;
sdk_session_id: string | null;
message_type: string;
content: string;
parent_id: string | null;
created_at: number;
}
export interface ProjectAgentState {
project_id: ProjectId;
last_session_id: SessionId;
sdk_session_id: string | null;
status: string;
cost_usd: number;
input_tokens: number;
output_tokens: number;
last_prompt: string | null;
updated_at: number;
}
export interface SessionMetricRecord {
id: number;
project_id: ProjectId;
session_id: SessionId;
start_time: number;
end_time: number;
peak_tokens: number;
turn_count: number;
tool_call_count: number;
cost_usd: number;
model: string | null;
status: string;
error_message: string | null;
}
export interface MdFileEntry {
name: string;
path: string;
priority: boolean;
}
export interface SessionAnchorRecord {
id: string;
project_id: string;
message_id: string;
anchor_type: string;
content: string;
estimated_tokens: number;
turn_index: number;
created_at: number;
}
export type AuditEventType =
| 'prompt_injection'
| 'wake_event'
| 'btmsg_sent'
| 'btmsg_received'
| 'status_change'
| 'heartbeat_missed'
| 'dead_letter';
export type NotificationUrgency = 'low' | 'normal' | 'critical';
export type TelemetryLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
export interface FsWriteEvent {
project_id: string;
file_path: string;
timestamp_ms: number;
}
export interface FsWatcherStatus {
max_watches: number;
estimated_watches: number;
usage_ratio: number;
active_projects: number;
warning: string | null;
}
export interface CtxEntry {
project: string;
key: string;
value: string;
updated_at: string;
}
export interface CtxSummary {
project: string;
summary: string;
created_at: string;
}
export interface MemoraNode {
id: number;
content: string;
tags: string[];
metadata?: Record<string, unknown>;
created_at?: string;
updated_at?: string;
}
export interface MemoraSearchResult {
nodes: MemoraNode[];
total: number;
}
export interface SshSessionRecord {
id: string;
name: string;
host: string;
port: number;
username: string;
key_file: string;
folder: string;
color: string;
created_at: number;
last_used_at: number;
}
export interface PluginMeta {
id: string;
name: string;
version: string;
description: string;
main: string;
permissions: string[];
}
export interface ClaudeProfile {
name: string;
email: string | null;
subscription_type: string | null;
display_name: string | null;
config_dir: string;
}
export interface ClaudeSkill {
name: string;
description: string;
source_path: string;
}
export interface RemoteMachineConfig {
label: string;
url: string;
token: string;
auto_connect: boolean;
spki_pins?: string[];
}
export interface RemoteMachineInfo {
id: string;
label: string;
url: string;
status: string;
auto_connect: boolean;
spki_pins: string[];
}
export interface RemoteSidecarMessage {
machineId: string;
sessionId?: string;
event?: Record<string, unknown>;
}
export interface RemotePtyData {
machineId: string;
sessionId?: string;
data?: string;
}
export interface RemotePtyExit {
machineId: string;
sessionId?: string;
}
export interface RemoteMachineEvent {
machineId: string;
payload?: unknown;
error?: unknown;
}
export interface RemoteReconnectingEvent {
machineId: string;
backoffSecs: number;
}
export interface RemoteSpkiTofuEvent {
machineId: string;
hash: string;
}
export interface FileChangedPayload {
pane_id: string;
path: string;
content: string;
}
export interface BtmsgFeedMessage {
id: string;
fromAgent: AgentId;
toAgent: AgentId;
content: string;
createdAt: string;
replyTo: string | null;
senderName: string;
senderRole: string;
recipientName: string;
recipientRole: string;
}
export interface AgentQueryOptions {
provider?: ProviderId;
session_id: string;
prompt: string;
cwd?: string;
max_turns?: number;
max_budget_usd?: number;
resume_session_id?: string;
permission_mode?: string;
setting_sources?: string[];
system_prompt?: string;
model?: string;
claude_config_dir?: string;
additional_directories?: string[];
worktree_name?: string;
provider_config?: Record<string, unknown>;
extra_env?: Record<string, string>;
remote_machine_id?: string;
}
export interface SidecarMessagePayload {
type: string;
sessionId?: string;
event?: Record<string, unknown>;
message?: string;
exitCode?: number | null;
signal?: string | null;
}
export interface PtySpawnOptions {
shell?: string;
cwd?: string;
args?: string[];
cols?: number;
rows?: number;
remote_machine_id?: string;
}