| import { contextBridge, ipcRenderer } from 'electron'; |
| import { encodeIngestItems } from './attachment-ingest-payload.js'; |
| import type { |
| MakaBridge, |
| OnboardingSnapshot, |
| DesktopTaskSubmissionReadinessRequest, |
| PermissionActionResult, |
| PermissionOverlayStartResult, |
| RendererIngestInput, |
| DesktopBranchFromTurnInput, |
| DesktopReviseBeforeTurnInput, |
| AppUpdateInstallRequest, |
| AppUpdateInstallResult, |
| AppUpdateStatus, |
| WindowCommand, |
| PetPackChangedEvent, |
| } from './bridge-contract.js'; |
| import type { ExternalSessionImportIpcResult } from './external-session-import-result.js'; |
| import type { |
| DesktopDiagnosticCopyResult, |
| DesktopErrorDiagnosticInput, |
| } from './diagnostics-contract.js'; |
| import type { |
| ConnectionEvent, |
| ConnectionTestResult, |
| CreateConnectionInput, |
| AppSettings, |
| BotProvider, |
| BotOnboardingSnapshot, |
| BotOnboardingStartInput, |
| HealthSnapshot, |
| ExecutionBoundaryReadModel, |
| LlmConnection, |
| ModelDiscoveryResult, |
| ModelInfo, |
| ActiveInteractionRequestEvent, |
| SandboxBoundaryResponse, |
| UserQuestionResponse, |
| PermissionMode, |
| CollaborationMode, |
| OrchestrationMode, |
| TurnOrchestration, |
| PlanSessionState, |
| SearchErrorReason, |
| SearchRequest, |
| SearchResult, |
| SettingsTestResult, |
| SessionCommand, |
| SessionChangedEvent, |
| SessionEvent, |
| SessionListFilter, |
| SessionSummary, |
| ShellRunUpdate, |
| StoredMessage, |
| ThinkingLevel, |
| UpdateConnectionInput, |
| UpdateAppSettingsInput, |
| UpdateAppSettingsResult, |
| UsageRange, |
| UsageStats, |
| E2eFixtureState, |
| ExternalSessionSummary, |
| GitReviewReadResult, |
| GitReviewMutationAction, |
| GitReviewMutationResult, |
| GitReviewSource, |
| ArtifactBinaryReadResult, |
| ArtifactChangedEvent, |
| ArtifactDescriptor, |
| ArtifactSaveResult, |
| ArtifactTextReadResult, |
| CapabilitySnapshotCollection, |
| RegenerateTurnInput, |
| TurnRecord, |
| PermissionSnapshot, |
| LocalMemoryEntryPreview, |
| LocalMemoryState, |
| AuthorizationUrlPayload, |
| SubscriptionAccountState, |
| SubscriptionActionResult, |
| PlanReminder, |
| ProjectRecord, |
| PlanReminderDeliveryTarget, |
| PlanReminderRecurrence, |
| DailyReviewArchive, |
| DailyReviewArchiveSummary, |
| QueueEnqueueOutcome, |
| DailyReviewConfig, |
| DailyReviewRange, |
| DailyReviewSummary, |
| WebSearchProvider, |
| WebSearchResponse, |
| BrowserState, |
| BrowserViewRect, |
| ThemePreference, |
| Task, |
| TaskLedgerChangedEvent, |
| DeepResearchChangedEvent, |
| DeepResearchClientProgress, |
| } from '@maka/core'; |
| import type { SessionTrace } from '@maka/core'; |
| import type { |
| AgentGraphClientChangedEvent, |
| AgentGraphClientSnapshot, |
| AgentGraphClientSnapshotOptions, |
| AgentGraphOperatorInspection, |
| BotStatus, |
| ShellRunPtyDataEvent, |
| ShellRunPtySnapshot, |
| WechatBridgeQrCodeResult, |
| } from '@maka/runtime'; |
| import type { GoalState } from '@maka/runtime'; |
| import type { BundledSkillCatalogEntry, ManagedSkillSourceEntry, ManagedSkillUpdatePreview, SkillEntry, SkillGovernanceDetails } from '@maka/ui'; |
| import type { ConfigCategory } from '@maka/storage'; |
| import type { TestProxyInput } from '@maka/core'; |
| import type { Result } from '@maka/core'; |
| import type { CreateSessionRequestInput } from '@maka/core'; |
| import type { |
| McpConfigFile, |
| McpServerConfig, |
| McpServerStatus, |
| McpTestResult, |
| } from '@maka/core'; |
| import type { |
| AttachmentRef, |
| InlineReference, |
| OnboardingMilestoneId, |
| QuoteRef, |
| } from '@maka/core'; |
| |
| type LocalMemoryMutationResult = |
| | { ok: true; state: LocalMemoryState; entry?: LocalMemoryEntryPreview; proposal?: LocalMemoryEntryPreview } |
| | { ok: false; state: LocalMemoryState; reason: string; message: string }; |
| |
| const makaBridge = { |
| pets: { |
| list() { |
| return ipcRenderer.invoke('pets:list'); |
| }, |
| getSelection() { |
| return ipcRenderer.invoke('pets:getSelection'); |
| }, |
| select(petId: string | null) { |
| return ipcRenderer.invoke('pets:select', petId); |
| }, |
| readSpriteSheet(petId: string) { |
| return ipcRenderer.invoke('pets:readSpriteSheet', petId); |
| }, |
| remove(petId: string) { |
| return ipcRenderer.invoke('pets:remove', petId); |
| }, |
| importLocalDirectory() { |
| return ipcRenderer.invoke('pets:importLocalDirectory'); |
| }, |
| subscribeChanges(handler: (event: PetPackChangedEvent) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: PetPackChangedEvent) => |
| handler(payload); |
| ipcRenderer.on('pets:changed', listener); |
| return () => ipcRenderer.off('pets:changed', listener); |
| }, |
| }, |
| tasks: { |
| list(sessionId: string): Promise<Task[]> { |
| return ipcRenderer.invoke('tasks:list', sessionId); |
| }, |
| subscribeChanges(handler: (event: TaskLedgerChangedEvent) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: TaskLedgerChangedEvent) => handler(payload); |
| ipcRenderer.on('tasks:changed', listener); |
| return () => ipcRenderer.off('tasks:changed', listener); |
| }, |
| }, |
| deepResearch: { |
| get(sessionId: string): Promise<DeepResearchClientProgress | undefined> { |
| return ipcRenderer.invoke('deepResearch:get', sessionId); |
| }, |
| subscribeChanges(handler: (event: DeepResearchChangedEvent) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: DeepResearchChangedEvent) => |
| handler(payload); |
| ipcRenderer.on('deepResearch:changed', listener); |
| return () => ipcRenderer.off('deepResearch:changed', listener); |
| }, |
| }, |
| graphs: { |
| getSnapshot( |
| rootSessionId: string, |
| options?: AgentGraphClientSnapshotOptions, |
| ): Promise<AgentGraphClientSnapshot> { |
| return ipcRenderer.invoke('graphs:getSnapshot', rootSessionId, options); |
| }, |
| inspectOperator( |
| rootSessionId: string, |
| operatorId: string, |
| ): Promise<AgentGraphOperatorInspection> { |
| return ipcRenderer.invoke('graphs:inspectOperator', rootSessionId, operatorId); |
| }, |
| stop(rootSessionId: string): Promise<void> { |
| return ipcRenderer.invoke('graphs:stop', rootSessionId); |
| }, |
| subscribe( |
| rootSessionId: string, |
| handler: (event: AgentGraphClientChangedEvent) => void, |
| ): () => void { |
| const listener = ( |
| _event: Electron.IpcRendererEvent, |
| payload: AgentGraphClientChangedEvent, |
| ) => { |
| if (payload.rootSessionId === rootSessionId) handler(payload); |
| }; |
| ipcRenderer.on('graphs:changed', listener); |
| return () => ipcRenderer.off('graphs:changed', listener); |
| }, |
| }, |
| sessions: { |
| list(filter?: SessionListFilter): Promise<SessionSummary[]> { |
| return ipcRenderer.invoke('sessions:list', filter); |
| }, |
| /** |
| * The single session-creation channel (#1433). `mode` names a |
| * product intent — main derives the permission boundary, name and |
| * labels it implies (`create-session-input.ts`); the renderer cannot |
| * reach a boundary like `explore` by asking for it directly. |
| */ |
| create(input?: CreateSessionRequestInput): Promise<SessionSummary> { |
| return ipcRenderer.invoke('sessions:create', input); |
| }, |
| async send( |
| sessionId: string, |
| command: |
| | SessionCommand |
| | { |
| type: 'send'; |
| turnId: string; |
| text: string; |
| displayText?: string; |
| skillIds?: string[]; |
| attachmentItems?: RendererIngestInput[]; |
| turnOrchestration?: TurnOrchestration; |
| quotes?: QuoteRef[]; |
| workspaceFileReferences?: Array<Pick<InlineReference, 'value' | 'start'>>; |
| }, |
| ): Promise< |
| | { |
| ok: true; |
| turnId: string; |
| attachments: AttachmentRef[]; |
| inlineReferences: InlineReference[]; |
| skillInvocation: import('@maka/runtime').SkillInvocationResult; |
| } |
| | { |
| ok: false; |
| reason: 'skill_invocation_failed'; |
| skillInvocation: import('@maka/runtime').SkillInvocationResult; |
| } |
| > { |
| if (command.type === 'send' && 'attachmentItems' in command && command.attachmentItems) { |
| const encoded = await encodeIngestItems(command.attachmentItems as RendererIngestInput[]); |
| return ipcRenderer.invoke('sessions:send', sessionId, { ...command, attachmentItems: encoded }); |
| } |
| return ipcRenderer.invoke('sessions:send', sessionId, command); |
| }, |
| compact(sessionId: string): Promise<void> { |
| return ipcRenderer.invoke('sessions:compact', sessionId); |
| }, |
| resumeLatest(sessionId: string): Promise< |
| | { disposition: 'started'; runId: string; turnId: string } |
| | { disposition: 'park'; rejectionReasons: string[]; diagnostics: unknown[] } |
| > { |
| return ipcRenderer.invoke('sessions:resumeLatest', sessionId); |
| }, |
| stop(sessionId: string, input?: { source?: 'stop_button' }): Promise<void> { |
| return ipcRenderer.invoke('sessions:stop', sessionId, input); |
| }, |
| steer(sessionId: string, text: string): Promise<QueueEnqueueOutcome> { |
| return ipcRenderer.invoke('sessions:steer', sessionId, text); |
| }, |
| readMessages(sessionId: string): Promise<StoredMessage[]> { |
| return ipcRenderer.invoke('sessions:readMessages', sessionId); |
| }, |
| readExecutionBoundary(sessionId: string): Promise<ExecutionBoundaryReadModel> { |
| return ipcRenderer.invoke('sessions:readExecutionBoundary', sessionId); |
| }, |
| listActiveInteractions(sessionId: string): Promise<ActiveInteractionRequestEvent[]> { |
| return ipcRenderer.invoke('sessions:listActiveInteractions', sessionId); |
| }, |
| listTurns(sessionId: string): Promise<TurnRecord[]> { |
| return ipcRenderer.invoke('sessions:listTurns', sessionId); |
| }, |
| regenerateTurn(sessionId: string, input: RegenerateTurnInput): Promise<void> { |
| return ipcRenderer.invoke('sessions:regenerateTurn', sessionId, input); |
| }, |
| branchFromTurn(sessionId: string, input: DesktopBranchFromTurnInput): Promise<SessionSummary> { |
| return ipcRenderer.invoke('sessions:branchFromTurn', sessionId, input); |
| }, |
| reviseBeforeTurn(sessionId: string, input: DesktopReviseBeforeTurnInput): Promise<SessionSummary> { |
| return ipcRenderer.invoke('sessions:reviseBeforeTurn', sessionId, input); |
| }, |
| respondToSandboxBoundary(sessionId: string, response: SandboxBoundaryResponse): Promise<void> { |
| return ipcRenderer.invoke('sessions:respondToSandboxBoundary', sessionId, response); |
| }, |
| respondToUserQuestion(sessionId: string, response: UserQuestionResponse): Promise<void> { |
| return ipcRenderer.invoke('sessions:respondToUserQuestion', sessionId, response); |
| }, |
| /** |
| * PR-CMD-PALETTE-SAVE-CONVERSATION-FILE-0: write the renderer-formatted |
| * conversation markdown to a user-chosen file. Renderer owns the |
| * `renderConversationMarkdown` step (it knows the session name + raw |
| * message stream); main owns the save dialog + file write. |
| */ |
| saveConversationToFile(input: { |
| markdown: string; |
| defaultName: string; |
| }): Promise< |
| { ok: true; path: string } | { ok: false; reason: 'canceled' | 'write_failed' | 'invalid_input' } |
| > { |
| return ipcRenderer.invoke('chat:saveConversationToFile', input); |
| }, |
| subscribeEvents(sessionId: string, handler: (event: SessionEvent) => void): () => void { |
| const channel = `sessions:event:${sessionId}`; |
| const listener = (_event: Electron.IpcRendererEvent, payload: SessionEvent) => handler(payload); |
| ipcRenderer.on(channel, listener); |
| const observerId = crypto.randomUUID(); |
| void ipcRenderer.invoke('sessions:observe', sessionId, observerId).catch(() => undefined); |
| return () => { |
| ipcRenderer.off(channel, listener); |
| void ipcRenderer.invoke('sessions:unobserve', observerId).catch(() => undefined); |
| }; |
| }, |
| subscribeChanges(handler: (event: SessionChangedEvent) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: SessionChangedEvent) => handler(payload); |
| ipcRenderer.on('sessions:changed', listener); |
| return () => ipcRenderer.off('sessions:changed', listener); |
| }, |
| archive(sessionId: string, options?: { revisionFamily?: boolean }): Promise<void> { |
| return ipcRenderer.invoke('sessions:archive', sessionId, options); |
| }, |
| unarchive(sessionId: string, options?: { revisionFamily?: boolean }): Promise<void> { |
| return ipcRenderer.invoke('sessions:unarchive', sessionId, options); |
| }, |
| setFlagged(sessionId: string, isFlagged: boolean, options?: { revisionFamily?: boolean }): Promise<void> { |
| return ipcRenderer.invoke('sessions:setFlagged', sessionId, isFlagged, options); |
| }, |
| rename(sessionId: string, name: string, options?: { revisionFamily?: boolean }): Promise<void> { |
| return ipcRenderer.invoke('sessions:rename', sessionId, name, options); |
| }, |
| setPermissionMode(sessionId: string, mode: PermissionMode): Promise<SessionSummary> { |
| return ipcRenderer.invoke('sessions:setPermissionMode', sessionId, mode); |
| }, |
| setCollaborationMode(sessionId: string, mode: CollaborationMode): Promise<SessionSummary> { |
| return ipcRenderer.invoke('sessions:setCollaborationMode', sessionId, mode); |
| }, |
| setOrchestrationMode(sessionId: string, mode: OrchestrationMode): Promise<SessionSummary> { |
| return ipcRenderer.invoke('sessions:setOrchestrationMode', sessionId, mode); |
| }, |
| getPlanState(sessionId: string): Promise<PlanSessionState> { |
| return ipcRenderer.invoke('plan-mode:getState', sessionId); |
| }, |
| subscribePlanChanges(sessionId: string, handler: () => void): () => void { |
| const channel = 'plan-mode:changed'; |
| const listener = (_event: Electron.IpcRendererEvent, payload: { sessionId: string }) => { |
| if (payload.sessionId === sessionId) handler(); |
| }; |
| ipcRenderer.on(channel, listener); |
| return () => ipcRenderer.off(channel, listener); |
| }, |
| requestPlanRevision(sessionId: string, proposalId: string): Promise<PlanSessionState> { |
| return ipcRenderer.invoke('plan-mode:requestRevision', sessionId, proposalId); |
| }, |
| abandonPlanProposal( |
| sessionId: string, |
| proposalId: string, |
| ): Promise<PlanSessionState> { |
| return ipcRenderer.invoke('plan-mode:abandon', sessionId, proposalId); |
| }, |
| approvePlan(sessionId: string, input: { |
| proposalId: string; |
| expectedRevision: number; |
| expectedStoreVersion: number; |
| turnId: string; |
| }): Promise<{ turnId: string; executionId: string }> { |
| return ipcRenderer.invoke('plan-mode:approve', sessionId, input); |
| }, |
| resumePlan(sessionId: string, executionId: string, turnId: string): Promise<{ |
| turnId: string; |
| executionId: string; |
| }> { |
| return ipcRenderer.invoke('plan-mode:resume', sessionId, executionId, turnId); |
| }, |
| abandonPlanExecution(sessionId: string, executionId: string): Promise<PlanSessionState> { |
| return ipcRenderer.invoke('plan-mode:abandonExecution', sessionId, executionId); |
| }, |
| setModel(sessionId: string, input: { llmConnectionSlug: string; model: string }): Promise<SessionSummary> { |
| return ipcRenderer.invoke('sessions:setModel', sessionId, input); |
| }, |
| setThinkingLevel(sessionId: string, level: ThinkingLevel | undefined | null): Promise<SessionSummary> { |
| return ipcRenderer.invoke('sessions:setThinkingLevel', sessionId, level ?? undefined); |
| }, |
| remove(sessionId: string, options?: { revisionFamily?: boolean }): Promise<void> { |
| return ipcRenderer.invoke('sessions:remove', sessionId, options); |
| }, |
| cleanupSessionCopy(sessionId: string): Promise<void> { |
| return ipcRenderer.invoke('sessions:cleanupSessionCopy', sessionId); |
| }, |
| abandonSessionCopy(sessionId: string): Promise<void> { |
| return ipcRenderer.invoke('sessions:abandonSessionCopy', sessionId); |
| }, |
| }, |
| externalSessions: { |
| listSources(): Promise<{ adapterIds: string[] }> { |
| return ipcRenderer.invoke('external-sessions:listSources'); |
| }, |
| list(input: { |
| adapterId: string; |
| includeArchived?: boolean; |
| cwd?: string; |
| cursor?: string; |
| }): Promise<{ sessions: ExternalSessionSummary[]; nextCursor: string | null }> { |
| return ipcRenderer.invoke('external-sessions:list', input); |
| }, |
| import(input: { |
| adapterId: string; |
| sourceSessionId: string; |
| }): Promise<ExternalSessionImportIpcResult> { |
| return ipcRenderer.invoke('external-sessions:import', input); |
| }, |
| }, |
| projects: { |
| list(): Promise<ProjectRecord[]> { |
| return ipcRenderer.invoke('projects:list'); |
| }, |
| add(): Promise< |
| { ok: true; project: ProjectRecord; path: string } | { ok: false; reason: 'cancelled' } |
| > { |
| return ipcRenderer.invoke('projects:add'); |
| }, |
| select( |
| projectId: string | null, |
| ): Promise<{ project: ProjectRecord | null; path: string }> { |
| return ipcRenderer.invoke('projects:select', projectId); |
| }, |
| relink(projectId: string): Promise< |
| { ok: true; project: ProjectRecord } | { ok: false; reason: 'cancelled' } |
| > { |
| return ipcRenderer.invoke('projects:relink', projectId); |
| }, |
| reveal(projectId: string): Promise< |
| | { ok: true; opened: string } |
| | { |
| ok: false; |
| reason: 'unknown-key' | 'not-allowed' | 'missing' | 'not-a-directory' | 'open-failed'; |
| } |
| > { |
| return ipcRenderer.invoke('projects:reveal', projectId); |
| }, |
| rename(projectId: string, name: string): Promise<ProjectRecord> { |
| return ipcRenderer.invoke('projects:rename', projectId, name); |
| }, |
| archive(projectId: string): Promise<ProjectRecord> { |
| return ipcRenderer.invoke('projects:archive', projectId); |
| }, |
| restore(projectId: string): Promise<ProjectRecord> { |
| return ipcRenderer.invoke('projects:restore', projectId); |
| }, |
| }, |
| shellRuns: { |
| list(sessionId: string): Promise<ShellRunUpdate[]> { |
| return ipcRenderer.invoke('shell-runs:list', sessionId); |
| }, |
| attach(input: { |
| sessionId: string; |
| ref: string; |
| }): Promise<ShellRunPtySnapshot | null> { |
| return ipcRenderer.invoke('shell-runs:attach', input); |
| }, |
| detach(input: { sessionId: string; ref: string }): Promise<void> { |
| return ipcRenderer.invoke('shell-runs:detach', input); |
| }, |
| start(sessionId: string): Promise<ShellRunUpdate> { |
| return ipcRenderer.invoke('shell-runs:start', sessionId); |
| }, |
| write(input: { |
| sessionId: string; |
| ref: string; |
| input?: string; |
| size?: { cols: number; rows: number }; |
| }): Promise<ShellRunUpdate | null> { |
| return ipcRenderer.invoke('shell-runs:write', input); |
| }, |
| stop(input: { |
| sessionId: string; |
| ref: string; |
| }): Promise<ShellRunUpdate | null> { |
| return ipcRenderer.invoke('shell-runs:stop', input); |
| }, |
| subscribeUpdates(handler: (update: ShellRunUpdate) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, update: ShellRunUpdate) => handler(update); |
| ipcRenderer.on('shell-runs:update', listener); |
| return () => ipcRenderer.off('shell-runs:update', listener); |
| }, |
| subscribePtyData(handler: (event: ShellRunPtyDataEvent) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: ShellRunPtyDataEvent) => |
| handler(payload); |
| ipcRenderer.on('shell-runs:pty-data', listener); |
| return () => ipcRenderer.off('shell-runs:pty-data', listener); |
| }, |
| }, |
| gitReview: { |
| read(input: { |
| sessionId: string; |
| source: GitReviewSource; |
| baseBranch?: string; |
| }): Promise<GitReviewReadResult> { |
| return ipcRenderer.invoke('git-review:read', input); |
| }, |
| mutate(input: { |
| sessionId: string; |
| source: Extract<GitReviewSource, 'unstaged' | 'staged'>; |
| revision: string; |
| path: string; |
| action: GitReviewMutationAction; |
| }): Promise<GitReviewMutationResult> { |
| return ipcRenderer.invoke('git-review:mutate', input); |
| }, |
| }, |
| goal: { |
| get(sessionId: string): Promise<GoalState | null> { |
| return ipcRenderer.invoke('goal:get', sessionId); |
| }, |
| clear(sessionId: string): Promise<void> { |
| return ipcRenderer.invoke('goal:clear', sessionId); |
| }, |
| }, |
| connections: { |
| list(): Promise<LlmConnection[]> { |
| return ipcRenderer.invoke('connections:list'); |
| }, |
| getDefault(): Promise<string | null> { |
| return ipcRenderer.invoke('connections:getDefault'); |
| }, |
| setDefault(slug: string | null): Promise<void> { |
| return ipcRenderer.invoke('connections:setDefault', slug); |
| }, |
| setDefaultModel(input: { slug: string; model: string } | null): Promise<void> { |
| return ipcRenderer.invoke('connections:setDefaultModel', input); |
| }, |
| create(input: CreateConnectionInput): Promise<LlmConnection> { |
| return ipcRenderer.invoke('connections:create', input); |
| }, |
| update(slug: string, patch: UpdateConnectionInput): Promise<LlmConnection> { |
| return ipcRenderer.invoke('connections:update', slug, patch); |
| }, |
| delete(slug: string): Promise<void> { |
| return ipcRenderer.invoke('connections:delete', slug); |
| }, |
| test(slug: string, opts?: { model?: string }): Promise<ConnectionTestResult> { |
| return ipcRenderer.invoke('connections:test', slug, opts); |
| }, |
| fetchModels(slug: string): Promise<ModelDiscoveryResult> { |
| return ipcRenderer.invoke('connections:fetchModels', slug); |
| }, |
| hasSecret(slug: string): Promise<boolean> { |
| return ipcRenderer.invoke('connections:hasSecret', slug); |
| }, |
| getRequestHeaders(slug: string): Promise<import('@maka/core').SavedRequestHeaders> { |
| return ipcRenderer.invoke('connections:getRequestHeaders', slug); |
| }, |
| setRequestHeaders( |
| slug: string, |
| headers: readonly import('@maka/core').RequestHeaderUpdate[], |
| ): Promise<import('@maka/core').SavedRequestHeaders> { |
| return ipcRenderer.invoke('connections:setRequestHeaders', slug, headers); |
| }, |
| subscribeEvents(handler: (event: ConnectionEvent) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: ConnectionEvent) => handler(payload); |
| ipcRenderer.on('connections:event', listener); |
| return () => ipcRenderer.off('connections:event', listener); |
| }, |
| }, |
| mcp: { |
| getConfig(): Promise<McpConfigFile> { |
| return ipcRenderer.invoke('mcp:getConfig'); |
| }, |
| listStatuses(): Promise<McpServerStatus[]> { |
| return ipcRenderer.invoke('mcp:listStatuses'); |
| }, |
| setConfig(config: McpConfigFile): Promise<McpConfigFile> { |
| return ipcRenderer.invoke('mcp:setConfig', config); |
| }, |
| upsert(serverId: string, config: McpServerConfig): Promise<McpConfigFile> { |
| return ipcRenderer.invoke('mcp:upsert', serverId, config); |
| }, |
| install(serverId: string, config: McpServerConfig): Promise<McpConfigFile> { |
| return ipcRenderer.invoke('mcp:install', serverId, config); |
| }, |
| remove(serverId: string): Promise<McpConfigFile> { |
| return ipcRenderer.invoke('mcp:remove', serverId); |
| }, |
| cancelInstall(serverId: string): Promise<McpConfigFile> { |
| return ipcRenderer.invoke('mcp:cancelInstall', serverId); |
| }, |
| test(serverId: string): Promise<McpTestResult> { |
| return ipcRenderer.invoke('mcp:test', serverId); |
| }, |
| reconnect(serverId: string): Promise<McpServerStatus> { |
| return ipcRenderer.invoke('mcp:reconnect', serverId); |
| }, |
| subscribeChanges(handler: (statuses: McpServerStatus[]) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: McpServerStatus[]) => handler(payload); |
| ipcRenderer.on('mcp:changed', listener); |
| return () => ipcRenderer.off('mcp:changed', listener); |
| }, |
| }, |
| // PR110b: onboarding snapshot + milestone IPCs. Renderer polls |
| // `getSnapshot()` on app load and re-polls when |
| // `sessions:changed` / `connections:changed` / settings change |
| // events fire. There is no push event for OnboardingState — it is |
| // a derived projection and refresh latency is acceptable. |
| onboarding: { |
| getSnapshot(): Promise<OnboardingSnapshot> { |
| return ipcRenderer.invoke('onboarding:getSnapshot'); |
| }, |
| setMilestone( |
| id: OnboardingMilestoneId, |
| status: 'completed' | 'skipped', |
| ): Promise<OnboardingSnapshot> { |
| return ipcRenderer.invoke('onboarding:setMilestone', id, status); |
| }, |
| clearMilestone(id: OnboardingMilestoneId): Promise<OnboardingSnapshot> { |
| return ipcRenderer.invoke('onboarding:clearMilestone', id); |
| }, |
| }, |
| taskReadiness: { |
| getSnapshot(input?: DesktopTaskSubmissionReadinessRequest) { |
| return ipcRenderer.invoke('taskReadiness:getSnapshot', input); |
| }, |
| }, |
| permissions: { |
| getSnapshot(): Promise<PermissionSnapshot> { |
| return ipcRenderer.invoke('permissions:getSnapshot'); |
| }, |
| openSystemSettings(permId: string): Promise<PermissionActionResult> { |
| return ipcRenderer.invoke('permissions:openSystemSettings', permId); |
| }, |
| requestAccess(permId: string): Promise<PermissionActionResult> { |
| return ipcRenderer.invoke('permissions:requestAccess', permId); |
| }, |
| startDragOnboarding(permId: string): Promise<PermissionOverlayStartResult> { |
| return ipcRenderer.invoke('permissions:startDragOnboarding', permId); |
| }, |
| }, |
| capabilities: { |
| getSnapshot(): Promise<CapabilitySnapshotCollection> { |
| return ipcRenderer.invoke('capabilities:getSnapshot'); |
| }, |
| }, |
| health: { |
| getSnapshot(): Promise<HealthSnapshot> { |
| return ipcRenderer.invoke('health:getSnapshot'); |
| }, |
| }, |
| memory: { |
| getState(): Promise<LocalMemoryState> { |
| return ipcRenderer.invoke('memory:getState'); |
| }, |
| listProposals(): Promise<ReadonlyArray<LocalMemoryEntryPreview>> { |
| return ipcRenderer.invoke('memory:listProposals'); |
| }, |
| propose(input: { title: string; content: string; scope?: 'workspace' | 'session'; sessionId?: string }): Promise<LocalMemoryMutationResult> { |
| return ipcRenderer.invoke('memory:propose', input); |
| }, |
| remember(input: { title: string; content: string; scope?: 'workspace' | 'session'; sessionId?: string }): Promise<LocalMemoryMutationResult> { |
| return ipcRenderer.invoke('memory:remember', input); |
| }, |
| approveProposal(proposalId: string): Promise<LocalMemoryMutationResult> { |
| return ipcRenderer.invoke('memory:approveProposal', proposalId); |
| }, |
| rejectProposal(proposalId: string): Promise<LocalMemoryMutationResult> { |
| return ipcRenderer.invoke('memory:rejectProposal', proposalId); |
| }, |
| archiveEntry(entryId: string, reason?: string): Promise<LocalMemoryMutationResult> { |
| return ipcRenderer.invoke('memory:archiveEntry', entryId, reason); |
| }, |
| restoreEntry(entryId: string): Promise<LocalMemoryMutationResult> { |
| return ipcRenderer.invoke('memory:restoreEntry', entryId); |
| }, |
| save(content: string): Promise<LocalMemoryState> { |
| return ipcRenderer.invoke('memory:save', content); |
| }, |
| reset(): Promise<LocalMemoryState> { |
| return ipcRenderer.invoke('memory:reset'); |
| }, |
| restoreLatestBackup(): Promise<{ ok: true; state: LocalMemoryState } | { ok: false; state: LocalMemoryState; message: string }> { |
| return ipcRenderer.invoke('memory:restoreLatestBackup'); |
| }, |
| restoreBackup(kind: 'save' | 'reset' | 'restore'): Promise<{ ok: true; state: LocalMemoryState } | { ok: false; state: LocalMemoryState; message: string }> { |
| return ipcRenderer.invoke('memory:restoreBackup', kind); |
| }, |
| setEnabled(enabled: boolean): Promise<LocalMemoryState> { |
| return ipcRenderer.invoke('memory:setEnabled', enabled); |
| }, |
| setAgentReadEnabled(enabled: boolean): Promise<LocalMemoryState> { |
| return ipcRenderer.invoke('memory:setAgentReadEnabled', enabled); |
| }, |
| openFile(): Promise<{ ok: true } | { ok: false; message: string }> { |
| return ipcRenderer.invoke('memory:openFile'); |
| }, |
| openLatestBackup(): Promise<{ ok: true } | { ok: false; message: string }> { |
| return ipcRenderer.invoke('memory:openLatestBackup'); |
| }, |
| openBackup(kind: 'save' | 'reset' | 'restore'): Promise<{ ok: true } | { ok: false; message: string }> { |
| return ipcRenderer.invoke('memory:openBackup', kind); |
| }, |
| }, |
| attachments: { |
| pickFiles(): Promise< |
| | { ok: true; files: { approvalId: string; name: string; mimeType?: string; size: number }[] } |
| | { ok: false; reason: 'cancelled' } |
| > { |
| return ipcRenderer.invoke('attachments:pickFiles'); |
| }, |
| // Staged-attachment thumbnail for the composer drawer. Peeks the approval |
| // (never consumes it) so the token stays redeemable for the actual send. |
| previewApproval(approvalId: string): Promise< |
| | { ok: true; base64: string; mimeType: string } |
| | { ok: false; reason: string } |
| > { |
| return ipcRenderer.invoke('attachments:previewApproval', approvalId); |
| }, |
| readBytes(sessionId: string, relativePath: string): Promise< |
| | { ok: true; base64: string; mimeType: string } |
| | { ok: false; reason: string } |
| > { |
| return ipcRenderer.invoke('attachments:readBytes', sessionId, relativePath); |
| }, |
| }, |
| search: { |
| // PR-SEARCH-2: local thread search. Renderer sends a `SearchRequest` |
| // (source must be 'thread'); main responds with `SearchResult[]` or |
| // an error envelope. The query body never leaves the device — the |
| // helper is local-only and the IPC handler never emits the query |
| // into telemetry. |
| thread(request: SearchRequest): Promise<SearchResult[] | { ok: false; reason: SearchErrorReason; message: string }> { |
| return ipcRenderer.invoke('search:thread', request); |
| }, |
| }, |
| // PR-OAUTH-SUBSCRIPTION-0: Claude subscription OAuth bridge. |
| // NEVER returns raw OAuth credentials; renderer only sees account |
| // state + quota + action results (xuan G-X3 + the |
| // claude-subscription-ipc-boundary contract test enforces this). |
| // |
| // kenji `1da909d5`/`45b31e16` hardening: `openAuthUrl` takes |
| // ONLY an `authRequestId`; the URL is held by main from the |
| // earlier `getAuthUrl` call. Renderer can never hand |
| // `shell.openExternal` an arbitrary URL. |
| // |
| // Whole feature is gated behind `MAKA_CLAUDE_SUBSCRIPTION_EXPERIMENTAL=1` |
| // until product/legal sign-off. `isExperimentalEnabled()` lets the |
| // Settings UI hide the card; even without that hide, all auth-flow |
| // handlers re-check the flag main-side (fail-closed via the |
| // `experimental_disabled` reason). |
| claudeSubscription: { |
| isExperimentalEnabled(): Promise<boolean> { |
| return ipcRenderer.invoke('claude-subscription:is-experimental-enabled'); |
| }, |
| getAuthUrl(): Promise<AuthorizationUrlPayload | SubscriptionActionResult> { |
| return ipcRenderer.invoke('claude-subscription:get-auth-url'); |
| }, |
| openAuthUrl(authRequestId: string): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('claude-subscription:open-auth-url', authRequestId); |
| }, |
| completeAuthorization(authRequestId: string, pasted: string): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('claude-subscription:complete-authorization', authRequestId, pasted); |
| }, |
| cancelAuthorization(authRequestId?: string): Promise<{ ok: true }> { |
| return ipcRenderer.invoke('claude-subscription:cancel-authorization', authRequestId); |
| }, |
| getAccountState(): Promise<SubscriptionAccountState> { |
| return ipcRenderer.invoke('claude-subscription:get-account-state'); |
| }, |
| refreshQuota(): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('claude-subscription:refresh-quota'); |
| }, |
| refreshTokens(): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('claude-subscription:refresh-tokens'); |
| }, |
| logout(): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('claude-subscription:logout'); |
| }, |
| }, |
| // PR-MODEL-OAUTH-ALL-0: Codex / Antigravity subscription |
| // bridges. Same shape as `claudeSubscription` (no token-shaped |
| // fields, opaque authRequestId, action-result envelopes). Each |
| // service's state snapshot is provider-specific because the |
| // upstream auth claims differ (Codex carries JWT account_id / |
| // plan; Antigravity is preview-only). |
| openAiCodex: { |
| isExperimentalEnabled(): Promise<boolean> { |
| return ipcRenderer.invoke('openai-codex:is-experimental-enabled'); |
| }, |
| getAuthUrl(): Promise<AuthorizationUrlPayload | SubscriptionActionResult> { |
| return ipcRenderer.invoke('openai-codex:get-auth-url'); |
| }, |
| openAuthUrl(authRequestId: string): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('openai-codex:open-auth-url', authRequestId); |
| }, |
| completeAuthorization(authRequestId: string): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('openai-codex:complete-authorization', authRequestId); |
| }, |
| cancelAuthorization(authRequestId?: string): Promise<{ ok: true }> { |
| return ipcRenderer.invoke('openai-codex:cancel-authorization', authRequestId); |
| }, |
| getAccountState(): Promise<{ |
| provider: 'openai-codex'; |
| runtimeState: 'not_logged_in' | 'authorizing' | 'authenticated' | 'refreshing' | 'refresh_failed'; |
| accountId?: string; |
| email?: string; |
| plan?: string; |
| picture?: string; |
| errorMessage?: string; |
| }> { |
| return ipcRenderer.invoke('openai-codex:get-account-state'); |
| }, |
| refreshTokens(): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('openai-codex:refresh-tokens'); |
| }, |
| logout(): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('openai-codex:logout'); |
| }, |
| }, |
| xaiOAuth: { |
| getAuthUrl(): Promise<AuthorizationUrlPayload | SubscriptionActionResult> { |
| return ipcRenderer.invoke('xai-oauth:get-auth-url'); |
| }, |
| openAuthUrl(authRequestId: string): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('xai-oauth:open-auth-url', authRequestId); |
| }, |
| completeAuthorization(authRequestId: string): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('xai-oauth:complete-authorization', authRequestId); |
| }, |
| cancelAuthorization(authRequestId?: string): Promise<{ ok: true }> { |
| return ipcRenderer.invoke('xai-oauth:cancel-authorization', authRequestId); |
| }, |
| getAccountState(): Promise<{ |
| provider: 'xai-oauth'; |
| runtimeState: |
| | 'not_logged_in' |
| | 'authorizing' |
| | 'authenticated' |
| | 'refreshing' |
| | 'refresh_failed' |
| | 'storage_failed'; |
| errorMessage?: string; |
| }> { |
| return ipcRenderer.invoke('xai-oauth:get-account-state'); |
| }, |
| refreshTokens(): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('xai-oauth:refresh-tokens'); |
| }, |
| logout(): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('xai-oauth:logout'); |
| }, |
| }, |
| githubCopilotSubscription: { |
| connectExistingLogin(): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('github-copilot:connect-existing-login'); |
| }, |
| getAccountState(): Promise<{ |
| provider: 'github-copilot'; |
| runtimeState: 'not_logged_in' | 'authenticated' | 'refreshing' | 'refresh_failed' | 'storage_failed'; |
| errorMessage?: string; |
| }> { |
| return ipcRenderer.invoke('github-copilot:get-account-state'); |
| }, |
| refreshTokens(): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('github-copilot:refresh-tokens'); |
| }, |
| logout(): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('github-copilot:logout'); |
| }, |
| }, |
| antigravitySubscription: { |
| isExperimentalEnabled(): Promise<boolean> { |
| return ipcRenderer.invoke('antigravity-subscription:is-experimental-enabled'); |
| }, |
| getAuthUrl(): Promise<AuthorizationUrlPayload | SubscriptionActionResult> { |
| return ipcRenderer.invoke('antigravity-subscription:get-auth-url'); |
| }, |
| openAuthUrl(authRequestId: string): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('antigravity-subscription:open-auth-url', authRequestId); |
| }, |
| completeAuthorization(authRequestId: string): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('antigravity-subscription:complete-authorization', authRequestId); |
| }, |
| cancelAuthorization(authRequestId?: string): Promise<{ ok: true }> { |
| return ipcRenderer.invoke('antigravity-subscription:cancel-authorization', authRequestId); |
| }, |
| getAccountState(): Promise<{ |
| provider: 'antigravity-subscription'; |
| status: 'preview'; |
| runtimeState: 'not_logged_in' | 'authorizing' | 'authenticated' | 'refreshing' | 'refresh_failed'; |
| errorMessage?: string; |
| }> { |
| return ipcRenderer.invoke('antigravity-subscription:get-account-state'); |
| }, |
| refreshTokens(): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('antigravity-subscription:refresh-tokens'); |
| }, |
| logout(): Promise<SubscriptionActionResult> { |
| return ipcRenderer.invoke('antigravity-subscription:logout'); |
| }, |
| }, |
| plans: { |
| list(): Promise<PlanReminder[]> { |
| return ipcRenderer.invoke('plans:list'); |
| }, |
| create(input: { title: string; note?: string; runAt: number | string; recurrence?: PlanReminderRecurrence; cronExpression?: string; delivery?: PlanReminderDeliveryTarget }): Promise<PlanReminder> { |
| return ipcRenderer.invoke('plans:create', input); |
| }, |
| update(id: string, patch: { title?: string; note?: string; runAt?: number | string; recurrence?: PlanReminderRecurrence; cronExpression?: string; delivery?: PlanReminderDeliveryTarget; enabled?: boolean }): Promise<PlanReminder> { |
| return ipcRenderer.invoke('plans:update', id, patch); |
| }, |
| setEnabled(id: string, enabled: boolean): Promise<PlanReminder> { |
| return ipcRenderer.invoke('plans:setEnabled', id, enabled); |
| }, |
| triggerNow(id: string): Promise<PlanReminder> { |
| return ipcRenderer.invoke('plans:triggerNow', id); |
| }, |
| snooze(id: string): Promise<PlanReminder> { |
| return ipcRenderer.invoke('plans:snooze', id); |
| }, |
| clearRunHistory(id: string): Promise<PlanReminder> { |
| return ipcRenderer.invoke('plans:clearRunHistory', id); |
| }, |
| delete(id: string): Promise<void> { |
| return ipcRenderer.invoke('plans:delete', id); |
| }, |
| subscribeChanges(handler: (event: { type: 'plans_changed'; reason: string; reminderId?: string; ts: number }) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: { type: 'plans_changed'; reason: string; reminderId?: string; ts: number }) => handler(payload); |
| ipcRenderer.on('plans:changed', listener); |
| return () => ipcRenderer.off('plans:changed', listener); |
| }, |
| subscribeDue(handler: (reminder: PlanReminder) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: PlanReminder) => handler(payload); |
| ipcRenderer.on('plans:due', listener); |
| return () => ipcRenderer.off('plans:due', listener); |
| }, |
| }, |
| settings: { |
| get(): Promise<AppSettings> { |
| return ipcRenderer.invoke('settings:get'); |
| }, |
| update(patch: UpdateAppSettingsInput): Promise<UpdateAppSettingsResult> { |
| return ipcRenderer.invoke('settings:update', patch); |
| }, |
| subscribeExternalChanged(handler: () => void): () => void { |
| const listener = () => handler(); |
| ipcRenderer.on('settings:externalChanged', listener); |
| return () => ipcRenderer.off('settings:externalChanged', listener); |
| }, |
| testNetworkProxy(input?: TestProxyInput): Promise<SettingsTestResult> { |
| return ipcRenderer.invoke('settings:testNetworkProxy', input); |
| }, |
| testBotChannel(provider: BotProvider): Promise<SettingsTestResult> { |
| return ipcRenderer.invoke('settings:testBotChannel', provider); |
| }, |
| usageStats(range?: UsageRange): Promise<UsageStats> { |
| return ipcRenderer.invoke('settings:usageStats', range); |
| }, |
| bots: { |
| listStatuses(): Promise<Record<BotProvider, BotStatus>> { |
| return ipcRenderer.invoke('settings:bots:listStatuses'); |
| }, |
| restart(provider: BotProvider): Promise<BotStatus> { |
| return ipcRenderer.invoke('settings:bots:restart', provider); |
| }, |
| wechatQrCode(): Promise<WechatBridgeQrCodeResult> { |
| return ipcRenderer.invoke('settings:bots:wechatQrCode'); |
| }, |
| subscribeStatusChanges(handler: (status: BotStatus) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: BotStatus) => handler(payload); |
| ipcRenderer.on('settings:bots:statusChanged', listener); |
| return () => ipcRenderer.off('settings:bots:statusChanged', listener); |
| }, |
| onboarding: { |
| start(input: BotOnboardingStartInput): Promise<Result<BotOnboardingSnapshot>> { |
| return ipcRenderer.invoke('settings:bots:onboarding:start', input); |
| }, |
| poll(sessionId: string): Promise<Result<BotOnboardingSnapshot>> { |
| return ipcRenderer.invoke('settings:bots:onboarding:poll', sessionId); |
| }, |
| cancel(sessionId: string): Promise<Result<BotOnboardingSnapshot>> { |
| return ipcRenderer.invoke('settings:bots:onboarding:cancel', sessionId); |
| }, |
| openInBrowser(sessionId: string): Promise<Result<void>> { |
| return ipcRenderer.invoke('settings:bots:onboarding:open', sessionId); |
| }, |
| }, |
| }, |
| }, |
| notifications: { |
| // Fire-and-forget signal that an agent turn reached a terminal |
| // state. `title` is the session name, `body` the start of the reply |
| // (or the error message); main sanitizes both and falls back to |
| // generic copy when blank. Main gates on the product toggle + window |
| // focus before raising a native OS notification. |
| runEnded(payload: { |
| kind: 'completed' | 'errored'; |
| title?: string; |
| body?: string; |
| }): Promise<void> { |
| return ipcRenderer.invoke('notifications:runEnded', payload); |
| }, |
| }, |
| inspector: { |
| /** Read-only per-session causal trace (#1625). Never writes runtime state. */ |
| trace(sessionId: string): Promise<Result<SessionTrace>> { |
| return ipcRenderer.invoke('inspector:trace', sessionId); |
| }, |
| }, |
| dailyReview: { |
| day(offsetDays: number, daySpan?: number): Promise<Result<DailyReviewSummary>> { |
| return ipcRenderer.invoke('daily-review:day', { offsetDays, daySpan }); |
| }, |
| getConfig(): Promise<DailyReviewConfig> { |
| return ipcRenderer.invoke('daily-review:getConfig'); |
| }, |
| setConfig(patch: Partial<DailyReviewConfig>): Promise<DailyReviewConfig> { |
| return ipcRenderer.invoke('daily-review:setConfig', patch); |
| }, |
| runOnce(input: { range: DailyReviewRange; offsetDays?: number; modelKey?: string }): Promise<{ archiveId: string }> { |
| return ipcRenderer.invoke('daily-review:runOnce', input); |
| }, |
| listArchives(): Promise<DailyReviewArchiveSummary[]> { |
| return ipcRenderer.invoke('daily-review:list'); |
| }, |
| getArchive(archiveId: string): Promise<DailyReviewArchive | null> { |
| return ipcRenderer.invoke('daily-review:get', archiveId); |
| }, |
| /** |
| * PR-DAILY-REVIEW-EXPORT-FILE-0: render the markdown in the renderer |
| * (where the human-readable title context lives) and ship the bytes |
| * to main for the save dialog + write. Main never sees the raw |
| * telemetry; only the formatted output. |
| */ |
| saveMarkdownToFile(input: { |
| markdown: string; |
| defaultName: string; |
| }): Promise< |
| { ok: true; path: string } | { ok: false; reason: 'canceled' | 'write_failed' | 'invalid_input' } |
| > { |
| return ipcRenderer.invoke('daily-review:saveMarkdownToFile', input); |
| }, |
| }, |
| webSearch: { |
| query(input: { |
| query: string; |
| limit?: number; |
| provider?: WebSearchProvider; |
| apiKey?: string; |
| }): Promise<WebSearchResponse> { |
| return ipcRenderer.invoke('web-search:query', input); |
| }, |
| test(input: { provider?: WebSearchProvider; apiKey?: string }): Promise<WebSearchResponse> { |
| return ipcRenderer.invoke('web-search:test', input); |
| }, |
| }, |
| appWindow: { |
| setTitlebarControlsVisible(visible: boolean): Promise<void> { |
| return ipcRenderer.invoke('window:setTitlebarControlsVisible', visible); |
| }, |
| setThemeSource(themePref: ThemePreference): Promise<void> { |
| return ipcRenderer.invoke('window:setThemeSource', themePref); |
| }, |
| // PR-WINDOW-TITLEBAR-0: re-sync the native Windows titleBarOverlay |
| // color/symbolColor to the resolved app surface. No-op on non-Windows. |
| setTitleBarOverlayTheme(theme: { isDark: boolean; backgroundColor: string }): Promise<void> { |
| return ipcRenderer.invoke('window:setTitleBarOverlayTheme', theme); |
| }, |
| // PR-SHOW-AFTER-FIRST-COMMIT: tell main the renderer finished its first |
| // React commit so the hidden window can be revealed. Fire-and-forget. |
| notifyRendererReady(): Promise<void> { |
| return ipcRenderer.invoke('window:notifyRendererReady'); |
| }, |
| // PR-2088: main-to-renderer route for native-menu commands (New Task / |
| // Settings / Keyboard Shortcuts). The `ipcRenderer.on`/`off` idiom keeps |
| // an HMR or shell remount from stacking duplicate listeners. |
| subscribeCommand(handler: (command: WindowCommand) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, command: WindowCommand) => handler(command); |
| ipcRenderer.on('window:command', listener); |
| return () => ipcRenderer.off('window:command', listener); |
| }, |
| }, |
| config: { |
| export(input: { categories: ConfigCategory[] }): Promise< |
| | { ok: false; reason: 'no_categories' | 'canceled' } |
| | { ok: true; path: string; includedData: ConfigCategory[] } |
| > { |
| return ipcRenderer.invoke('config:export', input); |
| }, |
| import(input: { strategy: 'skip' | 'overwrite' }): Promise< |
| | { ok: false; reason: 'canceled' | 'not_json' | 'malformed' | 'unsupported_version'; message?: string } |
| | { |
| ok: true; |
| includedData: ConfigCategory[]; |
| result: { |
| connections?: { created: number; overwritten: number; skipped: number }; |
| settings?: { applied: boolean }; |
| credentials?: { applied: number; skipped: number }; |
| memory?: { applied: boolean }; |
| }; |
| } |
| > { |
| return ipcRenderer.invoke('config:import', input); |
| }, |
| }, |
| app: { |
| info(): Promise<{ |
| appVersion: string; |
| electronVersion: string; |
| nodeVersion: string; |
| chromeVersion: string; |
| platform: string; |
| arch: string; |
| osRelease: string; |
| workspacePath: string; |
| homePath: string; |
| operationalStateDatabasePath: string; |
| projectId?: string | null; |
| projectPath: string; |
| projectGit: { isGitRepo: boolean; branch?: string }; |
| buildMode: 'dev' | 'packaged'; |
| buildCommit: string | null; |
| }> { |
| return ipcRenderer.invoke('app:info'); |
| }, |
| subscribeUpdateStatus(handler: (status: AppUpdateStatus) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, status: AppUpdateStatus) => handler(status); |
| ipcRenderer.on('app:updateStatusChanged', listener); |
| return () => ipcRenderer.off('app:updateStatusChanged', listener); |
| }, |
| updateStatus(): Promise<AppUpdateStatus> { |
| return ipcRenderer.invoke('app:updateStatus'); |
| }, |
| retryUpdateDownload(): Promise<AppUpdateStatus> { |
| return ipcRenderer.invoke('app:retryUpdateDownload'); |
| }, |
| installUpdate(input: AppUpdateInstallRequest): Promise<AppUpdateInstallResult> { |
| return ipcRenderer.invoke('app:installUpdate', input); |
| }, |
| sessionProjectInfo(sessionId: string): Promise<{ |
| projectPath: string; |
| projectGit: { isGitRepo: boolean; branch?: string }; |
| }> { |
| return ipcRenderer.invoke('app:sessionProjectInfo', sessionId); |
| }, |
| openPath( |
| key: 'workspace' | 'skills' | 'memory' | 'project', |
| sessionId?: string, |
| ): Promise< |
| | { ok: true; opened: string } |
| | { |
| ok: false; |
| reason: 'unknown-key' | 'not-allowed' | 'missing' | 'not-a-directory' | 'open-failed'; |
| } |
| > { |
| return ipcRenderer.invoke('app:openPath', key, sessionId); |
| }, |
| resolveProjectGitInfo(projectPath: string): Promise< |
| | { ok: true; projectPath: string; projectGit: { isGitRepo: boolean; branch?: string } } |
| | { ok: false; reason: 'invalid-path' | 'not-found' } |
| > { |
| return ipcRenderer.invoke('app:resolveProjectGitInfo', projectPath); |
| }, |
| openArtifactPath( |
| sessionId: string, |
| artifactId: string, |
| ): Promise< |
| | { ok: true; opened: string } |
| | { |
| ok: false; |
| reason: 'unknown-key' | 'not-allowed' | 'missing' | 'not-a-directory' | 'open-failed'; |
| } |
| > { |
| return ipcRenderer.invoke('app:openArtifactPath', sessionId, artifactId); |
| }, |
| saveArtifactAs(sessionId: string, artifactId: string): Promise<ArtifactSaveResult> { |
| return ipcRenderer.invoke('app:saveArtifactAs', sessionId, artifactId); |
| }, |
| }, |
| diagnostics: { |
| copyErrorReport(input: DesktopErrorDiagnosticInput): Promise<DesktopDiagnosticCopyResult> { |
| return ipcRenderer.invoke('diagnostics:copyErrorReport', input); |
| }, |
| }, |
| workspace: { |
| /** Composer `@` mention popup: list workspace files matching `query`. */ |
| searchFiles( |
| query: string, |
| options?: { sessionId?: string; limit?: number }, |
| ): Promise< |
| | { ok: true; files: Array<{ relativePath: string }> } |
| | { ok: false; reason: 'no_project' | 'search_failed' } |
| > { |
| return ipcRenderer.invoke('workspace:searchFiles', { query, ...options }); |
| }, |
| }, |
| e2eFixture: { |
| getState(): Promise<E2eFixtureState | null> { |
| return ipcRenderer.invoke('e2eFixture:getState'); |
| }, |
| }, |
| artifacts: { |
| list(sessionId: string, opts?: { includeDeleted?: boolean }): Promise<ArtifactDescriptor[]> { |
| return ipcRenderer.invoke('artifacts:list', sessionId, opts); |
| }, |
| get(sessionId: string, artifactId: string): Promise<ArtifactDescriptor | null> { |
| return ipcRenderer.invoke('artifacts:get', sessionId, artifactId); |
| }, |
| readText(sessionId: string, artifactId: string): Promise<ArtifactTextReadResult> { |
| return ipcRenderer.invoke('artifacts:readText', sessionId, artifactId); |
| }, |
| readBinary(sessionId: string, artifactId: string): Promise<ArtifactBinaryReadResult> { |
| return ipcRenderer.invoke('artifacts:readBinary', sessionId, artifactId); |
| }, |
| delete(sessionId: string, artifactId: string): Promise<void> { |
| return ipcRenderer.invoke('artifacts:delete', sessionId, artifactId); |
| }, |
| subscribeChanges(handler: (event: ArtifactChangedEvent) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: ArtifactChangedEvent) => handler(payload); |
| ipcRenderer.on('artifacts:changed', listener); |
| return () => ipcRenderer.off('artifacts:changed', listener); |
| }, |
| }, |
| skills: { |
| list(): Promise<SkillEntry[]> { |
| return ipcRenderer.invoke('skills:list'); |
| }, |
| listInvocable( |
| sessionId?: string, |
| newSessionContext?: { |
| llmConnectionSlug?: string; |
| model?: string; |
| collaborationMode?: 'agent' | 'plan'; |
| }, |
| ): Promise<import('@maka/runtime').InvocableSkillEntry[]> { |
| return ipcRenderer.invoke('skills:listInvocable', sessionId, newSessionContext); |
| }, |
| catalog: { |
| list(): Promise<BundledSkillCatalogEntry[]> { |
| return ipcRenderer.invoke('skills:catalog:list'); |
| }, |
| install(id: string): Promise< |
| | { ok: true; skill: SkillEntry } |
| | { ok: false; reason: 'not_found' | 'already_exists' | 'blocked_path' | 'write_failed' } |
| > { |
| return ipcRenderer.invoke('skills:catalog:install', id); |
| }, |
| }, |
| sources: { |
| list(): Promise<ManagedSkillSourceEntry[]> { |
| return ipcRenderer.invoke('skills:sources:list'); |
| }, |
| importLocalFile(): Promise< |
| | { ok: true; source: ManagedSkillSourceEntry } |
| | { ok: false; reason: 'cancelled' | 'invalid_skill' | 'already_exists' | 'blocked_path' | 'write_failed' } |
| > { |
| return ipcRenderer.invoke('skills:sources:importLocalFile'); |
| }, |
| }, |
| installManaged(sourceId: string): Promise< |
| | { ok: true; skill: SkillEntry } |
| | { ok: false; reason: 'not_found' | 'already_exists' | 'blocked_path' | 'write_failed' } |
| > { |
| return ipcRenderer.invoke('skills:installManaged', sourceId); |
| }, |
| details(skillId: string): Promise< |
| | { ok: true; details: SkillGovernanceDetails } |
| | { ok: false; reason: 'not_found' | 'invalid_id' } |
| > { |
| return ipcRenderer.invoke('skills:details', skillId); |
| }, |
| previewUpdate(skillId: string): Promise< |
| | { ok: true; preview: ManagedSkillUpdatePreview } |
| | { ok: false; reason: 'not_managed' | 'source_missing' | 'metadata_error' | 'blocked_path' | 'read_failed' } |
| > { |
| return ipcRenderer.invoke('skills:previewUpdate', skillId); |
| }, |
| updateManaged(skillId: string, options?: { force?: boolean; expectedCurrentSha256?: string; expectedSourceSha256?: string }): Promise< |
| | { ok: true; skill: SkillEntry } |
| | { ok: false; reason: 'not_managed' | 'source_missing' | 'local_modified' | 'metadata_error' | 'blocked_path' | 'write_failed' } |
| > { |
| return ipcRenderer.invoke('skills:updateManaged', skillId, options); |
| }, |
| setEnabled(skillId: string, enabled: boolean): Promise< |
| | { ok: true; skill: SkillEntry } |
| | { ok: false; reason: 'not_found' | 'blocked_path' | 'state_error' | 'write_failed' } |
| > { |
| return ipcRenderer.invoke('skills:setEnabled', skillId, enabled); |
| }, |
| setPinned(skillRef: string, pinned: boolean): Promise< |
| | { ok: true; skill: SkillEntry } |
| | { ok: false; reason: 'not_found' | 'blocked_path' | 'state_error' | 'write_failed' } |
| > { |
| return ipcRenderer.invoke('skills:setPinned', skillRef, pinned); |
| }, |
| createStarter(): Promise< |
| | { ok: true; created: boolean; skill: SkillEntry; filePath: string } |
| | { ok: false; reason: 'blocked_path' | 'already_exists' | 'write_failed' } |
| > { |
| return ipcRenderer.invoke('skills:createStarter'); |
| }, |
| delete(idOrRef: string): Promise< |
| | { ok: true } |
| | { ok: false; reason: 'not_found' | 'blocked_path' | 'blocked_scope' | 'delete_failed' } |
| > { |
| return ipcRenderer.invoke('skills:delete', idOrRef); |
| }, |
| open(id: string, target: 'file' | 'directory' = 'file'): Promise< |
| | { ok: true; target: 'file' | 'directory' } |
| | { ok: false; reason: 'invalid_id' | 'missing' | 'blocked_path' | 'not_file' | 'not_directory' | 'open_failed' } |
| > { |
| return ipcRenderer.invoke('skills:open', id, target); |
| }, |
| }, |
| // Embedded browser (P3). The native WebContentsView floats above the DOM; the |
| // renderer panel only mirrors its strip's rect and drives navigation. No |
| // automation endpoint/secret is ever exposed here — that stays main-internal. |
| browser: { |
| /** Tell main which conversation this window shows, so it can validate targets. */ |
| setActiveSession(sessionId: string | null): void { |
| ipcRenderer.send('browser:active-session', sessionId); |
| }, |
| /** Mirror the panel strip's on-screen rect (null hides the native view). */ |
| setViewport(input: { sessionId: string; rect: BrowserViewRect | null }): void { |
| ipcRenderer.send('browser:setViewport', input); |
| }, |
| navigate(sessionId: string, url: string): Promise<void> { |
| return ipcRenderer.invoke('browser:navigate', sessionId, url); |
| }, |
| back(sessionId: string): Promise<void> { |
| return ipcRenderer.invoke('browser:back', sessionId); |
| }, |
| forward(sessionId: string): Promise<void> { |
| return ipcRenderer.invoke('browser:forward', sessionId); |
| }, |
| reload(sessionId: string): Promise<void> { |
| return ipcRenderer.invoke('browser:reload', sessionId); |
| }, |
| stop(sessionId: string): Promise<void> { |
| return ipcRenderer.invoke('browser:stop', sessionId); |
| }, |
| close(sessionId: string): Promise<void> { |
| return ipcRenderer.invoke('browser:close-page', sessionId); |
| }, |
| getState(sessionId: string): Promise<BrowserState | null> { |
| return ipcRenderer.invoke('browser:get-state', sessionId); |
| }, |
| onState(handler: (payload: { sessionId: string; state: BrowserState }) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: { sessionId: string; state: BrowserState }) => |
| handler(payload); |
| ipcRenderer.on('browser:state', listener); |
| return () => ipcRenderer.off('browser:state', listener); |
| }, |
| onLive(handler: (payload: { sessionIds: string[] }) => void): () => void { |
| const listener = (_event: Electron.IpcRendererEvent, payload: { sessionIds: string[] }) => handler(payload); |
| ipcRenderer.on('browser:live', listener); |
| return () => ipcRenderer.off('browser:live', listener); |
| }, |
| }, |
| } satisfies MakaBridge; |
| |
| contextBridge.exposeInMainWorld('maka', makaBridge); |