diff --git a/src/preload/index.ts b/src/preload/index.ts index a049d81..69fb66d 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,74 +1,135 @@ import { contextBridge, ipcRenderer } from 'electron' +import type { + AiHistEntry, + AiHistRecentOptions, + AiHistResumeEntry, + AiHistSession, + AiHistStats, + AiHistStatusResponse, + AuthLoginInput, + AuthStatus, + BrokerAttachTerminalInput, + BrokerAttachTerminalResult, + BrokerDetails, + BrokerEventRecord, + BrokerListAgent, + BrokerSendMessageInput, + BrokerSetTerminalModeResult, + BrokerSpawnAgentInput, + BrokerSpawnAgentResult, + BrokerStatusEvent, + BurnAgentBreakdown, + BurnAgentInput, + BurnAgentSummary, + CloudAgentBinding, + CloudAgentEvent, + CloudAgentRecord, + CloudAgentStatus, + ConnectedIntegration, + CreateCloudAgentInput, + FsDirEntry, + FsReadPreviewResult, + GitBranchInfo, + GitBranchSyncStatus, + GitCheckoutBranchOptions, + GitCommitDraft, + GitCommitSelectionInput, + GitFileStatus, + GitGenerateCommitMessageInput, + GitHistoryCommit, + GitSummary, + IntegrationAdapter, + IntegrationConnectSession, + IntegrationsEvent, + PearAPI, + PendingRelayMessage, + ProactiveAgentBinding, + ProactiveAgentDeployResult, + ProactiveAgentDraft, + ProactiveAgentEvent, + ProactiveAgentRunsOptions, + ProactiveAgentRunsPage, + ProactiveAgentTranscript, + ProjectListResult, + TerminalAttachMode +} from '../shared/types/ipc' -export type ViewMode = 'terminal' | 'chat' | 'graph' | 'project-settings' | 'account-settings' | 'broker-details' | 'source-control' | 'ai-hist' | 'burn-session' - -type TerminalAttachMode = 'view' | 'drive' | 'passthrough' - -export type AiHistSource = 'claude' | 'codex' | 'cursor' | 'relay' - -export interface AiHistEntry { - id: number - source: AiHistSource - sessionId: string | null - project: string | null - prompt: string - timestampMs: number -} - -export interface AiHistSession { - sessionId: string - source: AiHistSource - project: string | null - firstPrompt: string - firstActivityMs: number - lastActivityMs: number - promptCount: number -} - -export interface AiHistStats { - total: number - bySource: Partial> - byProject: Array<{ project: string; count: number }> - firstTimestampMs: number | null - lastTimestampMs: number | null -} - -export interface BurnAgentInput { - projectId?: string - name: string - cwd?: string - cli?: string -} - -export interface BurnAgentSummary { - projectId?: string - name: string - agentKey: string - totalTokens: number - totalCost: number - turnCount: number - byModel: Array<{ model: string; tokens: number; cost: number }> - byTool: Array<{ tool: string; tokens: number; cost: number; count: number }> - sessionIds: Array<{ sessionId: string; ts?: string }> - updatedAt: number - status: 'ok' | 'unavailable' - error?: string -} - -export interface BurnAgentBreakdown extends BurnAgentSummary { - primarySessionId?: string - hotspots?: { - sessionId?: string - grandTotal: number - attributedTotal: number - unattributedTotal: number - attributionDegraded: boolean - files: Array<{ path: string; initialTokens: number; persistenceTokens: number; ridingTurns: number; totalCost: number }> - bashVerbs: Array<{ verb: string; callCount: number; distinctCommands: number; initialTokens: number; persistenceTokens: number; avgPersistenceTurns: number; totalCost: number; topExamples: string[] }> - bash: Array<{ command?: string; callCount: number; initialTokens: number; persistenceTokens: number; totalCost: number }> - subagents: Array<{ subagentType: string; callCount: number; initialTokens: number; persistenceTokens: number; totalCost: number }> - } -} +export type { + AiHistEntry, + AiHistRecentOptions, + AiHistResumeEntry, + AiHistSession, + AiHistSource, + AiHistStats, + AiHistStatusResponse, + AgentCurrentState, + AuthLoginInput, + AuthStatus, + AuthUser, + BrokerAgentDetails, + BrokerAttachTerminalInput, + BrokerAttachTerminalResult, + BrokerDetails, + BrokerEventRecord, + BrokerListAgent, + BrokerSendMessageInput, + BrokerSetTerminalModeResult, + BrokerSpawnAgentInput, + BrokerSpawnAgentResult, + BrokerStatusEvent, + BurnAgentBreakdown, + BurnAgentInput, + BurnAgentSummary, + CloudAgentBinding, + CloudAgentEvent, + CloudAgentMountStatus, + CloudAgentRecord, + CloudAgentSandboxStatus, + CloudAgentStatus, + CloudAgentSyncMode, + ConnectedIntegration, + CreateCloudAgentInput, + FsDirEntry, + FsReadPreviewResult, + GitBranchInfo, + GitBranchSyncStatus, + GitCheckoutBranchOptions, + GitCommitDraft, + GitCommitSelectionInput, + GitFileStatus, + GitFileStatusKind, + GitGenerateCommitMessageInput, + GitHistoryCoAuthor, + GitHistoryCommit, + GitHistoryFile, + GitSummary, + InboundDeliveryMode, + IntegrationAdapter, + IntegrationAuthMethod, + IntegrationCapabilities, + IntegrationConnectSession, + IntegrationConnectStatus, + IntegrationsEvent, + MessageInjectionMode, + PearAPI, + PendingRelayMessage, + ProactiveAgentBinding, + ProactiveAgentDeployResult, + ProactiveAgentDraft, + ProactiveAgentEvent, + ProactiveAgentHarness, + ProactiveAgentRun, + ProactiveAgentRunMode, + ProactiveAgentRunStatus, + ProactiveAgentRunsOptions, + ProactiveAgentRunsPage, + ProactiveAgentStatus, + ProactiveAgentTranscript, + ProactiveAgentWatchEventKind, + ProjectListResult, + TerminalAttachMode, + ViewMode +} from '../shared/types/ipc' // Thin generic wrappers so each handler binds an IPC channel + return type without // repeating the `as Promise` cast on every call site. @@ -84,10 +145,10 @@ function subscribe(channel: string, callback: (payload: T) => void): () => vo const api = { app: { - confirmQuit: () => ipcRenderer.invoke('app:confirm-quit') as Promise + confirmQuit: () => invoke('app:confirm-quit') }, project: { - list: () => invoke<{ projects: unknown[]; activeId: string | null }>('project:list'), + list: () => invoke('project:list'), add: (name: string, rootPath?: string) => invoke('project:add', name, rootPath), remove: (id: string) => invoke('project:remove', id), setActive: (id: string | null) => invoke('project:set-active', id), @@ -119,35 +180,32 @@ const api = { name: string, channels?: string[], errorMessage?: string - ) => invoke<{ removed: string[] }>('broker:auto-fix-runtime', projectId, cwd, name, channels, errorMessage), + ) => + invoke<{ removed: string[] }>( + 'broker:auto-fix-runtime', + projectId, + cwd, + name, + channels, + errorMessage + ), connectCloud: () => invoke('broker:connect-cloud'), - spawnAgent: (projectId: string, input: { - name: string - cli: string - model?: string - task?: string - channels?: string[] - cwd?: string - args?: string[] - }) => invoke<{ name: string; runtime: string }>('broker:spawn-agent', projectId, input), - attachTerminal: (input: { - projectId?: string - name: string - rows?: number - cols?: number - mode?: TerminalAttachMode - }) => invoke('broker:attach-terminal', input), - sendInputFast: (projectId: string | undefined, name: string, data: string) => - ipcRenderer.send('broker:send-input-fast', projectId, name, data), + spawnAgent: (projectId: string, input: BrokerSpawnAgentInput) => + invoke('broker:spawn-agent', projectId, input), + attachTerminal: (input: BrokerAttachTerminalInput) => + invoke('broker:attach-terminal', input), + sendInputFast: (projectId: string | undefined, name: string, data: string): void => { + ipcRenderer.send('broker:send-input-fast', projectId, name, data) + }, setTerminalMode: (projectId: string | undefined, name: string, mode: TerminalAttachMode) => - invoke('broker:set-terminal-mode', projectId, name, mode), + invoke('broker:set-terminal-mode', projectId, name, mode), getPending: (projectId: string | undefined, name: string) => - invoke('broker:get-pending', projectId, name), + invoke('broker:get-pending', projectId, name), flushPending: (projectId: string | undefined, name: string) => invoke<{ flushed: number }>('broker:flush-pending', projectId, name), resizePty: (projectId: string | undefined, name: string, rows: number, cols: number) => invoke('broker:resize-pty', projectId, name, rows, cols), - sendMessage: (projectId: string | undefined, input: { to: string; text: string; from?: string }) => + sendMessage: (projectId: string | undefined, input: BrokerSendMessageInput) => invoke('broker:send-message', projectId, input), subscribeAgentChannel: (projectId: string | undefined, name: string, channel: string) => invoke('broker:subscribe-agent-channel', projectId, name, channel), @@ -155,9 +213,10 @@ const api = { invoke('broker:unsubscribe-agent-channel', projectId, name, channel), releaseAgent: (projectId: string | undefined, name: string) => invoke('broker:release-agent', projectId, name), - listAgents: (projectId?: string) => invoke('broker:list-agents', projectId), - listDetails: () => invoke('broker:list-details'), - listEvents: () => invoke('broker:list-events'), + listAgents: (projectId?: string) => + invoke('broker:list-agents', projectId), + listDetails: () => invoke('broker:list-details'), + listEvents: () => invoke('broker:list-events'), shutdown: () => invoke('broker:shutdown'), onEvent: (callback: (event: unknown) => void) => subscribe('broker:event', callback), onPtyChunk: (callback: (projectId: string, name: string, chunk: string) => void) => { @@ -166,8 +225,8 @@ const api = { ipcRenderer.on('broker:pty-chunk', handler) return () => ipcRenderer.removeListener('broker:pty-chunk', handler) }, - onStatus: (callback: (status: { projectId?: string; status: string; error?: string }) => void) => - subscribe<{ projectId?: string; status: string; error?: string }>('broker:status', callback) + onStatus: (callback: (status: BrokerStatusEvent) => void) => + subscribe('broker:status', callback) }, burn: { listAgentSummaries: (agents: BurnAgentInput[]) => @@ -176,135 +235,131 @@ const api = { invoke('burn:get-agent-breakdown', agent) }, git: { - status: (path: string) => invoke('git:status', path), + status: (path: string) => invoke('git:status', path), diff: (path: string, file?: string) => invoke('git:diff', path, file), fileContent: (path: string, file: string, revision?: string) => invoke('git:file-content', path, file, revision), - summary: (path: string) => invoke('git:summary', path), + summary: (path: string) => invoke('git:summary', path), branches: (root: string) => invoke('git:branches', root), - branchDetails: (root: string) => invoke('git:branch-details', root), - checkoutBranch: (root: string, branch: string, options?: { stashChanges?: boolean }) => - invoke('git:checkout-branch', root, branch, options), - branchSyncStatus: (root: string) => invoke('git:branch-sync-status', root), - fetchRemote: (root: string) => invoke('git:fetch-remote', root), - pullCurrentBranch: (root: string) => invoke('git:pull-current-branch', root), - pushCurrentBranch: (root: string) => invoke('git:push-current-branch', root), - history: (path: string, limit?: number) => invoke('git:history', path, limit), - show: (path: string, hash: string, file?: string) => invoke('git:show', path, hash, file), - discardFiles: (path: string, files: string[]) => invoke('git:discard-files', path, files), + branchDetails: (root: string) => invoke('git:branch-details', root), + checkoutBranch: (root: string, branch: string, options?: GitCheckoutBranchOptions) => + invoke('git:checkout-branch', root, branch, options), + branchSyncStatus: (root: string) => invoke('git:branch-sync-status', root), + fetchRemote: (root: string) => invoke('git:fetch-remote', root), + pullCurrentBranch: (root: string) => + invoke('git:pull-current-branch', root), + pushCurrentBranch: (root: string) => + invoke('git:push-current-branch', root), + history: (path: string, limit?: number) => + invoke('git:history', path, limit), + show: (path: string, hash: string, file?: string) => + invoke('git:show', path, hash, file), + discardFiles: (path: string, files: string[]) => + invoke('git:discard-files', path, files), addGitignorePatterns: (path: string, patterns: string[]) => invoke('git:add-gitignore-patterns', path, patterns), - commitSelection: (path: string, input: { - title: string - body?: string - wholeFiles: string[] - patch?: string - }) => invoke<{ hash: string }>('git:commit-selection', path, input), - generateCommitMessage: (path: string, input: { wholeFiles: string[]; patch?: string }) => - invoke('git:generate-commit-message', path, input) + commitSelection: (path: string, input: GitCommitSelectionInput) => + invoke<{ hash: string }>('git:commit-selection', path, input), + generateCommitMessage: (path: string, input: GitGenerateCommitMessageInput) => + invoke('git:generate-commit-message', path, input) }, fs: { - listDir: (dirPath: string) => invoke('fs:list-dir', dirPath), - readPreview: (filePath: string) => invoke('fs:read-preview', filePath), + listDir: (dirPath: string) => invoke('fs:list-dir', dirPath), + readPreview: (filePath: string) => invoke('fs:read-preview', filePath), revealPath: (filePath: string) => invoke('fs:reveal-path', filePath) }, auth: { - login: (input?: { apiUrl?: string }) => invoke('auth:login', input), + login: (input?: AuthLoginInput) => invoke('auth:login', input), logout: () => invoke('auth:logout'), - status: () => invoke('auth:status') + status: () => invoke('auth:status') }, cloudAgent: { - list: () => ipcRenderer.invoke('cloud-agent:list'), - create: (input: { name: string; harness: string; model: string }) => - ipcRenderer.invoke('cloud-agent:create', input), - delete: (id: string) => ipcRenderer.invoke('cloud-agent:delete', id), + list: () => invoke('cloud-agent:list'), + create: (input: CreateCloudAgentInput) => + invoke('cloud-agent:create', input), + delete: (id: string) => invoke('cloud-agent:delete', id), attach: (projectId: string, cloudAgentId: string) => - ipcRenderer.invoke('cloud-agent:attach', projectId, cloudAgentId), - detach: (projectId: string) => ipcRenderer.invoke('cloud-agent:detach', projectId), - status: (projectId: string) => ipcRenderer.invoke('cloud-agent:status', projectId), - onEvent: (callback: (event: unknown) => void) => { - const handler = (_: unknown, event: unknown): void => callback(event) - ipcRenderer.on('cloud-agent:event', handler) - return () => ipcRenderer.removeListener('cloud-agent:event', handler) - } + invoke('cloud-agent:attach', projectId, cloudAgentId), + detach: (projectId: string) => invoke('cloud-agent:detach', projectId), + status: (projectId: string) => + invoke('cloud-agent:status', projectId), + onEvent: (callback: (event: CloudAgentEvent) => void) => + subscribe('cloud-agent:event', callback) }, proactiveAgent: { - list: (projectId: string) => ipcRenderer.invoke('proactive-agent:list', projectId), - create: (projectId: string, draft: unknown) => - ipcRenderer.invoke('proactive-agent:create', projectId, draft), - update: (projectId: string, personaId: string, draft: unknown) => - ipcRenderer.invoke('proactive-agent:update', projectId, personaId, draft), + list: (projectId: string) => + invoke('proactive-agent:list', projectId), + create: (projectId: string, draft: ProactiveAgentDraft) => + invoke('proactive-agent:create', projectId, draft), + update: (projectId: string, personaId: string, draft: ProactiveAgentDraft) => + invoke('proactive-agent:update', projectId, personaId, draft), deploy: (projectId: string, personaId: string) => - ipcRenderer.invoke('proactive-agent:deploy', projectId, personaId), + invoke('proactive-agent:deploy', projectId, personaId), pause: (projectId: string, personaId: string) => - ipcRenderer.invoke('proactive-agent:pause', projectId, personaId), + invoke('proactive-agent:pause', projectId, personaId), resume: (projectId: string, personaId: string) => - ipcRenderer.invoke('proactive-agent:resume', projectId, personaId), + invoke('proactive-agent:resume', projectId, personaId), undeploy: (projectId: string, personaId: string) => - ipcRenderer.invoke('proactive-agent:undeploy', projectId, personaId), - runs: (projectId: string, personaId: string, opts?: { limit?: number; cursor?: string }) => - ipcRenderer.invoke('proactive-agent:runs', projectId, personaId, opts), + invoke('proactive-agent:undeploy', projectId, personaId), + runs: (projectId: string, personaId: string, opts?: ProactiveAgentRunsOptions) => + invoke('proactive-agent:runs', projectId, personaId, opts), runTranscript: (runId: string) => - ipcRenderer.invoke('proactive-agent:run-transcript', runId), - onEvent: (callback: (event: unknown) => void) => { - const handler = (_: unknown, event: unknown): void => callback(event) - ipcRenderer.on('proactive-agent:event', handler) - return () => ipcRenderer.removeListener('proactive-agent:event', handler) - } + invoke('proactive-agent:run-transcript', runId), + onEvent: (callback: (event: ProactiveAgentEvent) => void) => + subscribe('proactive-agent:event', callback) }, integrations: { - catalog: () => ipcRenderer.invoke('integrations:catalog'), - list: (projectId: string) => ipcRenderer.invoke('integrations:list', projectId), + catalog: () => invoke('integrations:catalog'), + list: (projectId: string) => invoke('integrations:list', projectId), startConnect: (projectId: string, provider: string) => - ipcRenderer.invoke('integrations:start-connect', projectId, provider), + invoke('integrations:start-connect', projectId, provider), pollConnect: (sessionId: string) => - ipcRenderer.invoke('integrations:poll-connect', sessionId), + invoke('integrations:poll-connect', sessionId), completeConnect: ( projectId: string, sessionId: string, scope: Record, mountPaths: string[], notifyAgent: boolean - ) => ipcRenderer.invoke( - 'integrations:complete-connect', - projectId, - sessionId, - scope, - mountPaths, - notifyAgent - ), + ) => + invoke( + 'integrations:complete-connect', + projectId, + sessionId, + scope, + mountPaths, + notifyAgent + ), updateScope: ( projectId: string, integrationId: string, scope: Record, mountPaths: string[] - ) => ipcRenderer.invoke('integrations:update-scope', projectId, integrationId, scope, mountPaths), + ) => + invoke( + 'integrations:update-scope', + projectId, + integrationId, + scope, + mountPaths + ), disconnect: (projectId: string, integrationId: string) => - ipcRenderer.invoke('integrations:disconnect', projectId, integrationId), - onEvent: (callback: (event: unknown) => void) => { - const handler = (_: unknown, event: unknown): void => callback(event) - ipcRenderer.on('integrations:event', handler) - return () => ipcRenderer.removeListener('integrations:event', handler) - } + invoke('integrations:disconnect', projectId, integrationId), + onEvent: (callback: (event: IntegrationsEvent) => void) => + subscribe('integrations:event', callback) }, aiHist: { - status: () => - invoke<{ ok: true; dbPath: string } | { ok: false; reason: string }>('ai-hist:status'), - recent: (opts?: { source?: string; project?: string; limit?: number; beforeMs?: number }) => - invoke('ai-hist:recent', opts), - listSessions: (opts?: { source?: string; project?: string; limit?: number; beforeMs?: number }) => + status: () => invoke('ai-hist:status'), + recent: (opts?: AiHistRecentOptions) => invoke('ai-hist:recent', opts), + listSessions: (opts?: AiHistRecentOptions) => invoke('ai-hist:list-sessions', opts), getSession: (sessionId: string) => invoke('ai-hist:get-session', sessionId), - search: ( - query: string, - opts?: { source?: string; project?: string; limit?: number; beforeMs?: number } - ) => invoke('ai-hist:search', query, opts), - searchSessions: ( - query: string, - opts?: { source?: string; project?: string; limit?: number; beforeMs?: number } - ) => invoke('ai-hist:search-sessions', query, opts), + search: (query: string, opts?: AiHistRecentOptions) => + invoke('ai-hist:search', query, opts), + searchSessions: (query: string, opts?: AiHistRecentOptions) => + invoke('ai-hist:search-sessions', query, opts), stats: () => invoke('ai-hist:stats'), - resumeCommand: (entry: { source: string; sessionId: string | null; project: string | null }) => + resumeCommand: (entry: AiHistResumeEntry) => invoke('ai-hist:resume-command', entry), reload: () => invoke('ai-hist:reload') }, @@ -313,8 +368,6 @@ const api = { ipcRenderer.on(channel, handler) return () => ipcRenderer.removeListener(channel, handler) } -} +} satisfies PearAPI contextBridge.exposeInMainWorld('pear', api) - -export type PearAPI = typeof api diff --git a/src/renderer/src/lib/ipc.ts b/src/renderer/src/lib/ipc.ts index fcbd7dc..cefeac8 100644 --- a/src/renderer/src/lib/ipc.ts +++ b/src/renderer/src/lib/ipc.ts @@ -1,636 +1,14 @@ -export type TerminalAttachMode = 'view' | 'drive' | 'passthrough' -export type InboundDeliveryMode = 'auto_inject' | 'manual_flush' -export type MessageInjectionMode = 'wait' | 'steer' -export type AgentCurrentState = 'working' | 'idle' | 'blocked_on_send' +// Renderer-facing IPC entry point. +// +// All IPC type definitions live in @shared/types/ipc (a single source of +// truth shared with the preload, which uses `satisfies PearAPI` to enforce +// the implementation matches). We re-export everything from there so +// existing `import { ..., type Foo } from '@/lib/ipc'` call sites keep +// working unchanged. -export interface BurnAgentInput { - projectId?: string - name: string - cwd?: string - cli?: string -} - -export interface BurnAgentSummary { - projectId?: string - name: string - agentKey: string - totalTokens: number - totalCost: number - turnCount: number - byModel: Array<{ model: string; tokens: number; cost: number }> - byTool: Array<{ tool: string; tokens: number; cost: number; count: number }> - sessionIds: Array<{ sessionId: string; ts?: string }> - updatedAt: number - status: 'ok' | 'unavailable' - error?: string -} - -export interface BurnAgentBreakdown extends BurnAgentSummary { - primarySessionId?: string - hotspots?: { - sessionId?: string - grandTotal: number - attributedTotal: number - unattributedTotal: number - attributionDegraded: boolean - files: Array<{ path: string; initialTokens: number; persistenceTokens: number; ridingTurns: number; totalCost: number }> - bashVerbs: Array<{ verb: string; callCount: number; distinctCommands: number; initialTokens: number; persistenceTokens: number; avgPersistenceTurns: number; totalCost: number; topExamples: string[] }> - bash: Array<{ command?: string; callCount: number; initialTokens: number; persistenceTokens: number; totalCost: number }> - subagents: Array<{ subagentType: string; callCount: number; initialTokens: number; persistenceTokens: number; totalCost: number }> - } -} - -export interface PendingRelayMessage { - from: string - body: string - target: string - thread_id?: string - project_id?: string - project_alias?: string - priority: number - mode: MessageInjectionMode - queued_at_ms: number - event_id?: string -} - -export interface BrokerListAgent { - name: string - projectId: string - runtime?: string - cli?: string - model?: string - channels?: string[] - parent?: string - pid?: number - last_activity_at?: string - last_activity_ms?: number - current_state?: AgentCurrentState - inboundDeliveryMode?: InboundDeliveryMode -} - -export interface BrokerAgentDetails { - name: string - runtime: string - cli?: string - model?: string - channels: string[] - parent?: string - pid?: number - currentState?: AgentCurrentState -} - -export interface BrokerDetails { - projectId: string - name: string - cwd: string - channels: string[] - kind: 'local' | 'cloud' - url?: string - port?: number - apiKey?: string - brokerPid?: number - cloudSandboxId?: string | null - connectionPath?: string - connectionFileStatus?: 'matches' | 'missing' | 'different' | 'invalid' - apiKeyAvailable: boolean - health: 'connected' | 'unreachable' - session?: { - brokerVersion: string - protocolVersion: number - workspaceKey?: string - defaultWorkspaceId?: string - mode: string - uptimeSecs: number - } - relaycast?: { - workspaceKey?: string - defaultWorkspaceId?: string - authenticated?: boolean - workspaceCount?: number - workspaces: Array<{ - workspaceId: string - workspaceAlias?: string | null - selfName: string - selfAgentId: string - authenticated: boolean - default: boolean - }> - } - agentCount: number - pendingDeliveryCount: number - agents: BrokerAgentDetails[] - error?: string -} - -export interface BrokerEventRecord { - id: string - projectId: string - timestamp: number - event: Record & { - kind?: string - projectId?: string - } -} - -export type GitFileStatusKind = 'added' | 'modified' | 'deleted' | 'renamed' | 'untracked' - -export interface GitFileStatus { - path: string - oldPath?: string - status: GitFileStatusKind - staged: boolean -} - -export interface GitSummary { - branch: string - additions: number - deletions: number -} - -export interface GitHistoryFile { - path: string - oldPath?: string - status: string -} - -export interface GitHistoryCoAuthor { - name: string - email: string - avatarUrl?: string - cachedAvatarUrl?: string -} - -export interface GitHistoryCommit { - hash: string - shortHash: string - author: string - authorEmail: string - authorAvatarUrl?: string - authorCachedAvatarUrl?: string - coAuthors: GitHistoryCoAuthor[] - date: string - subject: string - body: string - tags: string[] - additions: number - deletions: number - files: GitHistoryFile[] -} - -export interface GitCommitDraft { - title: string - body: string -} - -export interface GitCommitSelectionInput { - title: string - body?: string - wholeFiles: string[] - patch?: string -} - -export interface GitBranchInfo { - name: string - current: boolean - remote: boolean - lastCommitDate: string - defaultBranch: boolean -} - -export interface GitBranchSyncStatus { - branch: string - remote: string | null - upstream: string | null - ahead: number - behind: number - hasRemote: boolean -} - -export interface GitCheckoutBranchOptions { - stashChanges?: boolean -} - -export type CloudAgentRecord = { - id: string - name: string - displayName?: string - harness: string - defaultModel: string - status: 'ready' | 'warming' | 'error' | 'stopped' - lastUsedAt?: string - lastError?: string - lastAuthenticatedAt?: string | null -} - -export type CreateCloudAgentInput = { - name: string - harness: string - model: string -} - -export type CloudAgentSandboxStatus = 'warming' | 'ready' | 'failed' | 'stopping' | 'stopped' - -export type CloudAgentBinding = { - projectId: string - cloudAgentId: string - sandboxId: string - relayfileMountPath: string - attachedAt: string -} - -export type CloudAgentMountStatus = { - ready: boolean - lastReconcileAt?: string - pendingWrites: number - conflicts: number -} - -export type CloudAgentSyncMode = 'sandbox-priority' | 'local-priority' - -export type CloudAgentStatus = { - binding: CloudAgentBinding - sandbox: { id: string; status: CloudAgentSandboxStatus } - mount: CloudAgentMountStatus - syncMode: CloudAgentSyncMode -} - -export type CloudAgentEvent = - | { type: 'sandbox-status'; projectId: string; status: CloudAgentSandboxStatus } - | { type: 'mount-status'; projectId: string; mount: CloudAgentMountStatus } - | { type: 'sync-mode-changed'; projectId: string; syncMode: CloudAgentSyncMode } - | { type: 'error'; projectId: string; message: string } - -export type ProactiveAgentHarness = 'claude' | 'codex' | 'opencode' -export type ProactiveAgentStatus = 'draft' | 'warming' | 'active' | 'paused' | 'error' -export type ProactiveAgentRunStatus = 'running' | 'succeeded' | 'failed' -export type ProactiveAgentRunMode = 'cloud' | 'local' -export type ProactiveAgentWatchEventKind = 'created' | 'updated' | 'deleted' - -export type ProactiveAgentDraft = { - id: string - name: string - description?: string - cloudAgentId: string - harness: ProactiveAgentHarness - model: string - systemPrompt: string - integrations: Record> - watch: Array<{ - paths: string[] - events: ProactiveAgentWatchEventKind[] - debounceMs?: number - match?: string - }> - handlerCode: string - inputs?: Record - memory?: { enabled: boolean; scopes?: Array<'workspace' | 'project' | 'persona'>; ttlDays?: number } - harnessSettings?: { reasoning?: 'low' | 'medium' | 'high'; timeoutSeconds?: number } - mount?: { enabled: boolean } - runMode?: ProactiveAgentRunMode -} - -export type ProactiveAgentBinding = { - projectId: string - personaId: string - cloudAgentId: string - status: ProactiveAgentStatus - lastError?: string - lastFiredAt?: string - createdAt: string - updatedAt: string - draft: ProactiveAgentDraft -} - -export type ProactiveAgentRun = { - runId: string - projectId: string - personaId: string - firedAt: string - trigger: { - type: 'relayfile-change' - path: string - eventKind: ProactiveAgentWatchEventKind - } - durationMs?: number - status: ProactiveAgentRunStatus - summary?: string - error?: string -} - -export type ProactiveAgentTranscript = { - runId: string - projectId?: string - personaId?: string - messages: Array<{ - role: 'system' | 'user' | 'assistant' | 'tool' - content: string - ts: string - }> -} - -export type ProactiveAgentRunsPage = { - runs: ProactiveAgentRun[] - nextCursor?: string -} - -export type ProactiveAgentDeployResult = { - status: 'active' | 'warming' | 'error' - error?: string -} - -export type ProactiveAgentEvent = - | { type: 'binding-updated'; projectId: string; personaId: string; binding: ProactiveAgentBinding } - | { type: 'binding-removed'; projectId: string; personaId: string } - | { type: 'run-started'; projectId: string; personaId: string; run: ProactiveAgentRun } - | { type: 'run-update'; projectId: string; personaId: string; runId: string; chunk: string } - | { type: 'run-finished'; projectId: string; personaId: string; run: ProactiveAgentRun } - -export type IntegrationAuthMethod = 'oauth' | 'token' | 'apikey' - -export type IntegrationCapabilities = { - webhook: boolean - poll: boolean - writeback: boolean -} +import type { PearAPI } from '@shared/types/ipc' -export type IntegrationAdapter = { - provider: string - displayName: string - iconUrl?: string - version: string - capabilities: IntegrationCapabilities - authMethod: IntegrationAuthMethod - requiredScopes?: string[] - defaultMountPaths: string[] - description: string -} - -export type ConnectedIntegration = { - provider: string - integrationId: string - scope: Record - mountPaths: string[] - connectedAt: string - notifyAgent: boolean - lastSyncAt?: string - lastError?: string -} - -export type IntegrationConnectStatus = - | 'pending' - | 'awaiting-user' - | 'choosing-scope' - | 'completed' - | 'error' - | 'expired' - -export type IntegrationConnectSession = { - sessionId: string - provider: string - status: IntegrationConnectStatus - authUrl?: string - scopeChoices?: Record - integrationId?: string - error?: string -} - -export type IntegrationsEvent = - | { type: 'session-update'; sessionId: string; session: IntegrationConnectSession } - | { type: 'integration-added'; projectId: string; integration: ConnectedIntegration } - | { type: 'integration-removed'; projectId: string; integrationId: string } - | { type: 'integration-error'; projectId: string; integrationId: string; message: string } - -export interface AuthUser { - name?: string - email?: string - githubUsername?: string - username?: string - avatarUrl?: string - cachedAvatarUrl?: string - organizationName?: string - projectName?: string -} - -export interface AuthStatus { - loggedIn: boolean - apiUrl?: string - user?: AuthUser -} - -export interface PearAPI { - app: { - confirmQuit: () => Promise - } - project: { - list: () => Promise<{ projects: unknown[]; activeId: string | null }> - add: (name: string, rootPath?: string) => Promise - remove: (id: string) => Promise - setActive: (id: string | null) => Promise - update: (id: string, update: Record) => Promise - addChannel: (projectId: string, name: string) => Promise - removeChannel: (projectId: string, name: string) => Promise - setChannelPeople: (projectId: string, channelName: string, people: string[]) => Promise - addRoot: (projectId: string, name?: string, rootPath?: string) => Promise - removeRoot: (projectId: string, rootId: string) => Promise - addIntegration: (projectId: string, name: string, type?: string) => Promise - removeIntegration: (projectId: string, integrationId: string) => Promise - } - broker: { - start: (projectId: string, cwd: string, name: string, channels?: string[]) => Promise - syncChannels: (projectId: string, channels: string[]) => Promise - autoFixRuntime: ( - projectId: string, - cwd: string, - name: string, - channels?: string[], - errorMessage?: string - ) => Promise<{ removed: string[] }> - connectCloud: () => Promise - spawnAgent: (projectId: string, input: { - name: string - cli: string - model?: string - task?: string - channels?: string[] - cwd?: string - args?: string[] - }) => Promise<{ name: string; runtime: string }> - attachTerminal: (input: { - projectId?: string - name: string - rows?: number - cols?: number - mode?: TerminalAttachMode - }) => Promise<{ - name: string - mode: InboundDeliveryMode - previousMode?: InboundDeliveryMode - pending: number - snapshot?: { - rows: number - cols: number - cursor: [number, number] - screen: string - } - }> - sendInputFast: (projectId: string | undefined, name: string, data: string) => void - setTerminalMode: (projectId: string | undefined, name: string, mode: TerminalAttachMode) => Promise<{ - name: string - mode: InboundDeliveryMode - flushed: number - pending: number - }> - getPending: (projectId: string | undefined, name: string) => Promise - flushPending: (projectId: string | undefined, name: string) => Promise<{ flushed: number }> - resizePty: (projectId: string | undefined, name: string, rows: number, cols: number) => Promise - sendMessage: (projectId: string | undefined, input: { to: string; text: string; from?: string }) => Promise - subscribeAgentChannel: (projectId: string | undefined, name: string, channel: string) => Promise - unsubscribeAgentChannel: (projectId: string | undefined, name: string, channel: string) => Promise - releaseAgent: (projectId: string | undefined, name: string) => Promise - listAgents: (projectId?: string) => Promise - listDetails: () => Promise - listEvents: () => Promise - shutdown: () => Promise - onEvent: (callback: (event: unknown) => void) => () => void - onPtyChunk: (callback: (projectId: string, name: string, chunk: string) => void) => () => void - onStatus: (callback: (status: { projectId?: string; status: string; error?: string }) => void) => () => void - } - burn: { - listAgentSummaries: (agents: BurnAgentInput[]) => Promise - getAgentBreakdown: (agent: BurnAgentInput) => Promise - } - git: { - status: (path: string) => Promise - diff: (path: string, file?: string) => Promise - fileContent: (path: string, file: string, revision?: string) => Promise - summary: (path: string) => Promise - branches: (root: string) => Promise - branchDetails: (root: string) => Promise - checkoutBranch: (root: string, branch: string, options?: GitCheckoutBranchOptions) => Promise - branchSyncStatus: (root: string) => Promise - fetchRemote: (root: string) => Promise - pullCurrentBranch: (root: string) => Promise - pushCurrentBranch: (root: string) => Promise - history: (path: string, limit?: number) => Promise - show: (path: string, hash: string, file?: string) => Promise - discardFiles: (path: string, files: string[]) => Promise - addGitignorePatterns: (path: string, patterns: string[]) => Promise - commitSelection: (path: string, input: GitCommitSelectionInput) => Promise<{ hash: string }> - generateCommitMessage: ( - path: string, - input: { wholeFiles: string[]; patch?: string } - ) => Promise - } - fs: { - listDir: (dirPath: string) => Promise< - { name: string; path: string; type: 'file' | 'directory' }[] - > - readPreview: (filePath: string) => Promise<{ - kind: 'text' | 'binary' | 'too-large' | 'missing' - content: string - size: number - }> - revealPath: (filePath: string) => Promise - } - auth: { - login: (input?: { apiUrl?: string }) => Promise - logout: () => Promise - status: () => Promise - } - cloudAgent: { - list: () => Promise - create: (input: CreateCloudAgentInput) => Promise - delete: (id: string) => Promise - attach: (projectId: string, cloudAgentId: string) => Promise - detach: (projectId: string) => Promise - status: (projectId: string) => Promise - onEvent: (callback: (event: CloudAgentEvent) => void) => () => void - } - proactiveAgent: { - list: (projectId: string) => Promise - create: (projectId: string, draft: ProactiveAgentDraft) => Promise - update: (projectId: string, personaId: string, draft: ProactiveAgentDraft) => Promise - deploy: (projectId: string, personaId: string) => Promise - pause: (projectId: string, personaId: string) => Promise - resume: (projectId: string, personaId: string) => Promise - undeploy: (projectId: string, personaId: string) => Promise - runs: ( - projectId: string, - personaId: string, - opts?: { limit?: number; cursor?: string } - ) => Promise - runTranscript: (runId: string) => Promise - onEvent: (callback: (event: ProactiveAgentEvent) => void) => () => void - } - integrations: { - catalog: () => Promise - list: (projectId: string) => Promise - startConnect: (projectId: string, provider: string) => Promise - pollConnect: (sessionId: string) => Promise - completeConnect: ( - projectId: string, - sessionId: string, - scope: Record, - mountPaths: string[], - notifyAgent: boolean - ) => Promise - updateScope: ( - projectId: string, - integrationId: string, - scope: Record, - mountPaths: string[] - ) => Promise - disconnect: (projectId: string, integrationId: string) => Promise - onEvent: (callback: (event: IntegrationsEvent) => void) => () => void - } - aiHist: { - status: () => Promise<{ ok: true; dbPath: string } | { ok: false; reason: string }> - recent: (opts?: { source?: string; project?: string; limit?: number; beforeMs?: number }) => Promise - listSessions: (opts?: { source?: string; project?: string; limit?: number; beforeMs?: number }) => Promise - getSession: (sessionId: string) => Promise - search: ( - query: string, - opts?: { source?: string; project?: string; limit?: number; beforeMs?: number } - ) => Promise - searchSessions: ( - query: string, - opts?: { source?: string; project?: string; limit?: number; beforeMs?: number } - ) => Promise - stats: () => Promise - resumeCommand: (entry: { source: string; sessionId: string | null; project: string | null }) => Promise - reload: () => Promise - } - onMenu: (channel: string, callback: (...args: unknown[]) => void) => () => void -} - -export type AiHistSource = 'claude' | 'codex' | 'cursor' | 'relay' - -export interface AiHistEntry { - id: number - source: AiHistSource - sessionId: string | null - project: string | null - prompt: string - timestampMs: number -} - -export interface AiHistSession { - sessionId: string - source: AiHistSource - project: string | null - firstPrompt: string - firstActivityMs: number - lastActivityMs: number - promptCount: number -} - -export interface AiHistStats { - total: number - bySource: Partial> - byProject: Array<{ project: string; count: number }> - firstTimestampMs: number | null - lastTimestampMs: number | null -} +export * from '@shared/types/ipc' declare global { interface Window { diff --git a/src/shared/types/ipc.ts b/src/shared/types/ipc.ts new file mode 100644 index 0000000..252a5f8 --- /dev/null +++ b/src/shared/types/ipc.ts @@ -0,0 +1,731 @@ +// Shared IPC types — the single source of truth for what crosses the +// preload bridge. Both src/preload/index.ts (the implementation) and +// src/renderer/src/lib/ipc.ts (the consumer-facing re-export) import from +// here. The preload uses `satisfies PearAPI` to enforce that the +// implementation matches this surface. + +export type ViewMode = + | 'terminal' + | 'chat' + | 'graph' + | 'project-settings' + | 'account-settings' + | 'broker-details' + | 'source-control' + | 'ai-hist' + | 'burn-session' + +export type TerminalAttachMode = 'view' | 'drive' | 'passthrough' +export type InboundDeliveryMode = 'auto_inject' | 'manual_flush' +export type MessageInjectionMode = 'wait' | 'steer' +export type AgentCurrentState = 'working' | 'idle' | 'blocked_on_send' + +export type AiHistSource = 'claude' | 'codex' | 'cursor' | 'relay' + +export interface AiHistEntry { + id: number + source: AiHistSource + sessionId: string | null + project: string | null + prompt: string + timestampMs: number +} + +export interface AiHistSession { + sessionId: string + source: AiHistSource + project: string | null + firstPrompt: string + firstActivityMs: number + lastActivityMs: number + promptCount: number +} + +export interface AiHistStats { + total: number + bySource: Partial> + byProject: Array<{ project: string; count: number }> + firstTimestampMs: number | null + lastTimestampMs: number | null +} + +export interface BurnAgentInput { + projectId?: string + name: string + cwd?: string + cli?: string +} + +export interface BurnAgentSummary { + projectId?: string + name: string + agentKey: string + totalTokens: number + totalCost: number + turnCount: number + byModel: Array<{ model: string; tokens: number; cost: number }> + byTool: Array<{ tool: string; tokens: number; cost: number; count: number }> + sessionIds: Array<{ sessionId: string; ts?: string }> + updatedAt: number + status: 'ok' | 'unavailable' + error?: string +} + +export interface BurnAgentBreakdown extends BurnAgentSummary { + primarySessionId?: string + hotspots?: { + sessionId?: string + grandTotal: number + attributedTotal: number + unattributedTotal: number + attributionDegraded: boolean + files: Array<{ path: string; initialTokens: number; persistenceTokens: number; ridingTurns: number; totalCost: number }> + bashVerbs: Array<{ verb: string; callCount: number; distinctCommands: number; initialTokens: number; persistenceTokens: number; avgPersistenceTurns: number; totalCost: number; topExamples: string[] }> + bash: Array<{ command?: string; callCount: number; initialTokens: number; persistenceTokens: number; totalCost: number }> + subagents: Array<{ subagentType: string; callCount: number; initialTokens: number; persistenceTokens: number; totalCost: number }> + } +} + +export interface PendingRelayMessage { + from: string + body: string + target: string + thread_id?: string + project_id?: string + project_alias?: string + priority: number + mode: MessageInjectionMode + queued_at_ms: number + event_id?: string +} + +export interface BrokerListAgent { + name: string + projectId: string + runtime?: string + cli?: string + model?: string + channels?: string[] + parent?: string + pid?: number + last_activity_at?: string + last_activity_ms?: number + current_state?: AgentCurrentState + inboundDeliveryMode?: InboundDeliveryMode +} + +export interface BrokerAgentDetails { + name: string + runtime: string + cli?: string + model?: string + channels: string[] + parent?: string + pid?: number + currentState?: AgentCurrentState +} + +export interface BrokerDetails { + projectId: string + name: string + cwd: string + channels: string[] + kind: 'local' | 'cloud' + url?: string + port?: number + apiKey?: string + brokerPid?: number + cloudSandboxId?: string | null + connectionPath?: string + connectionFileStatus?: 'matches' | 'missing' | 'different' | 'invalid' + apiKeyAvailable: boolean + health: 'connected' | 'unreachable' + session?: { + brokerVersion: string + protocolVersion: number + workspaceKey?: string + defaultWorkspaceId?: string + mode: string + uptimeSecs: number + } + relaycast?: { + workspaceKey?: string + defaultWorkspaceId?: string + authenticated?: boolean + workspaceCount?: number + workspaces: Array<{ + workspaceId: string + workspaceAlias?: string | null + selfName: string + selfAgentId: string + authenticated: boolean + default: boolean + }> + } + agentCount: number + pendingDeliveryCount: number + agents: BrokerAgentDetails[] + error?: string +} + +export interface BrokerEventRecord { + id: string + projectId: string + timestamp: number + event: Record & { + kind?: string + projectId?: string + } +} + +export interface BrokerSpawnAgentInput { + name: string + cli: string + model?: string + task?: string + channels?: string[] + cwd?: string + args?: string[] +} + +export interface BrokerSpawnAgentResult { + name: string + runtime: string +} + +export interface BrokerAttachTerminalInput { + projectId?: string + name: string + rows?: number + cols?: number + mode?: TerminalAttachMode +} + +export interface BrokerAttachTerminalResult { + name: string + mode: InboundDeliveryMode + previousMode?: InboundDeliveryMode + pending: number + snapshot?: { + rows: number + cols: number + cursor: [number, number] + screen: string + } +} + +export interface BrokerSetTerminalModeResult { + name: string + mode: InboundDeliveryMode + flushed: number + pending: number +} + +export interface BrokerSendMessageInput { + to: string + text: string + from?: string +} + +export interface BrokerStatusEvent { + projectId?: string + status: string + error?: string +} + +export type GitFileStatusKind = 'added' | 'modified' | 'deleted' | 'renamed' | 'untracked' + +export interface GitFileStatus { + path: string + oldPath?: string + status: GitFileStatusKind + staged: boolean +} + +export interface GitSummary { + branch: string + additions: number + deletions: number +} + +export interface GitHistoryFile { + path: string + oldPath?: string + status: string +} + +export interface GitHistoryCoAuthor { + name: string + email: string + avatarUrl?: string + cachedAvatarUrl?: string +} + +export interface GitHistoryCommit { + hash: string + shortHash: string + author: string + authorEmail: string + authorAvatarUrl?: string + authorCachedAvatarUrl?: string + coAuthors: GitHistoryCoAuthor[] + date: string + subject: string + body: string + tags: string[] + additions: number + deletions: number + files: GitHistoryFile[] +} + +export interface GitCommitDraft { + title: string + body: string +} + +export interface GitCommitSelectionInput { + title: string + body?: string + wholeFiles: string[] + patch?: string +} + +export interface GitGenerateCommitMessageInput { + wholeFiles: string[] + patch?: string +} + +export interface GitBranchInfo { + name: string + current: boolean + remote: boolean + lastCommitDate: string + defaultBranch: boolean +} + +export interface GitBranchSyncStatus { + branch: string + remote: string | null + upstream: string | null + ahead: number + behind: number + hasRemote: boolean +} + +export interface GitCheckoutBranchOptions { + stashChanges?: boolean +} + +export interface FsDirEntry { + name: string + path: string + type: 'file' | 'directory' +} + +export interface FsReadPreviewResult { + kind: 'text' | 'binary' | 'too-large' | 'missing' + content: string + size: number +} + +export type CloudAgentRecord = { + id: string + name: string + displayName?: string + harness: string + defaultModel: string + status: 'ready' | 'warming' | 'error' | 'stopped' + lastUsedAt?: string + lastError?: string + lastAuthenticatedAt?: string | null +} + +export type CreateCloudAgentInput = { + name: string + harness: string + model: string +} + +export type CloudAgentSandboxStatus = 'warming' | 'ready' | 'failed' | 'stopping' | 'stopped' + +export type CloudAgentBinding = { + projectId: string + cloudAgentId: string + sandboxId: string + relayfileMountPath: string + attachedAt: string +} + +export type CloudAgentMountStatus = { + ready: boolean + lastReconcileAt?: string + pendingWrites: number + conflicts: number +} + +export type CloudAgentSyncMode = 'sandbox-priority' | 'local-priority' + +export type CloudAgentStatus = { + binding: CloudAgentBinding + sandbox: { id: string; status: CloudAgentSandboxStatus } + mount: CloudAgentMountStatus + syncMode: CloudAgentSyncMode +} + +export type CloudAgentEvent = + | { type: 'sandbox-status'; projectId: string; status: CloudAgentSandboxStatus } + | { type: 'mount-status'; projectId: string; mount: CloudAgentMountStatus } + | { type: 'sync-mode-changed'; projectId: string; syncMode: CloudAgentSyncMode } + | { type: 'error'; projectId: string; message: string } + +export type ProactiveAgentHarness = 'claude' | 'codex' | 'opencode' +export type ProactiveAgentStatus = 'draft' | 'warming' | 'active' | 'paused' | 'error' +export type ProactiveAgentRunStatus = 'running' | 'succeeded' | 'failed' +export type ProactiveAgentRunMode = 'cloud' | 'local' +export type ProactiveAgentWatchEventKind = 'created' | 'updated' | 'deleted' + +export type ProactiveAgentDraft = { + id: string + name: string + description?: string + cloudAgentId: string + harness: ProactiveAgentHarness + model: string + systemPrompt: string + integrations: Record> + watch: Array<{ + paths: string[] + events: ProactiveAgentWatchEventKind[] + debounceMs?: number + match?: string + }> + handlerCode: string + inputs?: Record + memory?: { enabled: boolean; scopes?: Array<'workspace' | 'project' | 'persona'>; ttlDays?: number } + harnessSettings?: { reasoning?: 'low' | 'medium' | 'high'; timeoutSeconds?: number } + mount?: { enabled: boolean } + runMode?: ProactiveAgentRunMode +} + +export type ProactiveAgentBinding = { + projectId: string + personaId: string + cloudAgentId: string + status: ProactiveAgentStatus + lastError?: string + lastFiredAt?: string + createdAt: string + updatedAt: string + draft: ProactiveAgentDraft +} + +export type ProactiveAgentRun = { + runId: string + projectId: string + personaId: string + firedAt: string + trigger: { + type: 'relayfile-change' + path: string + eventKind: ProactiveAgentWatchEventKind + } + durationMs?: number + status: ProactiveAgentRunStatus + summary?: string + error?: string +} + +export type ProactiveAgentTranscript = { + runId: string + projectId?: string + personaId?: string + messages: Array<{ + role: 'system' | 'user' | 'assistant' | 'tool' + content: string + ts: string + }> +} + +export type ProactiveAgentRunsPage = { + runs: ProactiveAgentRun[] + nextCursor?: string +} + +export type ProactiveAgentDeployResult = { + status: 'active' | 'warming' | 'error' + error?: string +} + +export type ProactiveAgentRunsOptions = { + limit?: number + cursor?: string +} + +export type ProactiveAgentEvent = + | { type: 'binding-updated'; projectId: string; personaId: string; binding: ProactiveAgentBinding } + | { type: 'binding-removed'; projectId: string; personaId: string } + | { type: 'run-started'; projectId: string; personaId: string; run: ProactiveAgentRun } + | { type: 'run-update'; projectId: string; personaId: string; runId: string; chunk: string } + | { type: 'run-finished'; projectId: string; personaId: string; run: ProactiveAgentRun } + +export type IntegrationAuthMethod = 'oauth' | 'token' | 'apikey' + +export type IntegrationCapabilities = { + webhook: boolean + poll: boolean + writeback: boolean +} + +export type IntegrationAdapter = { + provider: string + displayName: string + iconUrl?: string + version: string + capabilities: IntegrationCapabilities + authMethod: IntegrationAuthMethod + requiredScopes?: string[] + defaultMountPaths: string[] + description: string +} + +export type ConnectedIntegration = { + provider: string + integrationId: string + scope: Record + mountPaths: string[] + connectedAt: string + notifyAgent: boolean + lastSyncAt?: string + lastError?: string +} + +export type IntegrationConnectStatus = + | 'pending' + | 'awaiting-user' + | 'choosing-scope' + | 'completed' + | 'error' + | 'expired' + +export type IntegrationConnectSession = { + sessionId: string + provider: string + status: IntegrationConnectStatus + authUrl?: string + scopeChoices?: Record + integrationId?: string + error?: string +} + +export type IntegrationsEvent = + | { type: 'session-update'; sessionId: string; session: IntegrationConnectSession } + | { type: 'integration-added'; projectId: string; integration: ConnectedIntegration } + | { type: 'integration-removed'; projectId: string; integrationId: string } + | { type: 'integration-error'; projectId: string; integrationId: string; message: string } + +export interface AuthUser { + name?: string + email?: string + githubUsername?: string + username?: string + avatarUrl?: string + cachedAvatarUrl?: string + organizationName?: string + projectName?: string +} + +export interface AuthStatus { + loggedIn: boolean + apiUrl?: string + user?: AuthUser +} + +export interface AuthLoginInput { + apiUrl?: string +} + +export interface AiHistRecentOptions { + source?: string + project?: string + limit?: number + beforeMs?: number +} + +export interface AiHistStatusResult { + ok: true + dbPath: string +} + +export interface AiHistStatusError { + ok: false + reason: string +} + +export type AiHistStatusResponse = AiHistStatusResult | AiHistStatusError + +export interface AiHistResumeEntry { + source: string + sessionId: string | null + project: string | null +} + +export interface ProjectListResult { + projects: unknown[] + activeId: string | null +} + +export interface PearAPI { + app: { + confirmQuit: () => Promise + } + project: { + list: () => Promise + add: (name: string, rootPath?: string) => Promise + remove: (id: string) => Promise + setActive: (id: string | null) => Promise + update: (id: string, update: Record) => Promise + addChannel: (projectId: string, name: string) => Promise + removeChannel: (projectId: string, name: string) => Promise + setChannelPeople: (projectId: string, channelName: string, people: string[]) => Promise + addRoot: (projectId: string, name?: string, rootPath?: string) => Promise + removeRoot: (projectId: string, rootId: string) => Promise + addIntegration: (projectId: string, name: string, type?: string) => Promise + removeIntegration: (projectId: string, integrationId: string) => Promise + } + broker: { + start: (projectId: string, cwd: string, name: string, channels?: string[]) => Promise + syncChannels: (projectId: string, channels: string[]) => Promise + autoFixRuntime: ( + projectId: string, + cwd: string, + name: string, + channels?: string[], + errorMessage?: string + ) => Promise<{ removed: string[] }> + connectCloud: () => Promise + spawnAgent: (projectId: string, input: BrokerSpawnAgentInput) => Promise + attachTerminal: (input: BrokerAttachTerminalInput) => Promise + sendInputFast: (projectId: string | undefined, name: string, data: string) => void + setTerminalMode: ( + projectId: string | undefined, + name: string, + mode: TerminalAttachMode + ) => Promise + getPending: (projectId: string | undefined, name: string) => Promise + flushPending: (projectId: string | undefined, name: string) => Promise<{ flushed: number }> + resizePty: (projectId: string | undefined, name: string, rows: number, cols: number) => Promise + sendMessage: (projectId: string | undefined, input: BrokerSendMessageInput) => Promise + subscribeAgentChannel: (projectId: string | undefined, name: string, channel: string) => Promise + unsubscribeAgentChannel: (projectId: string | undefined, name: string, channel: string) => Promise + releaseAgent: (projectId: string | undefined, name: string) => Promise + listAgents: (projectId?: string) => Promise + listDetails: () => Promise + listEvents: () => Promise + shutdown: () => Promise + onEvent: (callback: (event: unknown) => void) => () => void + onPtyChunk: (callback: (projectId: string, name: string, chunk: string) => void) => () => void + onStatus: (callback: (status: BrokerStatusEvent) => void) => () => void + } + burn: { + listAgentSummaries: (agents: BurnAgentInput[]) => Promise + getAgentBreakdown: (agent: BurnAgentInput) => Promise + } + git: { + status: (path: string) => Promise + diff: (path: string, file?: string) => Promise + fileContent: (path: string, file: string, revision?: string) => Promise + summary: (path: string) => Promise + branches: (root: string) => Promise + branchDetails: (root: string) => Promise + checkoutBranch: ( + root: string, + branch: string, + options?: GitCheckoutBranchOptions + ) => Promise + branchSyncStatus: (root: string) => Promise + fetchRemote: (root: string) => Promise + pullCurrentBranch: (root: string) => Promise + pushCurrentBranch: (root: string) => Promise + history: (path: string, limit?: number) => Promise + show: (path: string, hash: string, file?: string) => Promise + discardFiles: (path: string, files: string[]) => Promise + addGitignorePatterns: (path: string, patterns: string[]) => Promise + commitSelection: (path: string, input: GitCommitSelectionInput) => Promise<{ hash: string }> + generateCommitMessage: ( + path: string, + input: GitGenerateCommitMessageInput + ) => Promise + } + fs: { + listDir: (dirPath: string) => Promise + readPreview: (filePath: string) => Promise + revealPath: (filePath: string) => Promise + } + auth: { + login: (input?: AuthLoginInput) => Promise + logout: () => Promise + status: () => Promise + } + cloudAgent: { + list: () => Promise + create: (input: CreateCloudAgentInput) => Promise + delete: (id: string) => Promise + attach: (projectId: string, cloudAgentId: string) => Promise + detach: (projectId: string) => Promise + status: (projectId: string) => Promise + onEvent: (callback: (event: CloudAgentEvent) => void) => () => void + } + proactiveAgent: { + list: (projectId: string) => Promise + create: (projectId: string, draft: ProactiveAgentDraft) => Promise + update: ( + projectId: string, + personaId: string, + draft: ProactiveAgentDraft + ) => Promise + deploy: (projectId: string, personaId: string) => Promise + pause: (projectId: string, personaId: string) => Promise + resume: (projectId: string, personaId: string) => Promise + undeploy: (projectId: string, personaId: string) => Promise + runs: ( + projectId: string, + personaId: string, + opts?: ProactiveAgentRunsOptions + ) => Promise + runTranscript: (runId: string) => Promise + onEvent: (callback: (event: ProactiveAgentEvent) => void) => () => void + } + integrations: { + catalog: () => Promise + list: (projectId: string) => Promise + startConnect: (projectId: string, provider: string) => Promise + pollConnect: (sessionId: string) => Promise + completeConnect: ( + projectId: string, + sessionId: string, + scope: Record, + mountPaths: string[], + notifyAgent: boolean + ) => Promise + updateScope: ( + projectId: string, + integrationId: string, + scope: Record, + mountPaths: string[] + ) => Promise + disconnect: (projectId: string, integrationId: string) => Promise + onEvent: (callback: (event: IntegrationsEvent) => void) => () => void + } + aiHist: { + status: () => Promise + recent: (opts?: AiHistRecentOptions) => Promise + listSessions: (opts?: AiHistRecentOptions) => Promise + getSession: (sessionId: string) => Promise + search: (query: string, opts?: AiHistRecentOptions) => Promise + searchSessions: (query: string, opts?: AiHistRecentOptions) => Promise + stats: () => Promise + resumeCommand: (entry: AiHistResumeEntry) => Promise + reload: () => Promise + } + onMenu: (channel: string, callback: (...args: unknown[]) => void) => () => void +}