mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
refactor(preload): split bridge API modules
(cherry picked from commit b77e31873b)
This commit is contained in:
@@ -6,6 +6,17 @@
|
||||
"../src/renderer/src/**/*.tsx",
|
||||
"../src/preload/api-types.ts",
|
||||
"../src/preload/api/**/*",
|
||||
"../src/preload/browser-client-page-renderer-requests.ts",
|
||||
"../src/preload/browser-find-subscriptions.ts",
|
||||
"../src/preload/close-active-tab-payload-admission.ts",
|
||||
"../src/preload/e2e-config.ts",
|
||||
"../src/preload/gitlab.ts",
|
||||
"../src/preload/preload-runtime-support.ts",
|
||||
"../src/preload/renderer-heap-statistics-reader.ts",
|
||||
"../src/preload/renderer-process-memory-reader.ts",
|
||||
"../src/preload/renderer-restart-wiring.ts",
|
||||
"../src/preload/runtime-environment-subscriptions.ts",
|
||||
"../src/preload/usage-provider-api.ts",
|
||||
"../src/shared/**/*",
|
||||
"../src/main/gitlab/mappers.ts",
|
||||
"../src/main/ipc/worktree-branch-name.ts",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { ComputerAwakeStatus } from '../../shared/computer-awake-mode'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const agentAwakeApi = {
|
||||
getStatus: (): Promise<ComputerAwakeStatus> => ipcRenderer.invoke('agentAwake:getStatus'),
|
||||
onChanged: (callback: (status: ComputerAwakeStatus) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, status: ComputerAwakeStatus): void =>
|
||||
callback(status)
|
||||
ipcRenderer.on('agentAwake:changed', listener)
|
||||
return () => ipcRenderer.removeListener('agentAwake:changed', listener)
|
||||
}
|
||||
} satisfies PreloadApi['agentAwake']
|
||||
@@ -0,0 +1,88 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
AgentStatusClearIpcPayload,
|
||||
AgentStatusIpcPayload,
|
||||
MigrationUnsupportedPtyEntry
|
||||
} from '../../shared/agent-status-types'
|
||||
import type { AgentInterruptInferenceRequest } from '../../shared/agent-interrupt-intent'
|
||||
import type { AgentQuestionAnsweredInferenceRequest } from '../../shared/agent-question-answered-intent'
|
||||
|
||||
export const agentStatusApi = {
|
||||
/** Listen for agent status updates forwarded from native hook receivers. */
|
||||
onSet: (callback: (data: AgentStatusIpcPayload) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: AgentStatusIpcPayload) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('agentStatus:set', listener)
|
||||
return () => ipcRenderer.removeListener('agentStatus:set', listener)
|
||||
},
|
||||
onClear: (callback: (data: AgentStatusClearIpcPayload) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: AgentStatusClearIpcPayload) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('agentStatus:clear', listener)
|
||||
return () => ipcRenderer.removeListener('agentStatus:clear', listener)
|
||||
},
|
||||
/** Pull cached hook statuses after renderer hydration, so startup replays aren't lost before tabs exist. */
|
||||
getSnapshot: (): Promise<AgentStatusIpcPayload[]> =>
|
||||
ipcRenderer.invoke('agentStatus:getSnapshot'),
|
||||
inferInterrupt: (request: AgentInterruptInferenceRequest): Promise<boolean> =>
|
||||
ipcRenderer.invoke('agentStatus:inferInterrupt', request),
|
||||
inferQuestionAnswered: (request: AgentQuestionAnsweredInferenceRequest): Promise<boolean> =>
|
||||
ipcRenderer.invoke('agentStatus:inferQuestionAnswered', request),
|
||||
onMigrationUnsupported: (
|
||||
callback: (entry: MigrationUnsupportedPtyEntry) => void
|
||||
): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, entry: MigrationUnsupportedPtyEntry) =>
|
||||
callback(entry)
|
||||
ipcRenderer.on('agentStatus:migrationUnsupported', listener)
|
||||
return () => ipcRenderer.removeListener('agentStatus:migrationUnsupported', listener)
|
||||
},
|
||||
onMigrationUnsupportedClear: (callback: (data: { ptyId: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { ptyId: string }) => callback(data)
|
||||
ipcRenderer.on('agentStatus:migrationUnsupportedClear', listener)
|
||||
return () => ipcRenderer.removeListener('agentStatus:migrationUnsupportedClear', listener)
|
||||
},
|
||||
onLegacyWorkerTerminalRecovery: (
|
||||
callback: (data: {
|
||||
paneKey: string
|
||||
resolution: 'adopted' | 'exited' | 'rolled_back'
|
||||
ptyId?: string
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
paneKey: string
|
||||
resolution: 'adopted' | 'exited' | 'rolled_back'
|
||||
ptyId?: string
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('agentStatus:legacyWorkerTerminalRecovery', listener)
|
||||
return () => ipcRenderer.removeListener('agentStatus:legacyWorkerTerminalRecovery', listener)
|
||||
},
|
||||
getMigrationUnsupportedSnapshot: (): Promise<MigrationUnsupportedPtyEntry[]> =>
|
||||
ipcRenderer.invoke('agentStatus:getMigrationUnsupportedSnapshot'),
|
||||
/** Drop the cached hook status for a paneKey on both sides (memory + on-disk) so a relaunch can't resurrect a dismissed row. */
|
||||
drop: (paneKey: string): void => {
|
||||
ipcRenderer.send('agentStatus:drop', paneKey)
|
||||
},
|
||||
reconcileEndedProcess: (paneKey: string): void => {
|
||||
ipcRenderer.send('agentStatus:reconcileEndedProcess', paneKey)
|
||||
},
|
||||
/** Drop all cached hook statuses under one terminal tab prefix; fired on explicit tab close even without a local row. */
|
||||
dropByTabPrefix: (tabId: string): void => {
|
||||
ipcRenderer.send('agentStatus:dropByTabPrefix', tabId)
|
||||
},
|
||||
retirePaneAuthority: (paneKey: string): void => {
|
||||
ipcRenderer.send('agentStatus:retirePaneAuthority', paneKey)
|
||||
},
|
||||
restorePaneAuthority: (paneKey: string): void => {
|
||||
ipcRenderer.send('agentStatus:restorePaneAuthority', paneKey)
|
||||
},
|
||||
transferPaneAuthority: (args: {
|
||||
fromPaneKey: string
|
||||
toPaneKey: string
|
||||
ptyId?: string
|
||||
}): void => {
|
||||
ipcRenderer.send('agentStatus:transferPaneAuthority', args)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const agentTrustApi = {
|
||||
markTrusted: (args: {
|
||||
preset: 'cursor' | 'copilot' | 'codex'
|
||||
workspacePath: string
|
||||
connectionId?: string
|
||||
}): Promise<void> => ipcRenderer.invoke('agentTrust:markTrusted', args)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
AiVaultDeleteSessionArgs,
|
||||
AiVaultDeleteSessionResult
|
||||
} from '../../shared/ai-vault-session-deletion'
|
||||
import type {
|
||||
AiVaultFirstUserPromptArgs,
|
||||
AiVaultListArgs,
|
||||
AiVaultSubagentListArgs
|
||||
} from '../../shared/ai-vault-types'
|
||||
import type { AiVaultSessionTitlesArgs } from '../../shared/ai-vault-session-title'
|
||||
import type { AiVaultPrepareSessionResumeArgs } from '../../shared/ai-vault-resume-preparation'
|
||||
|
||||
export const aiVaultApi = {
|
||||
listSessions: (args?: AiVaultListArgs): Promise<unknown> =>
|
||||
ipcRenderer.invoke('aiVault:listSessions', args),
|
||||
resolveSessionTitles: (args: AiVaultSessionTitlesArgs): Promise<unknown> =>
|
||||
ipcRenderer.invoke('aiVault:resolveSessionTitles', args),
|
||||
cancelListSessions: (args: { requestToken: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('aiVault:cancelListSessions', args),
|
||||
prepareSessionResume: (args: AiVaultPrepareSessionResumeArgs): Promise<unknown> =>
|
||||
ipcRenderer.invoke('aiVault:prepareSessionResume', args),
|
||||
listSubagentSessions: (args: AiVaultSubagentListArgs): Promise<unknown> =>
|
||||
ipcRenderer.invoke('aiVault:listSubagentSessions', args),
|
||||
getFirstUserPrompt: (args: AiVaultFirstUserPromptArgs): Promise<unknown> =>
|
||||
ipcRenderer.invoke('aiVault:getFirstUserPrompt', args),
|
||||
deleteSession: (args: AiVaultDeleteSessionArgs): Promise<AiVaultDeleteSessionResult> =>
|
||||
ipcRenderer.invoke('aiVault:deleteSession', args),
|
||||
onWindowFocused: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('aiVault:windowFocused', listener)
|
||||
return () => ipcRenderer.removeListener('aiVault:windowFocused', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { AppIdentity } from '../../shared/app-identity'
|
||||
import type { FloatingTerminalCwdRequest } from '../../shared/ui-chrome-types'
|
||||
import type { MacCapturedDigitRowChord } from '../../shared/macos-symbolic-hotkeys'
|
||||
import type { MarkdownDocument } from '../../shared/filesystem-entry-types'
|
||||
import type { WriteTerminalRenderDesyncEvidenceArgs } from '../../shared/terminal-render-desync-evidence'
|
||||
import {
|
||||
KEYBOARD_LAYOUT_CHANGED_CHANNEL,
|
||||
type KeyboardLayoutChangeEvent
|
||||
} from '../../shared/keyboard-layout-events'
|
||||
import { prepareAndInvokeAppRestart } from '../renderer-restart-wiring'
|
||||
import { awaitBeforeUnloadCheckpoint, startupDiagnosticsEnabled } from '../preload-runtime-support'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const appApi = {
|
||||
getIdentity: (): Promise<AppIdentity> => ipcRenderer.invoke('app:getIdentity'),
|
||||
getFeatureWallAssetBaseUrl: (): Promise<string> =>
|
||||
ipcRenderer.invoke('app:getFeatureWallAssetBaseUrl'),
|
||||
relaunch: (): Promise<void> =>
|
||||
prepareAndInvokeAppRestart(
|
||||
window,
|
||||
() => ipcRenderer.invoke('app:relaunch'),
|
||||
awaitBeforeUnloadCheckpoint
|
||||
),
|
||||
restart: (): Promise<void> =>
|
||||
prepareAndInvokeAppRestart(
|
||||
window,
|
||||
() => ipcRenderer.invoke('app:restart'),
|
||||
awaitBeforeUnloadCheckpoint
|
||||
),
|
||||
reload: (): Promise<void> =>
|
||||
prepareAndInvokeAppRestart(
|
||||
window,
|
||||
() => ipcRenderer.invoke('app:reload'),
|
||||
awaitBeforeUnloadCheckpoint
|
||||
),
|
||||
stageBeforeUnloadSync: (args: Parameters<PreloadApi['app']['stageBeforeUnloadSync']>[0]) => {
|
||||
const result = ipcRenderer.sendSync('app:stage-before-unload-sync', args) as { ok?: unknown }
|
||||
if (result?.ok !== true) {
|
||||
throw new Error('Failed to stage renderer state before unload.')
|
||||
}
|
||||
},
|
||||
awaitFirstWindowStartupServices: (): Promise<void> =>
|
||||
ipcRenderer.invoke('app:awaitFirstWindowStartupServices'),
|
||||
prepareTerminalStartupRestoration: (): Promise<void> =>
|
||||
ipcRenderer.invoke('app:prepareTerminalStartupRestoration'),
|
||||
recoverLegacyWorkerTerminalsForRendererStartup: (): Promise<void> =>
|
||||
ipcRenderer.invoke('app:recoverLegacyWorkerTerminalsForRendererStartup'),
|
||||
startupDiagnostic: (event: string, details?: Record<string, unknown>): Promise<void> =>
|
||||
startupDiagnosticsEnabled
|
||||
? ipcRenderer.invoke('app:startupDiagnostic', event, details)
|
||||
: Promise.resolve(),
|
||||
getKeyboardInputSourceId: (): Promise<string | null> =>
|
||||
ipcRenderer.invoke('app:getKeyboardInputSourceId'),
|
||||
getMacCapturedDigitRowChords: (): Promise<MacCapturedDigitRowChord[]> =>
|
||||
ipcRenderer.invoke('app:getMacCapturedDigitRowChords'),
|
||||
getKeyboardLayoutSnapshot: () => ipcRenderer.invoke('app:getKeyboardLayoutSnapshot'),
|
||||
onKeyboardLayoutChanged: (callback: (event: KeyboardLayoutChangeEvent) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, event: KeyboardLayoutChangeEvent): void =>
|
||||
callback(event)
|
||||
ipcRenderer.on(KEYBOARD_LAYOUT_CHANGED_CHANNEL, listener)
|
||||
return () => ipcRenderer.removeListener(KEYBOARD_LAYOUT_CHANGED_CHANNEL, listener)
|
||||
},
|
||||
setUnreadDockBadgeCount: (count: number): Promise<void> =>
|
||||
ipcRenderer.invoke('app:setUnreadDockBadgeCount', count),
|
||||
getFloatingTerminalCwd: (args?: FloatingTerminalCwdRequest): Promise<string> =>
|
||||
ipcRenderer.invoke('app:getFloatingTerminalCwd', args),
|
||||
getFloatingMarkdownDirectory: (): Promise<string> =>
|
||||
ipcRenderer.invoke('app:getFloatingMarkdownDirectory'),
|
||||
pickFloatingMarkdownDocument: (): Promise<MarkdownDocument | null> =>
|
||||
ipcRenderer.invoke('app:pickFloatingMarkdownDocument'),
|
||||
pickFloatingWorkspaceDirectory: (): Promise<string | null> =>
|
||||
ipcRenderer.invoke('app:pickFloatingWorkspaceDirectory'),
|
||||
writeTerminalRenderDesyncEvidence: (args: WriteTerminalRenderDesyncEvidenceArgs) =>
|
||||
ipcRenderer.invoke('terminal:writeRenderDesyncEvidence', args)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { ExternalAutomationManagerResult } from '../api-types'
|
||||
import type {
|
||||
AutomationDispatchRequest,
|
||||
AutomationDispatchResult,
|
||||
ExternalAutomationRunsPage,
|
||||
AutomationRun,
|
||||
AutomationPrecheckResult
|
||||
} from '../../shared/automations-types'
|
||||
import type { AutomationOwnerRef } from '../../shared/automation-owner-ref'
|
||||
import type {
|
||||
ScopedExternalManagerActionRequest,
|
||||
ScopedExternalManagerCreateRequest,
|
||||
ScopedExternalManagerListRequest,
|
||||
ScopedExternalManagerRunsRequest,
|
||||
ScopedExternalManagerUpdateRequest
|
||||
} from '../../shared/external-automation-scope'
|
||||
import type { AutomationsChangedPayload } from '../../shared/runtime-client-events'
|
||||
|
||||
export const automationsApi = {
|
||||
listExternalManagerForOwner: (
|
||||
request: ScopedExternalManagerListRequest
|
||||
): Promise<ExternalAutomationManagerResult> =>
|
||||
ipcRenderer.invoke('automations:listExternalManagerForOwner', request),
|
||||
listExternalRunsForOwner: (
|
||||
request: ScopedExternalManagerRunsRequest
|
||||
): Promise<ExternalAutomationRunsPage> =>
|
||||
ipcRenderer.invoke('automations:listExternalRunsForOwner', request),
|
||||
createExternalForOwner: (request: ScopedExternalManagerCreateRequest): Promise<void> =>
|
||||
ipcRenderer.invoke('automations:createExternalForOwner', request),
|
||||
updateExternalForOwner: (request: ScopedExternalManagerUpdateRequest): Promise<void> =>
|
||||
ipcRenderer.invoke('automations:updateExternalForOwner', request),
|
||||
runExternalActionForOwner: (request: ScopedExternalManagerActionRequest): Promise<void> =>
|
||||
ipcRenderer.invoke('automations:runExternalActionForOwner', request),
|
||||
retainExternalScopes: (request: { owners: readonly AutomationOwnerRef[] }): Promise<void> =>
|
||||
ipcRenderer.invoke('automations:retainExternalScopes', request),
|
||||
runPrecheck: (args: {
|
||||
automationId: string
|
||||
runId: string
|
||||
}): Promise<AutomationPrecheckResult | null> =>
|
||||
ipcRenderer.invoke('automations:runPrecheck', args),
|
||||
markDispatchResult: (result: AutomationDispatchResult): Promise<AutomationRun> =>
|
||||
ipcRenderer.invoke('automations:markDispatchResult', result),
|
||||
snapshotWorkspaceName: (args: { workspaceId: string; displayName: string }): Promise<number> =>
|
||||
ipcRenderer.invoke('automations:snapshotWorkspaceName', args),
|
||||
rendererReady: (): Promise<void> => ipcRenderer.invoke('automations:rendererReady'),
|
||||
onDispatchRequested: (callback: (request: AutomationDispatchRequest) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, request: AutomationDispatchRequest) =>
|
||||
callback(request)
|
||||
ipcRenderer.on('automations:dispatchRequested', listener)
|
||||
return () => ipcRenderer.removeListener('automations:dispatchRequested', listener)
|
||||
},
|
||||
onChanged: (callback: (payload: AutomationsChangedPayload) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, payload: AutomationsChangedPayload) =>
|
||||
callback(payload)
|
||||
ipcRenderer.on('automations:changed', listener)
|
||||
return () => ipcRenderer.removeListener('automations:changed', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const bitbucketApi = {
|
||||
connect: (args: {
|
||||
authMode: 'token' | 'basic'
|
||||
accessToken?: string | null
|
||||
email?: string | null
|
||||
apiToken?: string | null
|
||||
baseUrl?: string | null
|
||||
}): Promise<{ ok: true; account: string | null } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('bitbucket:connect', args),
|
||||
|
||||
disconnect: (): Promise<void> => ipcRenderer.invoke('bitbucket:disconnect'),
|
||||
|
||||
status: (): Promise<unknown> => ipcRenderer.invoke('bitbucket:status')
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { BrowserViewportOverride } from '../../shared/browser-workspace-types'
|
||||
import type {
|
||||
BrowserWebAuthnAccountRequest,
|
||||
BrowserWebAuthnAccountResponse
|
||||
} from '../../shared/browser-webauthn-account'
|
||||
import { readBrowserClientHostIdArgument } from '../../shared/browser-client-host-id-argument'
|
||||
import { browserClientPageRendererRequests } from '../preload-runtime-support'
|
||||
|
||||
export const browserApiPart1 = {
|
||||
onClientPageRendererRequest: browserClientPageRendererRequests.subscribe,
|
||||
readClientHostId: (): string | null => readBrowserClientHostIdArgument(process.argv),
|
||||
registerGuest: (args: {
|
||||
browserPageId: string
|
||||
workspaceId: string
|
||||
worktreeId: string
|
||||
sessionProfileId?: string | null
|
||||
webContentsId: number
|
||||
}): Promise<boolean> => ipcRenderer.invoke('browser:registerGuest', args),
|
||||
isGuestRegistered: (args: { browserPageId: string; webContentsId: number }): Promise<boolean> =>
|
||||
ipcRenderer.invoke('browser:isGuestRegistered', args),
|
||||
repairGuestRegistration: (args: {
|
||||
browserPageId: string
|
||||
workspaceId: string
|
||||
worktreeId: string
|
||||
sessionProfileId?: string | null
|
||||
webContentsId: number
|
||||
}): Promise<boolean> => ipcRenderer.invoke('browser:repairGuestRegistration', args),
|
||||
unregisterGuest: (args: { browserPageId: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('browser:unregisterGuest', args),
|
||||
onWebAuthnAccountRequest: (
|
||||
callback: (request: BrowserWebAuthnAccountRequest) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
request: BrowserWebAuthnAccountRequest
|
||||
): void => callback(request)
|
||||
ipcRenderer.on('browser:webauthn-account-requested', listener)
|
||||
return () => ipcRenderer.removeListener('browser:webauthn-account-requested', listener)
|
||||
},
|
||||
onWebAuthnAccountRequestClosed: (
|
||||
callback: (event: { requestId: string }) => void
|
||||
): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { requestId: string }): void =>
|
||||
callback(data)
|
||||
ipcRenderer.on('browser:webauthn-account-request-closed', listener)
|
||||
return () => ipcRenderer.removeListener('browser:webauthn-account-request-closed', listener)
|
||||
},
|
||||
respondWebAuthnAccount: (response: BrowserWebAuthnAccountResponse): Promise<boolean> =>
|
||||
ipcRenderer.invoke('browser:respondWebAuthnAccount', response),
|
||||
openDevTools: (args: { browserPageId: string }): Promise<boolean> =>
|
||||
ipcRenderer.invoke('browser:openDevTools', args),
|
||||
setViewportOverride: (args: {
|
||||
browserPageId: string
|
||||
override: BrowserViewportOverride | null
|
||||
}): Promise<boolean> => ipcRenderer.invoke('browser:setViewportOverride', args),
|
||||
reportViewportScrollState: (args: {
|
||||
browserPageId: string
|
||||
state: {
|
||||
scrollLeft: number
|
||||
scrollTop: number
|
||||
maxScrollLeft: number
|
||||
maxScrollTop: number
|
||||
}
|
||||
}): void => ipcRenderer.send('browser:reportViewportScrollState', args),
|
||||
setAnnotationViewportBridge: (args): Promise<boolean> =>
|
||||
ipcRenderer.invoke('browser:setAnnotationViewportBridge', args),
|
||||
publishClientPageMetadata: (args) =>
|
||||
ipcRenderer.invoke('browser:publishClientPageMetadata', args),
|
||||
onGuestLoadFailed: (
|
||||
callback: (args: {
|
||||
browserPageId: string
|
||||
loadError: { code: number; description: string; validatedUrl: string }
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
browserPageId: string
|
||||
loadError: { code: number; description: string; validatedUrl: string }
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:guest-load-failed', listener)
|
||||
return () => ipcRenderer.removeListener('browser:guest-load-failed', listener)
|
||||
},
|
||||
onCertificateFailureChanged: (callback): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: Parameters<typeof callback>[0]
|
||||
): void => callback(data)
|
||||
ipcRenderer.on('browser:certificate-failure-changed', listener)
|
||||
return () => ipcRenderer.removeListener('browser:certificate-failure-changed', listener)
|
||||
},
|
||||
proceedCertificate: (args) => ipcRenderer.invoke('browser:proceedCertificate', args),
|
||||
onPermissionDenied: (
|
||||
callback: (event: { browserPageId: string; permission: string; origin: string }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { browserPageId: string; permission: string; origin: string }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:permission-denied', listener)
|
||||
return () => ipcRenderer.removeListener('browser:permission-denied', listener)
|
||||
},
|
||||
onPopup: (
|
||||
callback: (event: {
|
||||
browserPageId: string
|
||||
origin: string
|
||||
action: 'opened-in-orca' | 'opened-external' | 'blocked'
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
browserPageId: string
|
||||
origin: string
|
||||
action: 'opened-in-orca' | 'opened-external' | 'blocked'
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:popup', listener)
|
||||
return () => ipcRenderer.removeListener('browser:popup', listener)
|
||||
},
|
||||
onDownloadRequested: (
|
||||
callback: (event: {
|
||||
browserPageId: string
|
||||
downloadId: string
|
||||
origin: string
|
||||
filename: string
|
||||
totalBytes: number | null
|
||||
mimeType: string | null
|
||||
savePath: string
|
||||
status: 'downloading'
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
browserPageId: string
|
||||
downloadId: string
|
||||
origin: string
|
||||
filename: string
|
||||
totalBytes: number | null
|
||||
mimeType: string | null
|
||||
savePath: string
|
||||
status: 'downloading'
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:download-requested', listener)
|
||||
return () => ipcRenderer.removeListener('browser:download-requested', listener)
|
||||
},
|
||||
onDownloadProgress: (
|
||||
callback: (event: {
|
||||
browserPageId?: string
|
||||
downloadId: string
|
||||
receivedBytes: number
|
||||
totalBytes: number | null
|
||||
state: 'progressing' | 'interrupted' | null
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
browserPageId?: string
|
||||
downloadId: string
|
||||
receivedBytes: number
|
||||
totalBytes: number | null
|
||||
state: 'progressing' | 'interrupted' | null
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:download-progress', listener)
|
||||
return () => ipcRenderer.removeListener('browser:download-progress', listener)
|
||||
},
|
||||
onDownloadFinished: (
|
||||
callback: (event: {
|
||||
browserPageId?: string
|
||||
downloadId: string
|
||||
status: 'completed' | 'canceled' | 'failed'
|
||||
savePath: string | null
|
||||
remoteDestination?: { workspaceRelativePath: string; hostLabel: string }
|
||||
error: string | null
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
browserPageId?: string
|
||||
downloadId: string
|
||||
status: 'completed' | 'canceled' | 'failed'
|
||||
savePath: string | null
|
||||
remoteDestination?: { workspaceRelativePath: string; hostLabel: string }
|
||||
error: string | null
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:download-finished', listener)
|
||||
return () => ipcRenderer.removeListener('browser:download-finished', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const browserApiPart2 = {
|
||||
onContextMenuRequested: (
|
||||
callback: (event: {
|
||||
browserPageId: string
|
||||
x: number
|
||||
y: number
|
||||
screenX: number
|
||||
screenY: number
|
||||
pageUrl: string
|
||||
linkUrl: string | null
|
||||
selectionText: string
|
||||
canGoBack: boolean
|
||||
canGoForward: boolean
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
browserPageId: string
|
||||
x: number
|
||||
y: number
|
||||
screenX: number
|
||||
screenY: number
|
||||
pageUrl: string
|
||||
linkUrl: string | null
|
||||
selectionText: string
|
||||
canGoBack: boolean
|
||||
canGoForward: boolean
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:context-menu-requested', listener)
|
||||
return () => ipcRenderer.removeListener('browser:context-menu-requested', listener)
|
||||
},
|
||||
onContextMenuDismissed: (callback: (event: { browserPageId: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { browserPageId: string }) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('browser:context-menu-dismissed', listener)
|
||||
return () => ipcRenderer.removeListener('browser:context-menu-dismissed', listener)
|
||||
},
|
||||
onNavigationUpdate: (
|
||||
callback: (event: { browserPageId: string; url: string; title: string }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { browserPageId: string; url: string; title: string }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:navigation-update', listener)
|
||||
return () => ipcRenderer.removeListener('browser:navigation-update', listener)
|
||||
},
|
||||
onActivateView: (
|
||||
callback: (data: { worktreeId?: string; browserPageId?: string }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { worktreeId?: string; browserPageId?: string }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:activateView', listener)
|
||||
return () => ipcRenderer.removeListener('browser:activateView', listener)
|
||||
},
|
||||
onPaneFocus: (
|
||||
callback: (data: { worktreeId: string | null; browserPageId: string }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { worktreeId: string | null; browserPageId: string }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:pane-focus', listener)
|
||||
return () => ipcRenderer.removeListener('browser:pane-focus', listener)
|
||||
},
|
||||
onOpenLinkInOrcaTab: (
|
||||
callback: (event: { browserPageId: string; url: string }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { browserPageId: string; url: string }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:open-link-in-orca-tab', listener)
|
||||
return () => ipcRenderer.removeListener('browser:open-link-in-orca-tab', listener)
|
||||
},
|
||||
cancelDownload: (args: { downloadId: string }): Promise<boolean> =>
|
||||
ipcRenderer.invoke('browser:cancelDownload', args),
|
||||
setGrabMode: (args: {
|
||||
browserPageId: string
|
||||
enabled: boolean
|
||||
}): Promise<{ ok: true } | { ok: false; reason: string }> =>
|
||||
ipcRenderer.invoke('browser:setGrabMode', args),
|
||||
awaitGrabSelection: (args: { browserPageId: string; opId: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('browser:awaitGrabSelection', args),
|
||||
cancelGrab: (args: { browserPageId: string }): Promise<boolean> =>
|
||||
ipcRenderer.invoke('browser:cancelGrab', args),
|
||||
captureSelectionScreenshot: (args: {
|
||||
browserPageId: string
|
||||
rect: { x: number; y: number; width: number; height: number }
|
||||
}): Promise<{ ok: true; screenshot: unknown } | { ok: false; reason: string }> =>
|
||||
ipcRenderer.invoke('browser:captureSelectionScreenshot', args),
|
||||
extractHoverPayload: (args: {
|
||||
browserPageId: string
|
||||
}): Promise<{ ok: true; payload: unknown } | { ok: false; reason: string }> =>
|
||||
ipcRenderer.invoke('browser:extractHoverPayload', args),
|
||||
onGrabModeToggle: (callback: (browserPageId: string) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, browserPageId: string) =>
|
||||
callback(browserPageId)
|
||||
ipcRenderer.on('browser:grabModeToggle', listener)
|
||||
return () => ipcRenderer.removeListener('browser:grabModeToggle', listener)
|
||||
},
|
||||
onGrabActionShortcut: (
|
||||
callback: (args: { browserPageId: string; key: 'c' | 's' }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { browserPageId: string; key: 'c' | 's' }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:grabActionShortcut', listener)
|
||||
return () => ipcRenderer.removeListener('browser:grabActionShortcut', listener)
|
||||
},
|
||||
sessionListProfiles: (): Promise<unknown[]> => ipcRenderer.invoke('browser:session:listProfiles'),
|
||||
prepareSshWorkspacePartition: (args: {
|
||||
targetId: string
|
||||
browserProfileId?: string
|
||||
skipProbe?: boolean
|
||||
}): Promise<{ partition: string }> =>
|
||||
ipcRenderer.invoke('browser:prepareSshWorkspacePartition', args),
|
||||
sessionCreateProfile: (args: {
|
||||
scope: 'default' | 'isolated' | 'imported'
|
||||
label: string
|
||||
userAgentMode?: 'clean' | 'native'
|
||||
}): Promise<unknown> => ipcRenderer.invoke('browser:session:createProfile', args),
|
||||
sessionDeleteProfile: (args: { profileId: string }): Promise<boolean> =>
|
||||
ipcRenderer.invoke('browser:session:deleteProfile', args),
|
||||
sessionImportCookies: (args: {
|
||||
profileId: string
|
||||
}): Promise<{ ok: true; profileId: string; summary: unknown } | { ok: false; reason: string }> =>
|
||||
ipcRenderer.invoke('browser:session:importCookies', args),
|
||||
sessionResolvePartition: (args: { profileId: string | null }): Promise<string | null> =>
|
||||
ipcRenderer.invoke('browser:session:resolvePartition', args),
|
||||
sessionDetectBrowsers: (): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('browser:session:detectBrowsers'),
|
||||
sessionDetectBrowsersForClientHost: (args: {
|
||||
environmentId: string
|
||||
}): Promise<unknown[] | null> =>
|
||||
ipcRenderer.invoke('browser:session:detectBrowsersForClientHost', args),
|
||||
sessionImportFromBrowser: (args: {
|
||||
profileId: string
|
||||
browserFamily: string
|
||||
}): Promise<{ ok: true; profileId: string; summary: unknown } | { ok: false; reason: string }> =>
|
||||
ipcRenderer.invoke('browser:session:importFromBrowser', args),
|
||||
sessionImportFromBrowserForClientHost: (args: {
|
||||
environmentId: string
|
||||
profileId: string
|
||||
browserFamily: string
|
||||
browserProfile?: string
|
||||
}): Promise<
|
||||
{ ok: true; profileId: string; summary: unknown } | { ok: false; reason: string } | null
|
||||
> => ipcRenderer.invoke('browser:session:importFromBrowserForClientHost', args),
|
||||
sessionClientRouteImportSources: (args: {
|
||||
environmentId: string
|
||||
}): Promise<Record<string, unknown>> =>
|
||||
ipcRenderer.invoke('browser:session:clientRouteImportSources', args),
|
||||
sessionClearDefaultCookies: (): Promise<boolean> =>
|
||||
ipcRenderer.invoke('browser:session:clearDefaultCookies'),
|
||||
notifyActiveTabChanged: (args: { browserPageId: string }): Promise<boolean> =>
|
||||
ipcRenderer.invoke('browser:activeTabChanged', args)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { browserApiPart1 } from './browser-bridge-part-1'
|
||||
import { browserApiPart2 } from './browser-bridge-part-2'
|
||||
|
||||
export const browserApi = { ...browserApiPart1, ...browserApiPart2 }
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const cacheApi = {
|
||||
getGitHub: () => ipcRenderer.invoke('cache:getGitHub'),
|
||||
setGitHub: (args) => ipcRenderer.invoke('cache:setGitHub', args)
|
||||
} satisfies PreloadApi['cache']
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const claudeAccountsApi = {
|
||||
list: (): Promise<unknown> => ipcRenderer.invoke('claudeAccounts:list'),
|
||||
add: (args?: { runtime?: 'host' | 'wsl'; wslDistro?: string | null }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('claudeAccounts:add', args),
|
||||
cancelPendingLogin: (): Promise<boolean> =>
|
||||
ipcRenderer.invoke('claudeAccounts:cancelPendingLogin'),
|
||||
reauthenticate: (args: { accountId: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('claudeAccounts:reauthenticate', args),
|
||||
remove: (args: { accountId: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('claudeAccounts:remove', args),
|
||||
select: (args: {
|
||||
accountId: string | null
|
||||
runtime?: 'host' | 'wsl'
|
||||
wslDistro?: string | null
|
||||
}): Promise<unknown> => ipcRenderer.invoke('claudeAccounts:select', args)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { createUsageProviderApi } from '../usage-provider-api'
|
||||
|
||||
export const claudeUsageApi = createUsageProviderApi(ipcRenderer, 'claudeUsage')
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { CliInstallStatus } from '../../shared/cli-install-types'
|
||||
|
||||
export const cliApi = {
|
||||
getInstallStatus: (): Promise<CliInstallStatus> => ipcRenderer.invoke('cli:getInstallStatus'),
|
||||
install: (): Promise<CliInstallStatus> => ipcRenderer.invoke('cli:install'),
|
||||
remove: (): Promise<CliInstallStatus> => ipcRenderer.invoke('cli:remove'),
|
||||
getWslInstallStatus: (args?: { distro?: string | null }): Promise<CliInstallStatus> =>
|
||||
ipcRenderer.invoke('cli:getWslInstallStatus', args),
|
||||
installWsl: (args?: { distro?: string | null }): Promise<CliInstallStatus> =>
|
||||
ipcRenderer.invoke('cli:installWsl', args),
|
||||
removeWsl: (args?: { distro?: string | null }): Promise<CliInstallStatus> =>
|
||||
ipcRenderer.invoke('cli:removeWsl', args)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const codexAccountsApi = {
|
||||
list: (): Promise<unknown> => ipcRenderer.invoke('codexAccounts:list'),
|
||||
add: (args?: { runtime?: 'host' | 'wsl'; wslDistro?: string | null }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('codexAccounts:add', args),
|
||||
reauthenticate: (args: {
|
||||
accountId: string
|
||||
activateIfSelectionWasEmpty?: boolean
|
||||
}): Promise<unknown> => ipcRenderer.invoke('codexAccounts:reauthenticate', args),
|
||||
remove: (args: { accountId: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('codexAccounts:remove', args),
|
||||
select: (args: {
|
||||
accountId: string | null
|
||||
runtime?: 'host' | 'wsl'
|
||||
wslDistro?: string | null
|
||||
}): Promise<unknown> => ipcRenderer.invoke('codexAccounts:select', args),
|
||||
listStalePanes: (args: {
|
||||
ptyIds: string[]
|
||||
}): Promise<
|
||||
{
|
||||
ptyId: string
|
||||
launchAccountId: string | null
|
||||
activeAccountId: string | null
|
||||
reason?: 'account-change' | 'home-route-change'
|
||||
}[]
|
||||
> => ipcRenderer.invoke('codexAccounts:listStalePanes', args),
|
||||
listRecordedPaneLanes: (args: { ptyIds: string[] }): Promise<Record<string, string>> =>
|
||||
ipcRenderer.invoke('codexAccounts:listRecordedPaneLanes', args),
|
||||
forgetStalePanes: (args: { ptyIds: string[] }): Promise<void> =>
|
||||
ipcRenderer.invoke('codexAccounts:forgetStalePanes', args)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { CodexConfigSyncStatus } from '../../shared/codex-config-sync-types'
|
||||
|
||||
export const codexConfigSyncApi = {
|
||||
status: (): Promise<CodexConfigSyncStatus> => ipcRenderer.invoke('codexConfigSync:status')
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { createUsageProviderApi } from '../usage-provider-api'
|
||||
|
||||
export const codexUsageApi = createUsageProviderApi(ipcRenderer, 'codexUsage')
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const computerUsePermissionsApi = {
|
||||
getStatus: (): Promise<unknown> => ipcRenderer.invoke('computerUsePermissions:getStatus'),
|
||||
openSetup: (args?: { id?: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('computerUsePermissions:openSetup', args),
|
||||
reset: (): Promise<unknown> => ipcRenderer.invoke('computerUsePermissions:reset')
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
CrashReportBreadcrumbData,
|
||||
CrashReportCopyDiagnosticsArgs,
|
||||
CrashReportSubmitArgs,
|
||||
CrashReportSubmitResult,
|
||||
ReactErrorBoundaryReportArgs,
|
||||
ReactErrorBoundaryReportResult
|
||||
} from '../../shared/crash-reporting'
|
||||
import type { RendererHeapStatistics } from '../../shared/renderer-heap-statistics'
|
||||
import type { RendererProcessMemory } from '../../shared/renderer-process-memory'
|
||||
import { readRendererHeapStatistics } from '../renderer-heap-statistics-reader'
|
||||
import { readRendererProcessMemory } from '../renderer-process-memory-reader'
|
||||
|
||||
export const crashReportsApi = {
|
||||
getLatestPending: () => ipcRenderer.invoke('crashReports:getLatestPending'),
|
||||
getLatestReport: () => ipcRenderer.invoke('crashReports:getLatestReport'),
|
||||
dismiss: (args: { reportId: string }) => ipcRenderer.invoke('crashReports:dismiss', args),
|
||||
recordRendererError: (
|
||||
args: ReactErrorBoundaryReportArgs
|
||||
): Promise<ReactErrorBoundaryReportResult> =>
|
||||
ipcRenderer.invoke('crashReports:recordRendererError', args),
|
||||
recordBreadcrumb: (args: { name: string; data?: CrashReportBreadcrumbData }): void =>
|
||||
ipcRenderer.send('crashReports:recordBreadcrumb', args),
|
||||
submit: (args: CrashReportSubmitArgs): Promise<CrashReportSubmitResult> =>
|
||||
ipcRenderer.invoke('crashReports:submit', args),
|
||||
copyLatestDiagnostics: (args?: CrashReportCopyDiagnosticsArgs) =>
|
||||
ipcRenderer.invoke('crashReports:copyLatestDiagnostics', args),
|
||||
readHeapStatistics: (): RendererHeapStatistics | null => readRendererHeapStatistics(),
|
||||
readProcessMemory: (): Promise<RendererProcessMemory | null> => readRendererProcessMemory()
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
DashboardRevealAgentArgs,
|
||||
DashboardSleepWorkspaceArgs,
|
||||
DashboardSnapshot,
|
||||
DashboardSpawnAgentArgs
|
||||
} from '../../shared/dashboard-snapshot'
|
||||
|
||||
export const dashboardApi = {
|
||||
// Open the pop-out dashboard window, or focus it if already open.
|
||||
openPopout: (view?: 'board' | 'map'): Promise<void> =>
|
||||
ipcRenderer.invoke('dashboardPopout:open', view),
|
||||
|
||||
// ── Producer side (main window) ──────────────────────────────────────
|
||||
publishSnapshot: (snapshot: DashboardSnapshot): Promise<void> =>
|
||||
ipcRenderer.invoke('dashboard:publishSnapshot', snapshot),
|
||||
getPopoutOpen: (): Promise<boolean> => ipcRenderer.invoke('dashboard:getPopoutOpen'),
|
||||
onPopoutOpenChanged: (callback: (open: boolean) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, open: boolean): void => callback(open)
|
||||
ipcRenderer.on('dashboard:popoutOpenChanged', listener)
|
||||
return () => ipcRenderer.removeListener('dashboard:popoutOpenChanged', listener)
|
||||
},
|
||||
onSnapshotRequested: (callback: () => void): (() => void) => {
|
||||
const listener = (): void => callback()
|
||||
ipcRenderer.on('dashboard:snapshotRequested', listener)
|
||||
return () => ipcRenderer.removeListener('dashboard:snapshotRequested', listener)
|
||||
},
|
||||
onRevealAgent: (callback: (args: DashboardRevealAgentArgs) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, args: DashboardRevealAgentArgs): void =>
|
||||
callback(args)
|
||||
ipcRenderer.on('ui:revealDashboardAgent', listener)
|
||||
return () => ipcRenderer.removeListener('ui:revealDashboardAgent', listener)
|
||||
},
|
||||
onAckAgent: (callback: (paneKey: string) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, paneKey: string): void => callback(paneKey)
|
||||
ipcRenderer.on('ui:ackDashboardAgent', listener)
|
||||
return () => ipcRenderer.removeListener('ui:ackDashboardAgent', listener)
|
||||
},
|
||||
onSpawnAgent: (callback: (args: DashboardSpawnAgentArgs) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, args: DashboardSpawnAgentArgs): void =>
|
||||
callback(args)
|
||||
ipcRenderer.on('ui:spawnDashboardAgent', listener)
|
||||
return () => ipcRenderer.removeListener('ui:spawnDashboardAgent', listener)
|
||||
},
|
||||
onSleepWorkspace: (callback: (args: DashboardSleepWorkspaceArgs) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, args: DashboardSleepWorkspaceArgs): void =>
|
||||
callback(args)
|
||||
ipcRenderer.on('ui:sleepDashboardWorkspace', listener)
|
||||
return () => ipcRenderer.removeListener('ui:sleepDashboardWorkspace', listener)
|
||||
},
|
||||
|
||||
// ── Consumer side (pop-out window) ───────────────────────────────────
|
||||
requestSnapshot: (): Promise<void> => ipcRenderer.invoke('dashboard:requestSnapshot'),
|
||||
onSnapshot: (callback: (snapshot: DashboardSnapshot) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, snapshot: DashboardSnapshot): void =>
|
||||
callback(snapshot)
|
||||
ipcRenderer.on('dashboard:snapshot', listener)
|
||||
return () => ipcRenderer.removeListener('dashboard:snapshot', listener)
|
||||
},
|
||||
onViewRequested: (callback: (view: 'board' | 'map') => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, view: 'board' | 'map'): void =>
|
||||
callback(view)
|
||||
ipcRenderer.on('dashboard:viewRequested', listener)
|
||||
return () => ipcRenderer.removeListener('dashboard:viewRequested', listener)
|
||||
},
|
||||
revealAgent: (args: DashboardRevealAgentArgs): Promise<void> =>
|
||||
ipcRenderer.invoke('dashboardPopout:revealAgent', args),
|
||||
ackAgent: (paneKey: string): Promise<void> =>
|
||||
ipcRenderer.invoke('dashboardPopout:ackAgent', { paneKey }),
|
||||
spawnAgent: (args: DashboardSpawnAgentArgs): Promise<void> =>
|
||||
ipcRenderer.invoke('dashboardPopout:spawnAgent', args),
|
||||
sleepWorkspace: (args: DashboardSleepWorkspaceArgs): Promise<void> =>
|
||||
ipcRenderer.invoke('dashboardPopout:sleepWorkspace', args)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const developerPermissionsApi = {
|
||||
getStatus: (): Promise<unknown> => ipcRenderer.invoke('developerPermissions:getStatus'),
|
||||
request: (args: { id: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('developerPermissions:request', args),
|
||||
openSettings: (args: { id: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('developerPermissions:openSettings', args),
|
||||
testLocalNetworkConnection: (args: { host: string; port: number }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('developerPermissions:testLocalNetworkConnection', args)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const diagnosticsApi = {
|
||||
getStatus: (): Promise<unknown> => ipcRenderer.invoke('diagnostics:getStatus'),
|
||||
collectBundle: (lookbackMinutes?: number): Promise<unknown> =>
|
||||
ipcRenderer.invoke('diagnostics:collectBundle', lookbackMinutes),
|
||||
openBundlePreview: (bundleSubmissionId: string): Promise<void> =>
|
||||
ipcRenderer.invoke('diagnostics:openBundlePreview', bundleSubmissionId),
|
||||
discardBundlePreview: (bundleSubmissionId: string): Promise<void> =>
|
||||
ipcRenderer.invoke('diagnostics:discardBundlePreview', bundleSubmissionId),
|
||||
uploadBundle: (bundleSubmissionId: string): Promise<unknown> =>
|
||||
ipcRenderer.invoke('diagnostics:uploadBundle', bundleSubmissionId),
|
||||
deleteBundle: (ticketId: string): Promise<void> =>
|
||||
ipcRenderer.invoke('diagnostics:deleteBundle', ticketId)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import {
|
||||
DOC_PREVIEW_EXTERNAL_LINK_CHANNEL,
|
||||
DOC_PREVIEW_LOAD_FAILURE_CHANNEL,
|
||||
DOC_PREVIEW_AUTHORIZE_DIRECTORY_CHANNEL,
|
||||
DOC_PREVIEW_MINT_GRANT_CHANNEL,
|
||||
DOC_PREVIEW_REVOKE_GRANT_CHANNEL,
|
||||
type DocPreviewFailure
|
||||
} from '../../shared/doc-preview-scheme'
|
||||
import type { DocPreviewGrantRequest } from '../api/doc-preview-api'
|
||||
|
||||
export const docPreviewApi = {
|
||||
mintGrant: (request: DocPreviewGrantRequest): Promise<{ grantId: string; url: string }> =>
|
||||
ipcRenderer.invoke(DOC_PREVIEW_MINT_GRANT_CHANNEL, request),
|
||||
revokeGrant: (grantId: string): Promise<boolean> =>
|
||||
ipcRenderer.invoke(DOC_PREVIEW_REVOKE_GRANT_CHANNEL, grantId),
|
||||
authorizeDirectory: (grantId: string, relativePath: string): Promise<boolean> =>
|
||||
ipcRenderer.invoke(DOC_PREVIEW_AUTHORIZE_DIRECTORY_CHANNEL, grantId, relativePath),
|
||||
onExternalLink: (callback: (payload: { url: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, payload: { url: string }): void =>
|
||||
callback(payload)
|
||||
ipcRenderer.on(DOC_PREVIEW_EXTERNAL_LINK_CHANNEL, listener)
|
||||
return () => ipcRenderer.removeListener(DOC_PREVIEW_EXTERNAL_LINK_CHANNEL, listener)
|
||||
},
|
||||
onLoadFailure: (callback: (payload: DocPreviewFailure) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, payload: DocPreviewFailure): void =>
|
||||
callback(payload)
|
||||
ipcRenderer.on(DOC_PREVIEW_LOAD_FAILURE_CHANNEL, listener)
|
||||
return () => ipcRenderer.removeListener(DOC_PREVIEW_LOAD_FAILURE_CHANNEL, listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { preloadE2EConfig } from '../e2e-config'
|
||||
|
||||
export const e2eApi = {
|
||||
getConfig: () => preloadE2EConfig
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const emulatorApi = {
|
||||
startFrameStream: (args: {
|
||||
streamUrl: string
|
||||
streamKey?: string
|
||||
}): Promise<{
|
||||
streamId: string
|
||||
}> => ipcRenderer.invoke('emulator:frameStreamStart', args),
|
||||
stopFrameStream: (args: { streamId: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('emulator:frameStreamStop', args),
|
||||
onFrameStreamFrame: (
|
||||
callback: (data: { streamId: string; bytes: ArrayBuffer }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { streamId: string; bytes: ArrayBuffer }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('emulator:frameStreamFrame', listener)
|
||||
return () => ipcRenderer.removeListener('emulator:frameStreamFrame', listener)
|
||||
},
|
||||
onFrameStreamError: (
|
||||
callback: (data: { streamId: string; message: string }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { streamId: string; message: string }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('emulator:frameStreamError', listener)
|
||||
return () => ipcRenderer.removeListener('emulator:frameStreamError', listener)
|
||||
},
|
||||
startVideoStream: (args: { deviceId: string; streamId: string }): Promise<{ streamId: string }> =>
|
||||
ipcRenderer.invoke('emulator:videoStreamStart', args),
|
||||
stopVideoStream: (args: { streamId: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('emulator:videoStreamStop', args),
|
||||
onVideoStreamMeta: (
|
||||
callback: (data: {
|
||||
streamId: string
|
||||
deviceId: string
|
||||
meta: { codecId: string; width: number; height: number }
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
streamId: string
|
||||
deviceId: string
|
||||
meta: { codecId: string; width: number; height: number }
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('emulator:videoStreamMeta', listener)
|
||||
return () => ipcRenderer.removeListener('emulator:videoStreamMeta', listener)
|
||||
},
|
||||
onVideoStreamFrame: (
|
||||
callback: (data: {
|
||||
streamId: string
|
||||
deviceId: string
|
||||
config: boolean
|
||||
keyFrame: boolean
|
||||
bytes: ArrayBuffer
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
streamId: string
|
||||
deviceId: string
|
||||
config: boolean
|
||||
keyFrame: boolean
|
||||
bytes: ArrayBuffer
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('emulator:videoStreamFrame', listener)
|
||||
return () => ipcRenderer.removeListener('emulator:videoStreamFrame', listener)
|
||||
},
|
||||
onPaneFocus: (callback: (data: { worktreeId: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { worktreeId: string }) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('emulator:pane-focus', listener)
|
||||
return () => ipcRenderer.removeListener('emulator:pane-focus', listener)
|
||||
},
|
||||
onAutoAttach: (
|
||||
callback: (data: {
|
||||
worktreeId: string
|
||||
info: { deviceUdid: string; streamUrl: string; wsUrl: string; axUrl?: string }
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
worktreeId: string
|
||||
info: { deviceUdid: string; streamUrl: string; wsUrl: string; axUrl?: string }
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ui:emulatorAutoAttach', listener)
|
||||
return () => ipcRenderer.removeListener('ui:emulatorAutoAttach', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const ephemeralVmApi = {
|
||||
listRecipes: (args) => ipcRenderer.invoke('ephemeralVm:listRecipes', args),
|
||||
listRecipeCatalog: () => ipcRenderer.invoke('ephemeralVm:listRecipeCatalog'),
|
||||
doctor: (args) => ipcRenderer.invoke('ephemeralVm:doctor', args),
|
||||
provision: (args) => ipcRenderer.invoke('ephemeralVm:provision', args),
|
||||
cancelProvision: (args) => ipcRenderer.invoke('ephemeralVm:cancelProvision', args),
|
||||
onProvisionEvent: (callback) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
event: { provisionId: string; stream: 'stdout' | 'stderr'; chunk: string }
|
||||
): void => callback(event)
|
||||
ipcRenderer.on('ephemeralVm:provisionEvent', listener)
|
||||
return () => ipcRenderer.removeListener('ephemeralVm:provisionEvent', listener)
|
||||
},
|
||||
listRuntimes: () => ipcRenderer.invoke('ephemeralVm:listRuntimes'),
|
||||
attachWorkspace: (args) => ipcRenderer.invoke('ephemeralVm:attachWorkspace', args),
|
||||
suspendWorkspace: (args) => ipcRenderer.invoke('ephemeralVm:suspendWorkspace', args),
|
||||
resumeWorkspace: (args) => ipcRenderer.invoke('ephemeralVm:resumeWorkspace', args),
|
||||
cleanup: (args) => ipcRenderer.invoke('ephemeralVm:cleanup', args),
|
||||
stopCleanup: (args) => ipcRenderer.invoke('ephemeralVm:stopCleanup', args),
|
||||
getCleanupCommand: (args) => ipcRenderer.invoke('ephemeralVm:getCleanupCommand', args)
|
||||
} satisfies PreloadApi['ephemeralVm']
|
||||
@@ -0,0 +1,10 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const exportApi = {
|
||||
htmlToPdf: (args: {
|
||||
html: string
|
||||
title: string
|
||||
}): Promise<
|
||||
{ success: true; filePath: string } | { success: false; cancelled?: boolean; error?: string }
|
||||
> => ipcRenderer.invoke('export:html-to-pdf', args)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const feedbackApi = {
|
||||
submit: (args: {
|
||||
feedback: string
|
||||
submitAnonymously?: boolean
|
||||
githubLogin: string | null
|
||||
githubEmail: string | null
|
||||
images?: { contentType: string; data: Uint8Array }[]
|
||||
}): Promise<
|
||||
{ ok: true; imagesDelivered?: boolean } | { ok: false; status: number | null; error: string }
|
||||
> => ipcRenderer.invoke('feedback:submit', args)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const folderWorkspacesApi = {
|
||||
list: () => ipcRenderer.invoke('folderWorkspaces:list'),
|
||||
getPathStatus: (args) => ipcRenderer.invoke('folderWorkspaces:getPathStatus', args),
|
||||
create: (args) => ipcRenderer.invoke('folderWorkspaces:create', args),
|
||||
update: (args) => ipcRenderer.invoke('folderWorkspaces:update', args),
|
||||
delete: (args) => ipcRenderer.invoke('folderWorkspaces:delete', args)
|
||||
} satisfies PreloadApi['folderWorkspaces']
|
||||
@@ -0,0 +1,219 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { SshMutationExpectation } from '../../shared/ssh-types'
|
||||
import type { SearchResult } from '../../shared/code-search-types'
|
||||
import type { FsChangedPayload } from '../../shared/filesystem-entry-types'
|
||||
import type {
|
||||
LocalLogTailChangedPayload,
|
||||
LocalLogTailReadArgs,
|
||||
LocalLogTailReadResult,
|
||||
LocalLogTailWatchArgs
|
||||
} from '../../shared/local-log-tail-types'
|
||||
|
||||
export const fsApi = {
|
||||
readDir: (args: {
|
||||
dirPath: string
|
||||
connectionId?: string
|
||||
}): Promise<{ name: string; isDirectory: boolean; isSymlink: boolean }[]> =>
|
||||
ipcRenderer.invoke('fs:readDir', args),
|
||||
readFile: (args: {
|
||||
filePath: string
|
||||
connectionId?: string
|
||||
includeLocalLogMetadata?: boolean
|
||||
}): Promise<{
|
||||
content: string
|
||||
isBinary: boolean
|
||||
isImage?: boolean
|
||||
mimeType?: string
|
||||
fileIdentity?: string
|
||||
}> => ipcRenderer.invoke('fs:readFile', args),
|
||||
readLocalLogTail: (args: LocalLogTailReadArgs): Promise<LocalLogTailReadResult> =>
|
||||
ipcRenderer.invoke('fs:readLocalLogTail', args),
|
||||
startLocalLogTail: (args: LocalLogTailWatchArgs): Promise<void> =>
|
||||
ipcRenderer.invoke('fs:startLocalLogTail', args),
|
||||
stopLocalLogTail: (args: { subscriptionId: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('fs:stopLocalLogTail', args),
|
||||
onLocalLogTailChanged: (
|
||||
callback: (payload: LocalLogTailChangedPayload) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
payload: LocalLogTailChangedPayload
|
||||
): void => callback(payload)
|
||||
ipcRenderer.on('fs:localLogTailChanged', listener)
|
||||
return () => ipcRenderer.removeListener('fs:localLogTailChanged', listener)
|
||||
},
|
||||
downloadFile: (args: {
|
||||
filePath: string
|
||||
connectionId: string
|
||||
}): Promise<{ canceled: true } | { canceled: false; destinationPath: string }> =>
|
||||
ipcRenderer.invoke('fs:downloadFile', args),
|
||||
downloadFolder: (args: {
|
||||
dirPath: string
|
||||
connectionId: string
|
||||
}): Promise<{ canceled: true } | { canceled: false; destinationPath: string }> =>
|
||||
ipcRenderer.invoke('fs:downloadFolder', args),
|
||||
saveDownloadedFile: (args: {
|
||||
suggestedName: string
|
||||
content: string
|
||||
encoding: 'utf8' | 'base64'
|
||||
}): Promise<{ canceled: true } | { canceled: false; destinationPath: string }> =>
|
||||
ipcRenderer.invoke('fs:saveDownloadedFile', args),
|
||||
startDownloadedFile: (args: {
|
||||
suggestedName: string
|
||||
}): Promise<
|
||||
{ canceled: true } | { canceled: false; transferId: string; destinationPath: string }
|
||||
> => ipcRenderer.invoke('fs:startDownloadedFile', args),
|
||||
appendDownloadedFileChunk: (args: {
|
||||
transferId: string
|
||||
contentBase64: string
|
||||
}): Promise<{ ok: true }> => ipcRenderer.invoke('fs:appendDownloadedFileChunk', args),
|
||||
finishDownloadedFile: (args: {
|
||||
transferId: string
|
||||
}): Promise<{ canceled: false; destinationPath: string }> =>
|
||||
ipcRenderer.invoke('fs:finishDownloadedFile', args),
|
||||
cancelDownloadedFile: (args: { transferId: string }): Promise<{ ok: true }> =>
|
||||
ipcRenderer.invoke('fs:cancelDownloadedFile', args),
|
||||
listMarkdownDocuments: (args: {
|
||||
rootPath: string
|
||||
connectionId?: string
|
||||
}): Promise<{ filePath: string; relativePath: string; basename: string; name: string }[]> =>
|
||||
ipcRenderer.invoke('fs:listMarkdownDocuments', args),
|
||||
writeFile: (
|
||||
args: {
|
||||
filePath: string
|
||||
content: string
|
||||
connectionId?: string
|
||||
} & SshMutationExpectation
|
||||
): Promise<void> => ipcRenderer.invoke('fs:writeFile', args),
|
||||
createFile: (
|
||||
args: { filePath: string; connectionId?: string } & SshMutationExpectation
|
||||
): Promise<void> => ipcRenderer.invoke('fs:createFile', args),
|
||||
createDir: (
|
||||
args: { dirPath: string; connectionId?: string } & SshMutationExpectation
|
||||
): Promise<void> => ipcRenderer.invoke('fs:createDir', args),
|
||||
rename: (
|
||||
args: { oldPath: string; newPath: string; connectionId?: string } & SshMutationExpectation
|
||||
): Promise<void> => ipcRenderer.invoke('fs:rename', args),
|
||||
copy: (
|
||||
args: {
|
||||
sourcePath: string
|
||||
destinationPath: string
|
||||
connectionId?: string
|
||||
} & SshMutationExpectation
|
||||
): Promise<void> => ipcRenderer.invoke('fs:copy', args),
|
||||
deletePath: (
|
||||
args: {
|
||||
targetPath: string
|
||||
connectionId?: string
|
||||
recursive?: boolean
|
||||
} & SshMutationExpectation
|
||||
): Promise<void> => ipcRenderer.invoke('fs:deletePath', args),
|
||||
authorizeExternalPath: (args: { targetPath: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('fs:authorizeExternalPath', args),
|
||||
stat: (args: {
|
||||
filePath: string
|
||||
connectionId?: string
|
||||
}): Promise<{ size: number; isDirectory: boolean; mtime: number }> =>
|
||||
ipcRenderer.invoke('fs:stat', args),
|
||||
pathExists: (args: { filePath: string; connectionId?: string }): Promise<boolean> =>
|
||||
ipcRenderer.invoke('fs:pathExists', args),
|
||||
listFiles: (args: {
|
||||
rootPath: string
|
||||
connectionId?: string
|
||||
excludePaths?: string[]
|
||||
requestToken?: string
|
||||
maxResults?: number
|
||||
searchQuery?: string
|
||||
}): Promise<string[]> => ipcRenderer.invoke('fs:listFiles', args),
|
||||
cancelListFiles: (args: { requestToken: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('fs:cancelListFiles', args),
|
||||
search: (args: {
|
||||
query: string
|
||||
rootPath: string
|
||||
caseSensitive?: boolean
|
||||
wholeWord?: boolean
|
||||
useRegex?: boolean
|
||||
includePattern?: string
|
||||
excludePattern?: string
|
||||
maxResults?: number
|
||||
connectionId?: string
|
||||
}): Promise<SearchResult> => ipcRenderer.invoke('fs:search', args),
|
||||
importExternalPaths: (
|
||||
args: {
|
||||
sourcePaths: string[]
|
||||
destDir: string
|
||||
connectionId?: string
|
||||
ensureDir?: boolean
|
||||
} & SshMutationExpectation
|
||||
): Promise<{
|
||||
results: (
|
||||
| {
|
||||
sourcePath: string
|
||||
status: 'imported'
|
||||
destPath: string
|
||||
kind: 'file' | 'directory'
|
||||
renamed: boolean
|
||||
}
|
||||
| {
|
||||
sourcePath: string
|
||||
status: 'skipped'
|
||||
reason: 'missing' | 'symlink' | 'permission-denied' | 'unsupported'
|
||||
}
|
||||
| {
|
||||
sourcePath: string
|
||||
status: 'failed'
|
||||
reason: string
|
||||
}
|
||||
)[]
|
||||
}> => ipcRenderer.invoke('fs:importExternalPaths', args),
|
||||
stageExternalPathsForRuntimeUpload: (args: {
|
||||
sourcePaths: string[]
|
||||
}): Promise<{
|
||||
sources: (
|
||||
| {
|
||||
sourcePath: string
|
||||
status: 'staged'
|
||||
name: string
|
||||
kind: 'file' | 'directory'
|
||||
entries: (
|
||||
| { relativePath: string; kind: 'directory' }
|
||||
| { relativePath: string; kind: 'file'; contentBase64: string }
|
||||
)[]
|
||||
}
|
||||
| {
|
||||
sourcePath: string
|
||||
status: 'skipped'
|
||||
reason: 'missing' | 'symlink' | 'permission-denied' | 'unsupported'
|
||||
}
|
||||
| {
|
||||
sourcePath: string
|
||||
status: 'failed'
|
||||
reason: string
|
||||
}
|
||||
)[]
|
||||
}> => ipcRenderer.invoke('fs:stageExternalPathsForRuntimeUpload', args),
|
||||
resolveDroppedPathsForAgent: (
|
||||
args: {
|
||||
paths: string[]
|
||||
worktreePath: string
|
||||
connectionId?: string
|
||||
} & SshMutationExpectation
|
||||
): Promise<{
|
||||
resolvedPaths: string[]
|
||||
skipped: {
|
||||
sourcePath: string
|
||||
reason: 'missing' | 'symlink' | 'permission-denied' | 'unsupported'
|
||||
}[]
|
||||
failed: { sourcePath: string; reason: string }[]
|
||||
}> => ipcRenderer.invoke('fs:resolveDroppedPathsForAgent', args),
|
||||
watchWorktree: (args: { worktreePath: string; connectionId?: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('fs:watchWorktree', args),
|
||||
unwatchWorktree: (args: { worktreePath: string; connectionId?: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('fs:unwatchWorktree', args),
|
||||
onFsChanged: (callback: (payload: FsChangedPayload) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, payload: FsChangedPayload) =>
|
||||
callback(payload)
|
||||
ipcRenderer.on('fs:changed', listener)
|
||||
return () => ipcRenderer.removeListener('fs:changed', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { GitHubReactionContent } from '../../shared/github/comment-types'
|
||||
import type {
|
||||
GitHubPRRefreshCandidate,
|
||||
GitHubPRRefreshEvent,
|
||||
GitHubPRRefreshReason
|
||||
} from '../../shared/github/pull-request-refresh-types'
|
||||
import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types'
|
||||
import type { GitHubWorkItem, ListWorkItemsResult } from '../../shared/github/work-item-types'
|
||||
import type { GitHubCreateIssueResult } from '../../shared/issue-mutation-types'
|
||||
import type { TaskSourceContext } from '../../shared/task-source-context'
|
||||
|
||||
export const ghApiPart1 = {
|
||||
viewer: (): Promise<unknown> => ipcRenderer.invoke('gh:viewer'),
|
||||
repoSlug: (args: { repoPath: string; repoId?: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('gh:repoSlug', args),
|
||||
repoUpstream: (args: { repoPath: string; repoId?: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('gh:repoUpstream', args),
|
||||
prForBranch: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
branch: string
|
||||
linkedPRNumber?: number | null
|
||||
fallbackPRNumber?: number | null
|
||||
acceptMergedFallbackPR?: boolean
|
||||
currentHeadOid?: string | null
|
||||
}): Promise<unknown> => ipcRenderer.invoke('gh:prForBranch', args),
|
||||
refreshPRNow: (args: { candidate: GitHubPRRefreshCandidate }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('gh:refreshPRNow', args),
|
||||
enqueuePRRefresh: (args: {
|
||||
candidate: GitHubPRRefreshCandidate
|
||||
reason: GitHubPRRefreshReason
|
||||
priority?: number
|
||||
}): Promise<unknown> => ipcRenderer.invoke('gh:enqueuePRRefresh', args),
|
||||
reportVisiblePRRefreshCandidates: (args: {
|
||||
candidates: GitHubPRRefreshCandidate[]
|
||||
generation: number
|
||||
}): Promise<unknown> => ipcRenderer.invoke('gh:reportVisiblePRRefreshCandidates', args),
|
||||
onPRRefreshEvent: (callback: (event: GitHubPRRefreshEvent) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, event: GitHubPRRefreshEvent): void =>
|
||||
callback(event)
|
||||
ipcRenderer.on('gh:prRefreshEvent', listener)
|
||||
return () => ipcRenderer.removeListener('gh:prRefreshEvent', listener)
|
||||
},
|
||||
issue: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
number: number
|
||||
}): Promise<unknown> => ipcRenderer.invoke('gh:issue', args),
|
||||
workItem: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
number: number
|
||||
type?: 'issue' | 'pr'
|
||||
}): Promise<unknown> => ipcRenderer.invoke('gh:workItem', args),
|
||||
workItemByOwnerRepo: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
owner: string
|
||||
repo: string
|
||||
host?: string
|
||||
number: number
|
||||
type: 'issue' | 'pr'
|
||||
}): Promise<unknown> => ipcRenderer.invoke('gh:workItemByOwnerRepo', args),
|
||||
workItemDetails: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
number: number
|
||||
type?: 'issue' | 'pr'
|
||||
}): Promise<unknown> => ipcRenderer.invoke('gh:workItemDetails', args),
|
||||
notifyWorkItemMutated: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
type: 'issue' | 'pr'
|
||||
number: number
|
||||
}): Promise<boolean> => ipcRenderer.invoke('gh:notifyWorkItemMutated', args),
|
||||
prFileContents: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
path: string
|
||||
oldPath?: string
|
||||
status: string
|
||||
headSha: string
|
||||
baseSha: string
|
||||
}): Promise<unknown> => ipcRenderer.invoke('gh:prFileContents', args),
|
||||
listIssues: (args: { repoPath: string; repoId?: string; limit?: number }): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('gh:listIssues', args),
|
||||
createIssue: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
title: string
|
||||
body: string
|
||||
labels?: string[]
|
||||
assignees?: string[]
|
||||
}): Promise<GitHubCreateIssueResult> => ipcRenderer.invoke('gh:createIssue', args),
|
||||
countWorkItems: (args: { repoPath: string; repoId?: string; query?: string }): Promise<number> =>
|
||||
ipcRenderer.invoke('gh:countWorkItems', args),
|
||||
listWorkItems: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
limit?: number
|
||||
query?: string
|
||||
page?: number
|
||||
noCache?: boolean
|
||||
}): Promise<ListWorkItemsResult<Omit<GitHubWorkItem, 'repoId'>>> =>
|
||||
ipcRenderer.invoke('gh:listWorkItems', args),
|
||||
prChecks: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
headSha?: string
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
noCache?: boolean
|
||||
}): Promise<unknown[]> => ipcRenderer.invoke('gh:prChecks', args),
|
||||
prCheckDetails: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
checkRunId?: number
|
||||
workflowRunId?: number
|
||||
checkName?: string
|
||||
url?: string | null
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<unknown> => ipcRenderer.invoke('gh:prCheckDetails', args),
|
||||
rerunPRChecks: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
headSha?: string
|
||||
failedOnly?: boolean
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<{ ok: true; count: number } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('gh:rerunPRChecks', args),
|
||||
prComments: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
noCache?: boolean
|
||||
}): Promise<unknown[]> => ipcRenderer.invoke('gh:prComments', args),
|
||||
setPRCommentReaction: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
reactionSubjectId: string
|
||||
content: GitHubReactionContent
|
||||
reacted: boolean
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<boolean> => ipcRenderer.invoke('gh:setPRCommentReaction', args),
|
||||
resolveReviewThread: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
threadId: string
|
||||
resolve: boolean
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<boolean> => ipcRenderer.invoke('gh:resolveReviewThread', args),
|
||||
setPRFileViewed: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
pullRequestId: string
|
||||
path: string
|
||||
viewed: boolean
|
||||
}): Promise<boolean> => ipcRenderer.invoke('gh:setPRFileViewed', args),
|
||||
updatePRTitle: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
prNumber: number
|
||||
title: string
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<boolean> => ipcRenderer.invoke('gh:updatePRTitle', args),
|
||||
mergePR: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
method?: 'merge' | 'squash' | 'rebase'
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<{ ok: true } | { ok: false; error: string }> => ipcRenderer.invoke('gh:mergePR', args)
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { GitHubCommentResult } from '../../shared/github/comment-types'
|
||||
import type { GitHubAssignableUser, GitHubOwnerRepo } from '../../shared/github/pull-request-types'
|
||||
import type { GetRateLimitResult } from '../../shared/github/rate-limit-types'
|
||||
import type { GhAuthDiagnostic } from '../../shared/github/auth-types'
|
||||
import type { TaskSourceContext } from '../../shared/task-source-context'
|
||||
import type {
|
||||
GetProjectViewTableResult,
|
||||
GitHubProjectCommentMutationResult,
|
||||
GitHubProjectMutationResult,
|
||||
ListAccessibleProjectsResult,
|
||||
ListAssignableUsersBySlugResult,
|
||||
ListIssueTypesBySlugResult,
|
||||
ListLabelsBySlugResult,
|
||||
ListProjectViewsResult,
|
||||
ProjectWorkItemDetailsBySlugResult,
|
||||
ResolveProjectRefResult
|
||||
} from '../../shared/github/project-result-types'
|
||||
import type {
|
||||
AddIssueCommentBySlugArgs,
|
||||
ClearProjectItemFieldArgs,
|
||||
DeleteIssueCommentBySlugArgs,
|
||||
GetProjectViewTableArgs,
|
||||
ListAccessibleProjectsArgs,
|
||||
ListAssignableUsersBySlugArgs,
|
||||
ListIssueTypesBySlugArgs,
|
||||
ListLabelsBySlugArgs,
|
||||
ListProjectViewsArgs,
|
||||
ProjectWorkItemDetailsBySlugArgs,
|
||||
ResolveProjectRefArgs,
|
||||
UpdateIssueBySlugArgs,
|
||||
UpdateIssueCommentBySlugArgs,
|
||||
UpdateIssueTypeBySlugArgs,
|
||||
UpdatePullRequestBySlugArgs,
|
||||
UpdateProjectItemFieldArgs
|
||||
} from '../../shared/github/project-request-types'
|
||||
import type { AppStarSource } from '../../shared/gh-star-source'
|
||||
|
||||
export const ghApiPart2 = {
|
||||
setPRAutoMerge: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
enabled: boolean
|
||||
method?: 'merge' | 'squash' | 'rebase'
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<{ ok: true } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('gh:setPRAutoMerge', args),
|
||||
updatePRState: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
updates: { state: 'open' | 'closed' }
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<{ ok: true } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('gh:updatePRState', args),
|
||||
markPRReadyForReview: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<{ ok: true } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('gh:markPRReadyForReview', args),
|
||||
requestPRReviewers: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
reviewers: string[]
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<{ ok: true } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('gh:requestPRReviewers', args),
|
||||
removePRReviewers: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
reviewers: string[]
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<{ ok: true } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('gh:removePRReviewers', args),
|
||||
updateIssue: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
number: number
|
||||
updates: unknown
|
||||
}): Promise<{ ok: true } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('gh:updateIssue', args),
|
||||
addIssueComment: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
number: number
|
||||
body: string
|
||||
type?: 'issue' | 'pr'
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<GitHubCommentResult> => ipcRenderer.invoke('gh:addIssueComment', args),
|
||||
addPRReviewCommentReply: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
commentId: number
|
||||
body: string
|
||||
threadId?: string
|
||||
path?: string
|
||||
line?: number
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
}): Promise<GitHubCommentResult> => ipcRenderer.invoke('gh:addPRReviewCommentReply', args),
|
||||
addPRReviewComment: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
commitId: string
|
||||
path: string
|
||||
line: number
|
||||
startLine?: number
|
||||
body: string
|
||||
}): Promise<GitHubCommentResult> => ipcRenderer.invoke('gh:addPRReviewComment', args),
|
||||
listLabels: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
}): Promise<string[]> => ipcRenderer.invoke('gh:listLabels', args),
|
||||
listAssignableUsers: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
}): Promise<GitHubAssignableUser[]> => ipcRenderer.invoke('gh:listAssignableUsers', args),
|
||||
onWorkItemMutated: (
|
||||
callback: (payload: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
type: 'issue' | 'pr'
|
||||
number: number
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
payload: { repoPath: string; repoId?: string; type: 'issue' | 'pr'; number: number }
|
||||
): void => callback(payload)
|
||||
ipcRenderer.on('gh:workItemMutated', listener)
|
||||
return () => ipcRenderer.removeListener('gh:workItemMutated', listener)
|
||||
},
|
||||
checkOrcaStarred: (): Promise<boolean | null> => ipcRenderer.invoke('gh:checkOrcaStarred'),
|
||||
starOrca: (source: AppStarSource): Promise<boolean> => ipcRenderer.invoke('gh:starOrca', source),
|
||||
rateLimit: (args?: { force?: boolean }): Promise<GetRateLimitResult> =>
|
||||
ipcRenderer.invoke('gh:rateLimit', args),
|
||||
diagnoseAuth: (args?: { host?: string }): Promise<GhAuthDiagnostic> =>
|
||||
ipcRenderer.invoke('gh:diagnoseAuth', args),
|
||||
listAccessibleProjects: (
|
||||
args?: ListAccessibleProjectsArgs
|
||||
): Promise<ListAccessibleProjectsResult> => ipcRenderer.invoke('gh:listAccessibleProjects', args),
|
||||
resolveProjectRef: (args: ResolveProjectRefArgs): Promise<ResolveProjectRefResult> =>
|
||||
ipcRenderer.invoke('gh:resolveProjectRef', args),
|
||||
listProjectViews: (args: ListProjectViewsArgs): Promise<ListProjectViewsResult> =>
|
||||
ipcRenderer.invoke('gh:listProjectViews', args),
|
||||
getProjectViewTable: (args: GetProjectViewTableArgs): Promise<GetProjectViewTableResult> =>
|
||||
ipcRenderer.invoke('gh:getProjectViewTable', args),
|
||||
projectWorkItemDetailsBySlug: (
|
||||
args: ProjectWorkItemDetailsBySlugArgs
|
||||
): Promise<ProjectWorkItemDetailsBySlugResult> =>
|
||||
ipcRenderer.invoke('gh:projectWorkItemDetailsBySlug', args),
|
||||
updateProjectItemField: (
|
||||
args: UpdateProjectItemFieldArgs
|
||||
): Promise<GitHubProjectMutationResult> => ipcRenderer.invoke('gh:updateProjectItemField', args),
|
||||
clearProjectItemField: (args: ClearProjectItemFieldArgs): Promise<GitHubProjectMutationResult> =>
|
||||
ipcRenderer.invoke('gh:clearProjectItemField', args),
|
||||
updateIssueBySlug: (args: UpdateIssueBySlugArgs): Promise<GitHubProjectMutationResult> =>
|
||||
ipcRenderer.invoke('gh:updateIssueBySlug', args),
|
||||
updatePullRequestBySlug: (
|
||||
args: UpdatePullRequestBySlugArgs
|
||||
): Promise<GitHubProjectMutationResult> => ipcRenderer.invoke('gh:updatePullRequestBySlug', args),
|
||||
addIssueCommentBySlug: (
|
||||
args: AddIssueCommentBySlugArgs
|
||||
): Promise<GitHubProjectCommentMutationResult> =>
|
||||
ipcRenderer.invoke('gh:addIssueCommentBySlug', args),
|
||||
updateIssueCommentBySlug: (
|
||||
args: UpdateIssueCommentBySlugArgs
|
||||
): Promise<GitHubProjectMutationResult> =>
|
||||
ipcRenderer.invoke('gh:updateIssueCommentBySlug', args),
|
||||
deleteIssueCommentBySlug: (
|
||||
args: DeleteIssueCommentBySlugArgs
|
||||
): Promise<GitHubProjectMutationResult> =>
|
||||
ipcRenderer.invoke('gh:deleteIssueCommentBySlug', args),
|
||||
listLabelsBySlug: (args: ListLabelsBySlugArgs): Promise<ListLabelsBySlugResult> =>
|
||||
ipcRenderer.invoke('gh:listLabelsBySlug', args),
|
||||
listAssignableUsersBySlug: (
|
||||
args: ListAssignableUsersBySlugArgs
|
||||
): Promise<ListAssignableUsersBySlugResult> =>
|
||||
ipcRenderer.invoke('gh:listAssignableUsersBySlug', args),
|
||||
listIssueTypesBySlug: (args: ListIssueTypesBySlugArgs): Promise<ListIssueTypesBySlugResult> =>
|
||||
ipcRenderer.invoke('gh:listIssueTypesBySlug', args),
|
||||
updateIssueTypeBySlug: (args: UpdateIssueTypeBySlugArgs): Promise<GitHubProjectMutationResult> =>
|
||||
ipcRenderer.invoke('gh:updateIssueTypeBySlug', args)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { ghApiPart1 } from './gh-bridge-part-1'
|
||||
import { ghApiPart2 } from './gh-bridge-part-2'
|
||||
|
||||
export const ghApi = { ...ghApiPart1, ...ghApiPart2 }
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const gitBashApi = {
|
||||
isAvailable: (): Promise<boolean> => ipcRenderer.invoke('gitBash:isAvailable')
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { GitForkSyncExpectedUpstream, GitForkSyncResult } from '../../shared/git-fork-sync'
|
||||
import type { GitStagingArea, GitUpstreamStatus } from '../../shared/git-status-types'
|
||||
import type { GitPushTarget } from '../../shared/worktree/types'
|
||||
import type { GitHistoryOptions, GitHistoryResult } from '../../shared/git-history'
|
||||
|
||||
export const gitApi = {
|
||||
status: (args: {
|
||||
worktreePath: string
|
||||
connectionId?: string
|
||||
includeIgnored?: boolean
|
||||
bypassEffectiveUpstreamNegativeCache?: boolean
|
||||
reuseLineStats?: boolean
|
||||
branchLineTotalMergeBase?: string
|
||||
requestToken?: string
|
||||
}): Promise<unknown> => ipcRenderer.invoke('git:status', args),
|
||||
cancelStatus: (args: { requestToken: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('git:cancelStatus', args),
|
||||
setStatusUpstreamRefWatch: (args: {
|
||||
worktreeId: string
|
||||
worktreePath: string
|
||||
executionHostId: string
|
||||
connectionId?: string
|
||||
branch?: string
|
||||
upstreamName?: string
|
||||
}): Promise<void> => ipcRenderer.invoke('git:setStatusUpstreamRefWatch', args),
|
||||
submoduleStatus: (args: {
|
||||
worktreePath: string
|
||||
submodulePath: string
|
||||
connectionId?: string
|
||||
area?: GitStagingArea
|
||||
}): Promise<unknown> => ipcRenderer.invoke('git:submoduleStatus', args),
|
||||
checkIgnored: (args: {
|
||||
worktreePath: string
|
||||
paths: string[]
|
||||
connectionId?: string
|
||||
}): Promise<string[]> => ipcRenderer.invoke('git:checkIgnored', args),
|
||||
findHugeFoldersToIgnore: (args: { worktreePath: string }): Promise<string[]> =>
|
||||
ipcRenderer.invoke('git:findHugeFoldersToIgnore', args),
|
||||
appendGitignore: (args: { worktreePath: string; folderName: string }): Promise<boolean> =>
|
||||
ipcRenderer.invoke('git:appendGitignore', args),
|
||||
history: (
|
||||
args: { worktreePath: string; connectionId?: string } & GitHistoryOptions
|
||||
): Promise<GitHistoryResult> => ipcRenderer.invoke('git:history', args),
|
||||
conflictOperation: (args: { worktreePath: string; connectionId?: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('git:conflictOperation', args),
|
||||
abortMerge: (args: { worktreePath: string; connectionId?: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('git:abortMerge', args),
|
||||
abortRebase: (args: { worktreePath: string; connectionId?: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('git:abortRebase', args),
|
||||
diff: (args: {
|
||||
worktreePath: string
|
||||
filePath: string
|
||||
staged: boolean
|
||||
compareAgainstHead?: boolean
|
||||
connectionId?: string
|
||||
}): Promise<unknown> => ipcRenderer.invoke('git:diff', args),
|
||||
branchCompare: (args: {
|
||||
worktreePath: string
|
||||
baseRef: string
|
||||
connectionId?: string
|
||||
}): Promise<unknown> => ipcRenderer.invoke('git:branchCompare', args),
|
||||
commitCompare: (args: {
|
||||
worktreePath: string
|
||||
commitId: string
|
||||
connectionId?: string
|
||||
}): Promise<unknown> => ipcRenderer.invoke('git:commitCompare', args),
|
||||
upstreamStatus: (args: {
|
||||
worktreePath: string
|
||||
connectionId?: string
|
||||
pushTarget?: GitPushTarget
|
||||
}): Promise<GitUpstreamStatus> => ipcRenderer.invoke('git:upstreamStatus', args),
|
||||
fetch: (args: {
|
||||
worktreePath: string
|
||||
connectionId?: string
|
||||
pushTarget?: GitPushTarget
|
||||
}): Promise<void> => ipcRenderer.invoke('git:fetch', args),
|
||||
syncFork: (args: {
|
||||
worktreePath: string
|
||||
connectionId?: string
|
||||
expectedUpstream: GitForkSyncExpectedUpstream
|
||||
}): Promise<GitForkSyncResult> => ipcRenderer.invoke('git:syncFork', args),
|
||||
push: (args: {
|
||||
worktreePath: string
|
||||
publish?: boolean
|
||||
forceWithLease?: boolean
|
||||
connectionId?: string
|
||||
pushTarget?: unknown
|
||||
}): Promise<void> => ipcRenderer.invoke('git:push', args),
|
||||
pull: (args: {
|
||||
worktreePath: string
|
||||
connectionId?: string
|
||||
pushTarget?: GitPushTarget
|
||||
}): Promise<void> => ipcRenderer.invoke('git:pull', args),
|
||||
fastForward: (args: {
|
||||
worktreePath: string
|
||||
connectionId?: string
|
||||
pushTarget?: GitPushTarget
|
||||
}): Promise<void> => ipcRenderer.invoke('git:fastForward', args),
|
||||
rebaseFromBase: (args: {
|
||||
worktreePath: string
|
||||
baseRef: string
|
||||
connectionId?: string
|
||||
}): Promise<void> => ipcRenderer.invoke('git:rebaseFromBase', args),
|
||||
branchDiff: (args: {
|
||||
worktreePath: string
|
||||
compare: { baseRef: string; baseOid: string; headOid: string; mergeBase: string }
|
||||
filePath: string
|
||||
oldPath?: string
|
||||
connectionId?: string
|
||||
}): Promise<unknown> => ipcRenderer.invoke('git:branchDiff', args),
|
||||
commitDiff: (args: {
|
||||
worktreePath: string
|
||||
commitOid: string
|
||||
parentOid?: string | null
|
||||
filePath: string
|
||||
oldPath?: string
|
||||
connectionId?: string
|
||||
}): Promise<unknown> => ipcRenderer.invoke('git:commitDiff', args),
|
||||
commit: (args: {
|
||||
worktreePath: string
|
||||
message: string
|
||||
connectionId?: string
|
||||
}): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('git:commit', args),
|
||||
generateCommitMessage: (args: {
|
||||
worktreePath: string
|
||||
worktreeId?: string
|
||||
repoId?: string
|
||||
connectionId?: string
|
||||
sourceControlAiResolvedParams?: unknown
|
||||
sourceControlAi?: unknown
|
||||
agentCmdOverrides?: Record<string, string>
|
||||
}): Promise<unknown> => ipcRenderer.invoke('git:generateCommitMessage', args),
|
||||
discoverCommitMessageModels: (args: {
|
||||
agentId: string
|
||||
worktreePath?: string
|
||||
connectionId?: string
|
||||
}): Promise<unknown> => ipcRenderer.invoke('git:discoverCommitMessageModels', args),
|
||||
cancelGenerateCommitMessage: (args: {
|
||||
worktreePath: string
|
||||
connectionId?: string
|
||||
}): Promise<void> => ipcRenderer.invoke('git:cancelGenerateCommitMessage', args),
|
||||
generatePullRequestFields: (args: {
|
||||
worktreePath: string
|
||||
worktreeId?: string
|
||||
repoId?: string
|
||||
base: string
|
||||
title: string
|
||||
body: string
|
||||
draft: boolean
|
||||
provider?: unknown
|
||||
useTemplate?: boolean
|
||||
connectionId?: string
|
||||
sourceControlAiResolvedParams?: unknown
|
||||
sourceControlAi?: unknown
|
||||
agentCmdOverrides?: Record<string, string>
|
||||
}): Promise<unknown> => ipcRenderer.invoke('git:generatePullRequestFields', args),
|
||||
cancelGeneratePullRequestFields: (args: {
|
||||
worktreePath: string
|
||||
connectionId?: string
|
||||
}): Promise<void> => ipcRenderer.invoke('git:cancelGeneratePullRequestFields', args),
|
||||
stage: (args: { worktreePath: string; filePath: string; connectionId?: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('git:stage', args),
|
||||
bulkStage: (args: {
|
||||
worktreePath: string
|
||||
filePaths: string[]
|
||||
connectionId?: string
|
||||
}): Promise<void> => ipcRenderer.invoke('git:bulkStage', args),
|
||||
unstage: (args: {
|
||||
worktreePath: string
|
||||
filePath: string
|
||||
connectionId?: string
|
||||
}): Promise<void> => ipcRenderer.invoke('git:unstage', args),
|
||||
bulkUnstage: (args: {
|
||||
worktreePath: string
|
||||
filePaths: string[]
|
||||
connectionId?: string
|
||||
}): Promise<void> => ipcRenderer.invoke('git:bulkUnstage', args),
|
||||
discard: (args: {
|
||||
worktreePath: string
|
||||
filePath: string
|
||||
connectionId?: string
|
||||
}): Promise<void> => ipcRenderer.invoke('git:discard', args),
|
||||
bulkDiscard: (args: {
|
||||
worktreePath: string
|
||||
filePaths: string[]
|
||||
connectionId?: string
|
||||
}): Promise<void> => ipcRenderer.invoke('git:bulkDiscard', args),
|
||||
remoteFileUrl: (args: {
|
||||
worktreePath: string
|
||||
relativePath: string
|
||||
line: number
|
||||
connectionId?: string
|
||||
}): Promise<string | null> => ipcRenderer.invoke('git:remoteFileUrl', args),
|
||||
remoteCommitUrl: (args: {
|
||||
worktreePath: string
|
||||
sha: string
|
||||
connectionId?: string
|
||||
}): Promise<string | null> => ipcRenderer.invoke('git:remoteCommitUrl', args)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { glApi } from '../gitlab'
|
||||
|
||||
export const glApiBridge = glApi
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { GrokAccountStatus } from '../../shared/rate-limit-types'
|
||||
|
||||
export const grokAccountsApi = {
|
||||
getStatus: (): Promise<GrokAccountStatus> => ipcRenderer.invoke('grokAccounts:getStatus')
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { WorktreeSetupLaunch } from '../../shared/worktree/launch-types'
|
||||
import type { ExecutionHostId } from '../../shared/execution-host'
|
||||
|
||||
export const hooksApi = {
|
||||
check: (args: {
|
||||
repoId: string
|
||||
hostId?: ExecutionHostId
|
||||
}): Promise<{
|
||||
status?: 'ok' | 'error'
|
||||
hasHooks: boolean
|
||||
hooks: unknown
|
||||
mayNeedUpdate: boolean
|
||||
}> => ipcRenderer.invoke('hooks:check', args),
|
||||
|
||||
inspectSetupScriptImports: (args: {
|
||||
repoId: string
|
||||
hostId?: ExecutionHostId
|
||||
}): Promise<unknown[]> => ipcRenderer.invoke('hooks:inspectSetupScriptImports', args),
|
||||
|
||||
createIssueCommandRunner: (args: {
|
||||
repoId: string
|
||||
worktreePath: string
|
||||
command: string
|
||||
}): Promise<WorktreeSetupLaunch> => ipcRenderer.invoke('hooks:createIssueCommandRunner', args),
|
||||
|
||||
readIssueCommand: (args: {
|
||||
repoId: string
|
||||
hostId?: ExecutionHostId
|
||||
}): Promise<{
|
||||
status?: 'ok' | 'error'
|
||||
localContent: string | null
|
||||
sharedContent: string | null
|
||||
effectiveContent: string | null
|
||||
localFilePath: string
|
||||
source: 'local' | 'shared' | 'none'
|
||||
}> => ipcRenderer.invoke('hooks:readIssueCommand', args),
|
||||
|
||||
writeIssueCommand: (args: {
|
||||
repoId: string
|
||||
content: string
|
||||
hostId?: ExecutionHostId
|
||||
}): Promise<void> => ipcRenderer.invoke('hooks:writeIssueCommand', args)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { HostedReviewForBranchArgs } from '../../shared/hosted-review'
|
||||
|
||||
export const hostedReviewApi = {
|
||||
forBranch: (args: HostedReviewForBranchArgs): Promise<unknown> =>
|
||||
ipcRenderer.invoke('hostedReview:forBranch', args),
|
||||
getCreationEligibility: (args: unknown): Promise<unknown> =>
|
||||
ipcRenderer.invoke('hostedReview:getCreationEligibility', args),
|
||||
create: (args: unknown): Promise<unknown> => ipcRenderer.invoke('hostedReview:create', args),
|
||||
createStacked: (args: unknown): Promise<unknown> =>
|
||||
ipcRenderer.invoke('hostedReview:createStacked', args)
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { JiraProjectStatusOrder } from '../../shared/jira-types'
|
||||
|
||||
export const jiraApi = {
|
||||
connect: (args: {
|
||||
siteUrl: string
|
||||
email: string
|
||||
apiToken: string
|
||||
authType?: 'cloud' | 'server'
|
||||
}): Promise<{ ok: true; viewer: unknown } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('jira:connect', args),
|
||||
|
||||
disconnect: (args?: { siteId?: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('jira:disconnect', args),
|
||||
|
||||
selectSite: (args: { siteId: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('jira:selectSite', args),
|
||||
|
||||
status: (): Promise<unknown> => ipcRenderer.invoke('jira:status'),
|
||||
|
||||
readStatus: (): Promise<unknown> => ipcRenderer.invoke('jira:readStatus'),
|
||||
|
||||
testConnection: (args?: {
|
||||
siteId?: string
|
||||
}): Promise<{ ok: true; viewer: unknown } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('jira:testConnection', args),
|
||||
|
||||
searchIssues: (args: {
|
||||
jql: string
|
||||
limit?: number
|
||||
siteId?: string
|
||||
requestId?: string
|
||||
}): Promise<unknown[]> => ipcRenderer.invoke('jira:searchIssues', args),
|
||||
cancelSearchIssues: (args: { requestId: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('jira:cancelSearchIssues', args),
|
||||
|
||||
listIssues: (args?: {
|
||||
filter?: 'assigned' | 'reported' | 'all' | 'done'
|
||||
limit?: number
|
||||
siteId?: string
|
||||
}): Promise<unknown[]> => ipcRenderer.invoke('jira:listIssues', args),
|
||||
|
||||
getIssue: (args: { key: string; siteId?: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('jira:getIssue', args),
|
||||
|
||||
lookupIssueSummary: (args: {
|
||||
key: string
|
||||
siteId: string
|
||||
requestId?: string
|
||||
}): Promise<unknown> => ipcRenderer.invoke('jira:lookupIssueSummary', args),
|
||||
cancelIssueSummary: (args: { requestId: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('jira:cancelIssueSummary', args),
|
||||
|
||||
createIssue: (args: {
|
||||
siteId?: string
|
||||
projectId: string
|
||||
issueTypeId: string
|
||||
title: string
|
||||
description?: string
|
||||
customFields?: Record<string, unknown>
|
||||
}): Promise<{ ok: true; id: string; key: string; url: string } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('jira:createIssue', args),
|
||||
|
||||
updateIssue: (args: {
|
||||
key: string
|
||||
updates: unknown
|
||||
siteId?: string
|
||||
}): Promise<{ ok: true } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('jira:updateIssue', args),
|
||||
|
||||
addIssueComment: (args: {
|
||||
key: string
|
||||
body: string
|
||||
siteId?: string
|
||||
}): Promise<{ ok: true; id: string } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('jira:addIssueComment', args),
|
||||
|
||||
issueComments: (args: { key: string; siteId?: string }): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('jira:issueComments', args),
|
||||
|
||||
listProjects: (args?: { siteId?: string }): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('jira:listProjects', args),
|
||||
|
||||
listIssueTypes: (args: { projectIdOrKey: string; siteId?: string }): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('jira:listIssueTypes', args),
|
||||
|
||||
listCreateFields: (args: {
|
||||
projectIdOrKey: string
|
||||
issueTypeId: string
|
||||
siteId?: string
|
||||
}): Promise<unknown[]> => ipcRenderer.invoke('jira:listCreateFields', args),
|
||||
|
||||
listPriorities: (args?: { siteId?: string }): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('jira:listPriorities', args),
|
||||
|
||||
listAssignableUsers: (args: {
|
||||
key: string
|
||||
query?: string
|
||||
siteId?: string
|
||||
}): Promise<unknown[]> => ipcRenderer.invoke('jira:listAssignableUsers', args),
|
||||
searchUsers: (args?: { query?: string; siteId?: string }): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('jira:searchUsers', args),
|
||||
|
||||
listTransitions: (args: { key: string; siteId?: string }): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('jira:listTransitions', args),
|
||||
getProjectStatusOrder: (args: {
|
||||
projectKey: string
|
||||
siteId?: string
|
||||
}): Promise<JiraProjectStatusOrder> => ipcRenderer.invoke('jira:getProjectStatusOrder', args)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { KeybindingActionId, KeybindingFileSnapshot } from '../../shared/keybindings'
|
||||
|
||||
export const keybindingsApi = {
|
||||
get: (): Promise<KeybindingFileSnapshot> => ipcRenderer.invoke('keybindings:get'),
|
||||
ensureFile: (): Promise<KeybindingFileSnapshot> => ipcRenderer.invoke('keybindings:ensureFile'),
|
||||
setAction: (args: {
|
||||
actionId: KeybindingActionId
|
||||
bindings: string[] | null
|
||||
}): Promise<KeybindingFileSnapshot> => ipcRenderer.invoke('keybindings:setAction', args),
|
||||
reload: (): Promise<KeybindingFileSnapshot> => ipcRenderer.invoke('keybindings:reload'),
|
||||
openFile: (): Promise<KeybindingFileSnapshot> => ipcRenderer.invoke('keybindings:openFile'),
|
||||
revealFile: (): Promise<KeybindingFileSnapshot> => ipcRenderer.invoke('keybindings:revealFile'),
|
||||
onChanged: (callback: (snapshot: KeybindingFileSnapshot) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, snapshot: KeybindingFileSnapshot): void =>
|
||||
callback(snapshot)
|
||||
ipcRenderer.on('keybindings:changed', listener)
|
||||
return () => ipcRenderer.removeListener('keybindings:changed', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { LinearProjectDetail } from '../../shared/linear/project-types'
|
||||
|
||||
export const linearApi = {
|
||||
connect: (args: {
|
||||
apiKey: string
|
||||
}): Promise<{ ok: true; viewer: unknown } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('linear:connect', args),
|
||||
|
||||
disconnect: (args?: { workspaceId?: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('linear:disconnect', args),
|
||||
|
||||
selectWorkspace: (args: { workspaceId: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('linear:selectWorkspace', args),
|
||||
|
||||
status: (): Promise<unknown> => ipcRenderer.invoke('linear:status'),
|
||||
|
||||
testConnection: (args?: {
|
||||
workspaceId?: string
|
||||
}): Promise<{ ok: true; viewer: unknown } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('linear:testConnection', args),
|
||||
|
||||
searchIssues: (args: {
|
||||
query: string
|
||||
limit?: number
|
||||
workspaceId?: string
|
||||
}): Promise<unknown[]> => ipcRenderer.invoke('linear:searchIssues', args),
|
||||
|
||||
listIssues: (args?: {
|
||||
filter?: 'assigned' | 'created' | 'all' | 'completed'
|
||||
limit?: number
|
||||
workspaceId?: string
|
||||
attributeFilter?: unknown
|
||||
}): Promise<unknown> => ipcRenderer.invoke('linear:listIssues', args),
|
||||
|
||||
createIssue: (args: {
|
||||
teamId: string
|
||||
title: string
|
||||
description?: string
|
||||
workspaceId?: string
|
||||
parentIssueId?: string
|
||||
projectId?: string | null
|
||||
stateId?: string
|
||||
priority?: number
|
||||
assigneeId?: string | null
|
||||
labelIds?: string[]
|
||||
}): Promise<
|
||||
| { ok: true; id: string; identifier: string; title: string; url: string }
|
||||
| { ok: false; error: string }
|
||||
> => ipcRenderer.invoke('linear:createIssue', args),
|
||||
|
||||
getIssue: (args: { id: string; workspaceId?: string }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('linear:getIssue', args),
|
||||
|
||||
updateIssue: (args: {
|
||||
id: string
|
||||
updates: unknown
|
||||
workspaceId?: string
|
||||
}): Promise<{ ok: true } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('linear:updateIssue', args),
|
||||
|
||||
addIssueComment: (args: {
|
||||
issueId: string
|
||||
body: string
|
||||
workspaceId?: string
|
||||
}): Promise<{ ok: true; id: string } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('linear:addIssueComment', args),
|
||||
|
||||
issueComments: (args: { issueId: string; workspaceId?: string }): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('linear:issueComments', args),
|
||||
|
||||
listTeams: (args?: { workspaceId?: string }): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('linear:listTeams', args),
|
||||
|
||||
listProjects: (args?: {
|
||||
query?: string
|
||||
limit?: number
|
||||
workspaceId?: string
|
||||
force?: boolean
|
||||
}): Promise<unknown> => ipcRenderer.invoke('linear:listProjects', args),
|
||||
|
||||
createProject: (args: {
|
||||
name: string
|
||||
description?: string
|
||||
content?: string
|
||||
teamIds: string[]
|
||||
workspaceId?: string
|
||||
leadId?: string | null
|
||||
memberIds?: string[]
|
||||
labelIds?: string[]
|
||||
priority?: number
|
||||
startDate?: string
|
||||
targetDate?: string
|
||||
}): Promise<{ ok: true; project: LinearProjectDetail } | { ok: false; error: string }> =>
|
||||
ipcRenderer.invoke('linear:createProject', args),
|
||||
|
||||
getProject: (args: { id: string; workspaceId: string; force?: boolean }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('linear:getProject', args),
|
||||
|
||||
listProjectIssues: (args: {
|
||||
projectId: string
|
||||
limit?: number
|
||||
workspaceId: string
|
||||
force?: boolean
|
||||
}): Promise<unknown> => ipcRenderer.invoke('linear:listProjectIssues', args),
|
||||
|
||||
listCustomViews: (args: {
|
||||
model: string
|
||||
limit?: number
|
||||
workspaceId?: string
|
||||
force?: boolean
|
||||
}): Promise<unknown> => ipcRenderer.invoke('linear:listCustomViews', args),
|
||||
|
||||
getCustomView: (args: {
|
||||
viewId: string
|
||||
model: string
|
||||
workspaceId: string
|
||||
force?: boolean
|
||||
}): Promise<unknown> => ipcRenderer.invoke('linear:getCustomView', args),
|
||||
|
||||
listCustomViewIssues: (args: {
|
||||
viewId: string
|
||||
limit?: number
|
||||
workspaceId: string
|
||||
force?: boolean
|
||||
}): Promise<unknown> => ipcRenderer.invoke('linear:listCustomViewIssues', args),
|
||||
|
||||
listCustomViewProjects: (args: {
|
||||
viewId: string
|
||||
limit?: number
|
||||
workspaceId: string
|
||||
force?: boolean
|
||||
}): Promise<unknown> => ipcRenderer.invoke('linear:listCustomViewProjects', args),
|
||||
|
||||
teamStates: (args: { teamId: string; workspaceId?: string }): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('linear:teamStates', args),
|
||||
|
||||
teamLabels: (args: { teamId: string; workspaceId?: string }): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('linear:teamLabels', args),
|
||||
|
||||
teamMembers: (args: { teamId: string; workspaceId?: string }): Promise<unknown[]> =>
|
||||
ipcRenderer.invoke('linear:teamMembers', args)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
LocalhostWorktreeLabelResult,
|
||||
LocalhostWorktreeLabelRoute
|
||||
} from '../../shared/localhost-worktree-labels'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const localhostWorktreeLabelsApi = {
|
||||
register: (args: LocalhostWorktreeLabelRoute): Promise<LocalhostWorktreeLabelResult> =>
|
||||
ipcRenderer.invoke('localhostWorktreeLabels:register', args)
|
||||
} satisfies PreloadApi['localhostWorktreeLabels']
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const macosTccPromptsApi = {
|
||||
onThreshold: (callback: (payload: unknown) => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, payload: unknown): void =>
|
||||
callback(payload)
|
||||
ipcRenderer.on('macosTccPrompts:threshold', listener)
|
||||
return () => ipcRenderer.removeListener('macosTccPrompts:threshold', listener)
|
||||
},
|
||||
consumePending: (): Promise<{ claimId: number; promptCount: number } | null> =>
|
||||
ipcRenderer.invoke('macosTccPrompts:consumePending'),
|
||||
acknowledgePending: (claimId: number): Promise<void> =>
|
||||
ipcRenderer.invoke('macosTccPrompts:acknowledgePending', claimId),
|
||||
releasePending: (claimId: number): Promise<void> =>
|
||||
ipcRenderer.invoke('macosTccPrompts:releasePending', claimId),
|
||||
dismiss: (): Promise<void> => ipcRenderer.invoke('macosTccPrompts:dismiss')
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { MemorySnapshot } from '../../shared/process-stats-types'
|
||||
|
||||
export const memoryApi = {
|
||||
getSnapshot: (): Promise<MemorySnapshot> => ipcRenderer.invoke('memory:getSnapshot')
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const minimaxCredentialsApi = {
|
||||
getStatus: (): Promise<{ configured: boolean }> =>
|
||||
ipcRenderer.invoke('minimaxCredentials:getStatus'),
|
||||
saveCookie: (cookie: string): Promise<{ configured: boolean }> =>
|
||||
ipcRenderer.invoke('minimaxCredentials:saveCookie', cookie),
|
||||
clearCookie: (): Promise<{ configured: boolean }> =>
|
||||
ipcRenderer.invoke('minimaxCredentials:clearCookie')
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { MobileRelayStatus } from '../../shared/mobile-relay-status'
|
||||
import type { MobilePairingConnectionMode } from '../../shared/mobile-pairing-connection-mode'
|
||||
import type { RuntimePairingReach } from '../../shared/runtime-pairing-reach'
|
||||
import type { MobileRelayMintFailure } from '../../shared/mobile-relay-mint-failure'
|
||||
|
||||
export const mobileApi = {
|
||||
listNetworkInterfaces: (): Promise<{
|
||||
interfaces: { name: string; address: string; hasDefaultRoute?: boolean }[]
|
||||
}> => ipcRenderer.invoke('mobile:listNetworkInterfaces'),
|
||||
|
||||
getPairingQR: (args?: {
|
||||
address?: string
|
||||
connectionMode?: MobilePairingConnectionMode
|
||||
rotate?: boolean
|
||||
}): Promise<
|
||||
| {
|
||||
available: false
|
||||
reason?: string
|
||||
guidance?: string
|
||||
relayFailure?: MobileRelayMintFailure
|
||||
}
|
||||
| {
|
||||
available: true
|
||||
qrDataUrl: string | null
|
||||
/** Natural bitmap width and height in pixels. */
|
||||
qrSize: number | null
|
||||
qrError?: 'encoding_failed'
|
||||
pairingUrl: string
|
||||
/** Null when no direct address was advertised — the QR pairs over Relay alone. */
|
||||
endpoint: string | null
|
||||
deviceId: string
|
||||
connectionMode: MobilePairingConnectionMode
|
||||
}
|
||||
> => ipcRenderer.invoke('mobile:getPairingQR', args),
|
||||
|
||||
getWindowsFirewallStatus: (args?: { address?: string }) =>
|
||||
ipcRenderer.invoke('mobile:getWindowsFirewallStatus', args),
|
||||
|
||||
repairWindowsFirewall: () => ipcRenderer.invoke('mobile:repairWindowsFirewall'),
|
||||
|
||||
openWindowsNetworkSettings: () => ipcRenderer.invoke('mobile:openWindowsNetworkSettings'),
|
||||
|
||||
getRuntimePairingUrl: (args?: {
|
||||
address?: string
|
||||
rotate?: boolean
|
||||
// Why: the widen is one-way and host-wide, so main must gate it on the reach the user picked, not
|
||||
// on how the typed address happens to look (a Custom loopback may front an SSH tunnel).
|
||||
reach?: RuntimePairingReach
|
||||
}): Promise<
|
||||
| { available: false; reason?: 'network_exposure_failed'; guidance?: string }
|
||||
| {
|
||||
available: true
|
||||
pairingUrl: string
|
||||
webClientUrl: string | null
|
||||
endpoint: string
|
||||
deviceId: string
|
||||
}
|
||||
> => ipcRenderer.invoke('mobile:getRuntimePairingUrl', args),
|
||||
|
||||
listDevices: (): Promise<{
|
||||
devices: { deviceId: string; name: string; pairedAt: number; lastSeenAt: number }[]
|
||||
}> => ipcRenderer.invoke('mobile:listDevices'),
|
||||
|
||||
revokeDevice: (args: { deviceId: string }): Promise<{ revoked: boolean }> =>
|
||||
ipcRenderer.invoke('mobile:revokeDevice', args),
|
||||
|
||||
listRuntimeAccessGrants: () => ipcRenderer.invoke('mobile:listRuntimeAccessGrants'),
|
||||
|
||||
revokeRuntimeAccess: (args: { deviceId: string }): Promise<{ revoked: boolean }> =>
|
||||
ipcRenderer.invoke('mobile:revokeRuntimeAccess', args),
|
||||
|
||||
isWebSocketReady: (): Promise<{ ready: boolean; endpoint: string | null }> =>
|
||||
ipcRenderer.invoke('mobile:isWebSocketReady'),
|
||||
|
||||
getRelayStatus: (): Promise<{ status: MobileRelayStatus }> =>
|
||||
ipcRenderer.invoke('mobile:getRelayStatus'),
|
||||
|
||||
onRelayStatusChanged: (callback: (status: MobileRelayStatus) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, status: MobileRelayStatus) =>
|
||||
callback(status)
|
||||
ipcRenderer.on('mobile:relayStatusChanged', listener)
|
||||
return () => ipcRenderer.removeListener('mobile:relayStatusChanged', listener)
|
||||
},
|
||||
|
||||
consumePendingUnpairedDeviceAuthFailure: (): Promise<boolean> =>
|
||||
ipcRenderer.invoke('mobile:consumePendingUnpairedDeviceAuthFailure'),
|
||||
|
||||
/** Fires (throttled, once per session) when an unpaired phone repeatedly fails direct-transport auth. */
|
||||
onUnpairedDeviceAuthFailure: (callback: () => void): (() => void) => {
|
||||
const listener = () => callback()
|
||||
ipcRenderer.on('mobile:unpairedDeviceAuthFailure', listener)
|
||||
return () => ipcRenderer.removeListener('mobile:unpairedDeviceAuthFailure', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
NativeChatAppendedPayload,
|
||||
NativeChatReadSessionResult,
|
||||
NativeChatSubscriptionFrame
|
||||
} from '../api-types'
|
||||
import type { AgentType } from '../../shared/native-chat-types'
|
||||
|
||||
export const nativeChatApi = {
|
||||
readSession: (
|
||||
agent: AgentType,
|
||||
sessionId: string,
|
||||
limit?: number,
|
||||
transcriptPath?: string
|
||||
): Promise<NativeChatReadSessionResult> =>
|
||||
ipcRenderer.invoke('nativeChat:readSession', { agent, sessionId, limit, transcriptPath }),
|
||||
/** Start live tailing; onAppended fires with only newly-appended messages. Returns an unsubscribe fn that closes the watcher. */
|
||||
subscribe: (
|
||||
args: {
|
||||
subscriptionId: string
|
||||
agent: AgentType
|
||||
sessionId: string
|
||||
transcriptPath?: string
|
||||
limit?: number
|
||||
},
|
||||
onFrame: (frame: NativeChatSubscriptionFrame) => void
|
||||
): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, payload: NativeChatAppendedPayload) => {
|
||||
if (payload.subscriptionId === args.subscriptionId) {
|
||||
onFrame(payload.frame)
|
||||
}
|
||||
}
|
||||
ipcRenderer.on('nativeChat:appended', listener)
|
||||
ipcRenderer.send('nativeChat:subscribe', args)
|
||||
return () => {
|
||||
ipcRenderer.removeListener('nativeChat:appended', listener)
|
||||
ipcRenderer.send('nativeChat:unsubscribe', { subscriptionId: args.subscriptionId })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const notebookApi = {
|
||||
runPythonCell: (args: {
|
||||
filePath: string
|
||||
code: string
|
||||
preamble?: string
|
||||
connectionId?: string | null
|
||||
}): Promise<{ stdout: string; stderr: string; exitCode: number | null; error?: string }> =>
|
||||
ipcRenderer.invoke('notebook:runPythonCell', args)
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
NotificationDeliveryProbeResult,
|
||||
NotificationDismissResult,
|
||||
NotificationDispatchResult,
|
||||
NotificationPermissionStatusResult,
|
||||
NotificationSoundDataResult,
|
||||
NotificationSoundPathResult,
|
||||
NotificationSoundResult
|
||||
} from '../../shared/notification-settings-types'
|
||||
|
||||
// Why: cache one shared Audio + blob URL per sound path so notifications do not re-read large files.
|
||||
let cachedNotificationSound: {
|
||||
path: string
|
||||
blobUrl: string
|
||||
audio: HTMLAudioElement
|
||||
} | null = null
|
||||
let isNotificationSoundPlaying = false
|
||||
// Why: audio.play() can reject before ended/error fires; cleanup prevents leaked listeners.
|
||||
let cleanupNotificationSoundPlayback: (() => void) | null = null
|
||||
|
||||
function clearNotificationSoundPlaybackState(): void {
|
||||
cleanupNotificationSoundPlayback?.()
|
||||
cleanupNotificationSoundPlayback = null
|
||||
isNotificationSoundPlaying = false
|
||||
}
|
||||
|
||||
function disposeCachedNotificationSound(): void {
|
||||
if (cachedNotificationSound) {
|
||||
clearNotificationSoundPlaybackState()
|
||||
cachedNotificationSound.audio.pause()
|
||||
cachedNotificationSound.audio.src = ''
|
||||
URL.revokeObjectURL(cachedNotificationSound.blobUrl)
|
||||
cachedNotificationSound = null
|
||||
}
|
||||
}
|
||||
|
||||
export const notificationsApi = {
|
||||
dispatch: (args: Record<string, unknown>): Promise<NotificationDispatchResult> =>
|
||||
ipcRenderer.invoke('notifications:dispatch', args),
|
||||
dismiss: (ids: string[]): Promise<NotificationDismissResult> =>
|
||||
ipcRenderer.invoke('notifications:dismiss', ids),
|
||||
openSystemSettings: (): Promise<void> => ipcRenderer.invoke('notifications:openSystemSettings'),
|
||||
getPermissionStatus: (): Promise<NotificationPermissionStatusResult> =>
|
||||
ipcRenderer.invoke('notifications:getPermissionStatus'),
|
||||
probeDelivery: (args?: { force?: boolean }): Promise<NotificationDeliveryProbeResult> =>
|
||||
ipcRenderer.invoke('notifications:probeDelivery', args),
|
||||
playSound: async (options?: {
|
||||
force?: boolean
|
||||
volume?: number
|
||||
}): Promise<NotificationSoundResult> => {
|
||||
try {
|
||||
// Why: drop replays while still ringing; the test button passes force to always confirm.
|
||||
if (!options?.force && isNotificationSoundPlaying) {
|
||||
return { played: false, reason: 'deduped' }
|
||||
}
|
||||
|
||||
const resolved = (await ipcRenderer.invoke(
|
||||
'notifications:resolveSoundPath'
|
||||
)) as NotificationSoundPathResult
|
||||
if (!resolved.ok) {
|
||||
if (cachedNotificationSound) {
|
||||
disposeCachedNotificationSound()
|
||||
}
|
||||
return { played: false, reason: resolved.reason }
|
||||
}
|
||||
|
||||
let entry = cachedNotificationSound
|
||||
if (!entry || entry.path !== resolved.path) {
|
||||
const sound = (await ipcRenderer.invoke(
|
||||
'notifications:loadSound'
|
||||
)) as NotificationSoundDataResult
|
||||
if (!sound.ok) {
|
||||
disposeCachedNotificationSound()
|
||||
return { played: false, reason: sound.reason }
|
||||
}
|
||||
const arrayBuffer = new ArrayBuffer(sound.data.byteLength)
|
||||
new Uint8Array(arrayBuffer).set(sound.data)
|
||||
const blob = new Blob([arrayBuffer], { type: sound.mimeType })
|
||||
disposeCachedNotificationSound()
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
entry = { path: sound.path, blobUrl, audio: new Audio(blobUrl) }
|
||||
cachedNotificationSound = entry
|
||||
}
|
||||
|
||||
const audio = entry.audio
|
||||
// Why: restart from zero on each play so bursts replay instead of stacking copies (GNOME canberra / VS Code signal service).
|
||||
audio.currentTime = 0
|
||||
if (typeof options?.volume === 'number' && Number.isFinite(options.volume)) {
|
||||
audio.volume = Math.min(1, Math.max(0, options.volume / 100))
|
||||
}
|
||||
isNotificationSoundPlaying = true
|
||||
cleanupNotificationSoundPlayback?.()
|
||||
const release = (): void => {
|
||||
cleanup()
|
||||
if (cleanupNotificationSoundPlayback === cleanup) {
|
||||
cleanupNotificationSoundPlayback = null
|
||||
}
|
||||
isNotificationSoundPlaying = false
|
||||
}
|
||||
const cleanup = (): void => {
|
||||
audio.removeEventListener('ended', release)
|
||||
audio.removeEventListener('error', release)
|
||||
}
|
||||
cleanupNotificationSoundPlayback = cleanup
|
||||
audio.addEventListener('ended', release)
|
||||
audio.addEventListener('error', release)
|
||||
try {
|
||||
await audio.play()
|
||||
} catch {
|
||||
release()
|
||||
return { played: false, reason: 'playback-failed' }
|
||||
}
|
||||
return { played: true }
|
||||
} catch {
|
||||
clearNotificationSoundPlaybackState()
|
||||
return { played: false, reason: 'playback-failed' }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { OnboardingState } from '../../shared/onboarding-state-types'
|
||||
|
||||
export const onboardingApi = {
|
||||
get: (): Promise<OnboardingState> => ipcRenderer.invoke('onboarding:get'),
|
||||
update: (
|
||||
updates: Partial<Omit<OnboardingState, 'checklist'>> & {
|
||||
checklist?: Partial<OnboardingState['checklist']>
|
||||
}
|
||||
): Promise<OnboardingState> => ipcRenderer.invoke('onboarding:update', updates)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { createUsageProviderApi } from '../usage-provider-api'
|
||||
|
||||
export const openCodeUsageApi = createUsageProviderApi(ipcRenderer, 'openCodeUsage')
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const orcaProfilesApi = {
|
||||
list: () => ipcRenderer.invoke('orcaProfiles:list'),
|
||||
authStatus: () => ipcRenderer.invoke('orcaProfiles:authStatus'),
|
||||
createLocal: (args) => ipcRenderer.invoke('orcaProfiles:createLocal', args),
|
||||
createCloudLinked: (args) => ipcRenderer.invoke('orcaProfiles:createCloudLinked', args),
|
||||
switchProfile: (args) => ipcRenderer.invoke('orcaProfiles:switch', args),
|
||||
transferProject: (args) => ipcRenderer.invoke('orcaProfiles:transferProject', args),
|
||||
findProjectProfiles: (args) => ipcRenderer.invoke('orcaProfiles:findProjectProfiles', args),
|
||||
connectCurrent: () => ipcRenderer.invoke('orcaProfiles:connectCurrent'),
|
||||
refreshAuth: () => ipcRenderer.invoke('orcaProfiles:refreshAuth'),
|
||||
signOutCurrent: () => ipcRenderer.invoke('orcaProfiles:signOutCurrent'),
|
||||
selectOrg: (args) => ipcRenderer.invoke('orcaProfiles:selectOrg', args),
|
||||
orgMembersList: (args) => ipcRenderer.invoke('orcaProfiles:orgMembersList', args),
|
||||
orgMemberInvite: (args) => ipcRenderer.invoke('orcaProfiles:orgMemberInvite', args),
|
||||
orgInviteRevoke: (args) => ipcRenderer.invoke('orcaProfiles:orgInviteRevoke', args),
|
||||
orgMemberChangeRole: (args) => ipcRenderer.invoke('orcaProfiles:orgMemberChangeRole', args),
|
||||
orgMemberRemove: (args) => ipcRenderer.invoke('orcaProfiles:orgMemberRemove', args)
|
||||
} satisfies PreloadApi['orcaProfiles']
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { CustomPet } from '../../shared/pet-types'
|
||||
|
||||
export const petApi = {
|
||||
import: (): Promise<CustomPet | null> => ipcRenderer.invoke('pet:import'),
|
||||
importPetBundle: (): Promise<CustomPet | null> => ipcRenderer.invoke('pet:importPetBundle'),
|
||||
read: (id: string, fileName: string, kind?: 'image' | 'bundle'): Promise<ArrayBuffer | null> =>
|
||||
ipcRenderer.invoke('pet:read', id, fileName, kind),
|
||||
delete: (id: string, fileName: string, kind?: 'image' | 'bundle'): Promise<void> =>
|
||||
ipcRenderer.invoke('pet:delete', id, fileName, kind)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { getLinuxDisplayServer } from '../preload-runtime-support'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const platformApi = {
|
||||
get: () => ({
|
||||
platform: process.platform,
|
||||
// Why: sandboxed preload cannot require node:os; Electron exposes the OS
|
||||
// version on process.getSystemVersion when available.
|
||||
osRelease:
|
||||
(process as NodeJS.Process & { getSystemVersion?: () => string }).getSystemVersion?.() ?? '',
|
||||
arch: process.arch,
|
||||
// Why: these identify the default shell without probing user config files.
|
||||
// process.env is available in the sandboxed preload; node:os is not.
|
||||
shell: process.env.SHELL?.trim() || process.env.ComSpec?.trim() || '',
|
||||
displayServer: getLinuxDisplayServer()
|
||||
})
|
||||
} satisfies PreloadApi['platform']
|
||||
@@ -0,0 +1,63 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
PluginPanelActionOutcome,
|
||||
PluginPanelEntry
|
||||
} from '../../shared/plugins/plugin-panel-bridge'
|
||||
import type { PluginConsentRequest } from '../../shared/plugins/plugin-consent-request'
|
||||
import type { PluginChangeEvent } from '../../shared/plugins/plugin-change-event'
|
||||
import type {
|
||||
PluginHostInstallResult,
|
||||
PluginHostInstallSource,
|
||||
PluginHostListEntry,
|
||||
PluginHostLogLine
|
||||
} from '../api-types'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const pluginsApi = {
|
||||
list: (): Promise<PluginHostListEntry[]> => ipcRenderer.invoke('plugins:list'),
|
||||
listLanguagePacks: () => ipcRenderer.invoke('plugins:listLanguagePacks'),
|
||||
consent: (args: PluginConsentRequest): Promise<PluginHostListEntry[]> =>
|
||||
ipcRenderer.invoke('plugins:consent', args),
|
||||
setEnabled: (args: { pluginKey: string; enabled: boolean }): Promise<PluginHostListEntry[]> =>
|
||||
ipcRenderer.invoke('plugins:setEnabled', args),
|
||||
readPanelEntry: (args: {
|
||||
pluginKey: string
|
||||
panelId: string
|
||||
}): Promise<PluginPanelEntry | null> => ipcRenderer.invoke('plugins:readPanelEntry', args),
|
||||
invokeCommand: (args: {
|
||||
pluginKey: string
|
||||
commandId: string
|
||||
args?: unknown
|
||||
}): Promise<unknown> => ipcRenderer.invoke('plugins:invokeCommand', args),
|
||||
panelAction: (args: {
|
||||
sessionToken: string
|
||||
action: string
|
||||
params?: unknown
|
||||
}): Promise<PluginPanelActionOutcome> => ipcRenderer.invoke('plugins:panelAction', args),
|
||||
install: (source: PluginHostInstallSource): Promise<PluginHostInstallResult> =>
|
||||
ipcRenderer.invoke('plugins:install', source),
|
||||
listMarketplaces: () => ipcRenderer.invoke('plugins:listMarketplaces'),
|
||||
addMarketplace: (source) => ipcRenderer.invoke('plugins:addMarketplace', source),
|
||||
removeMarketplace: (args) => ipcRenderer.invoke('plugins:removeMarketplace', args),
|
||||
refreshMarketplaces: (args = {}) => ipcRenderer.invoke('plugins:refreshMarketplaces', args),
|
||||
listMarketplacePlugins: () => ipcRenderer.invoke('plugins:listMarketplacePlugins'),
|
||||
previewMarketplacePlugin: (args) => ipcRenderer.invoke('plugins:previewMarketplacePlugin', args),
|
||||
installMarketplacePlugin: (preview) =>
|
||||
ipcRenderer.invoke('plugins:installMarketplacePlugin', preview),
|
||||
previewMarketplaceUpdate: (args) => ipcRenderer.invoke('plugins:previewMarketplaceUpdate', args),
|
||||
rollbackMarketplacePlugin: (args) =>
|
||||
ipcRenderer.invoke('plugins:rollbackMarketplacePlugin', args),
|
||||
remove: (args: { pluginKey: string }): Promise<PluginHostListEntry[]> =>
|
||||
ipcRenderer.invoke('plugins:remove', args),
|
||||
getLogs: (args: { pluginKey: string }): Promise<PluginHostLogLine[]> =>
|
||||
ipcRenderer.invoke('plugins:getLogs', args),
|
||||
refresh: (): Promise<PluginHostListEntry[]> => ipcRenderer.invoke('plugins:refresh'),
|
||||
onChanged: (callback): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, change: PluginChangeEvent): void =>
|
||||
callback(change)
|
||||
ipcRenderer.on('plugins:changed', listener)
|
||||
return () => {
|
||||
ipcRenderer.removeListener('plugins:changed', listener)
|
||||
}
|
||||
}
|
||||
} satisfies PreloadApi['plugins']
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { PreflightRuntimeContext, RefreshAgentsResult } from '../api-types'
|
||||
|
||||
export const preflightApi = {
|
||||
check: (args?: {
|
||||
force?: boolean
|
||||
}): Promise<{
|
||||
git: { installed: boolean }
|
||||
gh: { installed: boolean; authenticated: boolean }
|
||||
glab?: { installed: boolean; authenticated: boolean }
|
||||
bitbucket?: { configured: boolean; authenticated: boolean; account: string | null }
|
||||
azureDevOps?: {
|
||||
configured: boolean
|
||||
authenticated: boolean
|
||||
account: string | null
|
||||
baseUrl: string | null
|
||||
tokenConfigured: boolean
|
||||
}
|
||||
gitea?: {
|
||||
configured: boolean
|
||||
authenticated: boolean
|
||||
account: string | null
|
||||
baseUrl: string | null
|
||||
tokenConfigured: boolean
|
||||
}
|
||||
linear: { connected: boolean }
|
||||
}> => ipcRenderer.invoke('preflight:check', args),
|
||||
detectAgents: (args?: PreflightRuntimeContext): Promise<string[]> =>
|
||||
ipcRenderer.invoke('preflight:detectAgents', args),
|
||||
refreshAgents: (args?: PreflightRuntimeContext): Promise<RefreshAgentsResult> =>
|
||||
ipcRenderer.invoke('preflight:refreshAgents', args),
|
||||
detectRemoteAgents: (args: { connectionId: string }): Promise<string[]> =>
|
||||
ipcRenderer.invoke('preflight:detectRemoteAgents', args),
|
||||
detectRemoteWindowsTerminalCapabilities: (args: {
|
||||
connectionId: string
|
||||
}): Promise<{
|
||||
wslAvailable: boolean
|
||||
wslDistros: string[]
|
||||
pwshAvailable: boolean
|
||||
gitBashAvailable: boolean
|
||||
hostPlatform: NodeJS.Platform | null
|
||||
}> => ipcRenderer.invoke('preflight:detectRemoteWindowsTerminalCapabilities', args)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { NestedRepoScanResult } from '../../shared/project-group-types'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const projectGroupsApi = {
|
||||
list: () => ipcRenderer.invoke('projectGroups:list'),
|
||||
create: (args) => ipcRenderer.invoke('projectGroups:create', args),
|
||||
update: (args) => ipcRenderer.invoke('projectGroups:update', args),
|
||||
delete: (args) => ipcRenderer.invoke('projectGroups:delete', args),
|
||||
moveProject: (args) => ipcRenderer.invoke('projectGroups:moveProject', args),
|
||||
scanNested: (args) => ipcRenderer.invoke('projectGroups:scanNested', args),
|
||||
cancelNestedScan: (args) => ipcRenderer.invoke('projectGroups:cancelNestedScan', args),
|
||||
onNestedScanProgress: (callback) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { scanId: string; scan: NestedRepoScanResult }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('projectGroups:scanNestedProgress', listener)
|
||||
return () => ipcRenderer.removeListener('projectGroups:scanNestedProgress', listener)
|
||||
},
|
||||
importNested: (args) => ipcRenderer.invoke('projectGroups:importNested', args)
|
||||
} satisfies PreloadApi['projectGroups']
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const projectsApi = {
|
||||
list: () => ipcRenderer.invoke('projects:list'),
|
||||
update: (args) => ipcRenderer.invoke('projects:update', args),
|
||||
listHostSetups: () => ipcRenderer.invoke('projectHostSetups:list'),
|
||||
createHostSetup: (args) => ipcRenderer.invoke('projectHostSetups:create', args),
|
||||
setupExistingFolder: (args) => ipcRenderer.invoke('projectHostSetups:setupExistingFolder', args),
|
||||
updateHostSetup: (args) => ipcRenderer.invoke('projectHostSetups:update', args),
|
||||
deleteHostSetup: (args) => ipcRenderer.invoke('projectHostSetups:delete', args)
|
||||
} satisfies PreloadApi['projects']
|
||||
@@ -0,0 +1,207 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { AgentSessionPtyWriteRefusal } from '../../shared/agent-session-pty-write-admission'
|
||||
import type { ProjectExecutionRuntimeResolution } from '../../shared/project-execution-runtime'
|
||||
import type { StartupCommandDelivery } from '../../shared/codex-startup-delivery'
|
||||
import type {
|
||||
AgentProviderSessionMetadata,
|
||||
SleepingAgentLaunchConfig
|
||||
} from '../../shared/agent-session-resume'
|
||||
import type { TuiAgent } from '../../shared/tui-agent'
|
||||
import type { PtyListedSession } from '../../shared/pty-listed-session'
|
||||
import type {
|
||||
PtyRendererDeliveryHealthReply,
|
||||
PtyRendererDeliveryStateReport
|
||||
} from '../../shared/pty-renderer-delivery-health'
|
||||
import type { TerminalViewAttributes } from '../../shared/terminal-view-attributes'
|
||||
import type { PtyMainDeliveryDiagnostics } from '../../shared/pty-delivery-diagnostics'
|
||||
import type { AgentKind, LaunchSource, RequestKind } from '../../shared/telemetry-events'
|
||||
|
||||
export const ptyApiPart1 = {
|
||||
spawn: (opts: {
|
||||
cols: number
|
||||
rows: number
|
||||
cwd?: string
|
||||
cwdFallback?: 'worktree'
|
||||
env?: Record<string, string>
|
||||
envToDelete?: string[]
|
||||
command?: string
|
||||
commandDelivery?: 'renderer' | 'provider'
|
||||
launchConfig?: SleepingAgentLaunchConfig
|
||||
resumeProviderSession?: AgentProviderSessionMetadata
|
||||
launchToken?: string
|
||||
launchAgent?: TuiAgent
|
||||
startupCommandDelivery?: StartupCommandDelivery
|
||||
connectionId?: string | null
|
||||
worktreeId?: string
|
||||
sessionId?: string
|
||||
shellOverride?: string
|
||||
projectRuntime?: ProjectExecutionRuntimeResolution
|
||||
terminalColorQueryReplies?: { foreground?: string; background?: string }
|
||||
// Why: marks the PTY hidden before its first byte so the delivery gate + model responder own spawn-time queries (terminal-query-authority.md §races).
|
||||
initiallyHidden?: boolean
|
||||
// Why: closes the SIGKILL race (INVESTIGATION.md) — main sync-flushes the (worktreeId, tabId, leafId → ptyId) binding before pty:spawn returns.
|
||||
tabId?: string
|
||||
leafId?: string
|
||||
// Why: loose typing on purpose — renderer owns launch metadata, main owns whether the launch happened and validates (telemetry-plan.md §Agent launch semantics).
|
||||
telemetry?: { agent_kind: AgentKind; launch_source: LaunchSource; request_kind: RequestKind }
|
||||
}): Promise<{
|
||||
id: string
|
||||
/** Which lifetime of `id` this reply named; absent when the execution host predates the field. */
|
||||
incarnationId?: string
|
||||
launchConfig?: SleepingAgentLaunchConfig
|
||||
snapshot?: string
|
||||
snapshotCols?: number
|
||||
snapshotRows?: number
|
||||
snapshotPrefixAnsi?: string
|
||||
snapshotFrameAnsi?: string
|
||||
snapshotFrameRestoreAnsi?: string
|
||||
snapshotKittyKeyboardFlags?: number
|
||||
snapshotTerminalOwner?: 'shell'
|
||||
snapshotSeq?: number
|
||||
isReattach?: boolean
|
||||
isAlternateScreen?: boolean
|
||||
replay?: string
|
||||
sessionExpired?: boolean
|
||||
coldRestore?: { scrollback: string; cwd: string; cols?: number; rows?: number }
|
||||
startupCwdFallback?: { kind: 'worktree'; cwd: string }
|
||||
agentResumeUnavailable?: true
|
||||
}> => ipcRenderer.invoke('pty:spawn', opts),
|
||||
write: (id: string, data: string): void => {
|
||||
ipcRenderer.send('pty:write', { id, data })
|
||||
},
|
||||
writeAccepted: (id: string, data: string): Promise<boolean> =>
|
||||
ipcRenderer.invoke('pty:writeAccepted', { id, data }),
|
||||
onWriteUnavailable: (
|
||||
callback: (payload: {
|
||||
id: string
|
||||
/** Set only when a durable agent-session lease refused the write; absent otherwise. */
|
||||
agentSessionRefusal?: AgentSessionPtyWriteRefusal
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const handler = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
payload: { id: string; agentSessionRefusal?: AgentSessionPtyWriteRefusal }
|
||||
): void => callback(payload)
|
||||
ipcRenderer.on('pty:writeUnavailable', handler)
|
||||
return () => ipcRenderer.removeListener('pty:writeUnavailable', handler)
|
||||
},
|
||||
resize: (id: string, cols: number, rows: number): void => {
|
||||
ipcRenderer.send('pty:resize', { id, cols, rows })
|
||||
},
|
||||
claimViewport: (id: string, cols: number, rows: number): void => {
|
||||
ipcRenderer.send('pty:claimViewport', { id, cols, rows })
|
||||
},
|
||||
reportGeometry: (id: string, cols: number, rows: number): void => {
|
||||
ipcRenderer.send('pty:reportGeometry', { id, cols, rows })
|
||||
},
|
||||
signal: (id: string, signal: string): void => {
|
||||
ipcRenderer.send('pty:signal', { id, signal })
|
||||
},
|
||||
clearBuffer: (id: string): void => {
|
||||
ipcRenderer.send('pty:clearBuffer', { id })
|
||||
},
|
||||
ackColdRestore: (id: string): void => {
|
||||
ipcRenderer.send('pty:ackColdRestore', { id })
|
||||
},
|
||||
ackData: (id: string, charCount: number, processedChars?: number): void => {
|
||||
ipcRenderer.send('pty:ackData', {
|
||||
id,
|
||||
charCount,
|
||||
...(typeof processedChars === 'number' ? { processedChars } : {})
|
||||
})
|
||||
},
|
||||
onDeliveryResyncRequest: (callback: (payload: { requestId: number }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, payload: { requestId: number }) =>
|
||||
callback(payload)
|
||||
ipcRenderer.on('pty:requestDeliveryResync', listener)
|
||||
return () => ipcRenderer.removeListener('pty:requestDeliveryResync', listener)
|
||||
},
|
||||
respondDeliveryResync: (payload: {
|
||||
requestId: number
|
||||
processedCharsByPty: Record<string, number>
|
||||
}): void => {
|
||||
ipcRenderer.send('pty:deliveryResyncResponse', payload)
|
||||
},
|
||||
reportRendererDeliveryState: (
|
||||
report: PtyRendererDeliveryStateReport
|
||||
): Promise<PtyRendererDeliveryHealthReply> =>
|
||||
ipcRenderer.invoke('pty:reportRendererDeliveryState', report),
|
||||
getPtyDataListenerCount: (): number => ipcRenderer.listenerCount('pty:data'),
|
||||
rendererDispatcherReady: (): void => {
|
||||
ipcRenderer.send('pty:rendererDispatcherReady')
|
||||
},
|
||||
setActiveRendererPty: (id: string, active: boolean): void => {
|
||||
ipcRenderer.send('pty:setActiveRendererPty', { id, active })
|
||||
},
|
||||
setRendererPtyVisible: (id: string, visible: boolean): void => {
|
||||
ipcRenderer.send('pty:setRendererPtyVisible', { id, visible })
|
||||
},
|
||||
setHiddenRendererPty: (id: string, hidden: boolean): void => {
|
||||
ipcRenderer.send('pty:setHiddenRendererPty', { id, hidden })
|
||||
},
|
||||
setPtyDeliveryInterest: (id: string, interested: boolean): void => {
|
||||
ipcRenderer.send('pty:setPtyDeliveryInterest', { id, interested })
|
||||
},
|
||||
publishTerminalViewAttributes: (attributes: TerminalViewAttributes): void => {
|
||||
ipcRenderer.send('pty:terminalViewAttributes', attributes)
|
||||
},
|
||||
kill: (id: string, opts?: { keepHistory?: boolean }): Promise<void> =>
|
||||
ipcRenderer.invoke('pty:kill', { id, keepHistory: opts?.keepHistory ?? false }),
|
||||
listSessions: (): Promise<PtyListedSession[]> => ipcRenderer.invoke('pty:listSessions'),
|
||||
getAuthoritativeBufferSnapshotCapabilities: (
|
||||
ids: string[]
|
||||
): Promise<{ id: string; authoritative: boolean | null }[]> =>
|
||||
ipcRenderer.invoke('pty:getAuthoritativeBufferSnapshotCapabilities', { ids }),
|
||||
hasPty: (id: string): Promise<boolean | null> => ipcRenderer.invoke('pty:hasPty', { id }),
|
||||
getMainBufferSnapshot: (
|
||||
id: string,
|
||||
opts?: { scrollbackRows?: number }
|
||||
): Promise<{
|
||||
data: string
|
||||
frameRestoreAnsi?: string
|
||||
cols: number
|
||||
rows: number
|
||||
cwd?: string | null
|
||||
seq?: number
|
||||
pendingDeliveryStartSeq?: number
|
||||
source?: 'headless' | 'renderer'
|
||||
alternateScreen?: boolean
|
||||
scrollbackAnsi?: string
|
||||
pendingEscapeTailAnsi?: string
|
||||
kittyKeyboardFlags?: number
|
||||
terminalOwner?: 'shell'
|
||||
} | null> => ipcRenderer.invoke('pty:getMainBufferSnapshot', { id, opts }),
|
||||
getRendererDeliveryDebugSnapshot: (): Promise<{
|
||||
pendingPtyCount: number
|
||||
pendingChars: number
|
||||
maxPendingCharsByPty: number
|
||||
rendererInFlightPtyCount: number
|
||||
rendererInFlightChars: number
|
||||
maxRendererInFlightCharsByPty: number
|
||||
activeRendererPtyCount: number
|
||||
flushScheduled: boolean
|
||||
peakPendingChars: number
|
||||
peakMaxPendingCharsByPty: number
|
||||
peakRendererInFlightChars: number
|
||||
peakMaxRendererInFlightCharsByPty: number
|
||||
ackGatedFlushSkipCount: number
|
||||
hiddenDeliveryGatedPtyCount: number
|
||||
hiddenDeliveryGatedVisiblePtyCount: number
|
||||
hiddenDeliveryGatedActivePtyCount: number
|
||||
deliveryInterestPtyCount: number
|
||||
hiddenDeliveryDroppedChars: number
|
||||
hiddenDeliveryDroppedChunks: number
|
||||
pendingDroppedChars: number
|
||||
diagnostics: PtyMainDeliveryDiagnostics
|
||||
rendererLifecycleResetCount: number
|
||||
lastLifecycleResetClearedChars: number
|
||||
rendererPtyDispatcherReady: boolean
|
||||
rendererDispatcherReadyForcedCount: number
|
||||
}> => ipcRenderer.invoke('pty:getRendererDeliveryDebugSnapshot'),
|
||||
resetRendererDeliveryDebug: (): Promise<void> =>
|
||||
ipcRenderer.invoke('pty:resetRendererDeliveryDebug'),
|
||||
hasChildProcesses: (id: string): Promise<boolean> =>
|
||||
ipcRenderer.invoke('pty:hasChildProcesses', { id }),
|
||||
getForegroundProcess: (id: string): Promise<string | null> =>
|
||||
ipcRenderer.invoke('pty:getForegroundProcess', { id })
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { PtyModelRestoreNeededEvent } from '../../shared/pty-model-restore-marker'
|
||||
import type { TerminalSideEffectBatch } from '../../shared/terminal-side-effect-facts'
|
||||
|
||||
export const ptyApiPart2 = {
|
||||
inspectProcess: (
|
||||
id: string
|
||||
): Promise<{
|
||||
foregroundProcess: string | null
|
||||
hasChildProcesses: boolean
|
||||
unavailable?: true
|
||||
}> => ipcRenderer.invoke('pty:inspectProcess', { id }),
|
||||
confirmForegroundProcess: (id: string): Promise<string | null> =>
|
||||
ipcRenderer.invoke('pty:confirmForegroundProcess', { id }),
|
||||
getCwd: (id: string): Promise<string> => ipcRenderer.invoke('pty:getCwd', { id }),
|
||||
getSize: (id: string): Promise<{ cols: number; rows: number } | null> =>
|
||||
ipcRenderer.invoke('pty:getSize', { id }),
|
||||
onData: (
|
||||
callback: (data: {
|
||||
id: string
|
||||
data: string
|
||||
seq?: number
|
||||
rawLength?: number
|
||||
transformed?: boolean
|
||||
background?: boolean
|
||||
droppedOutput?: boolean
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
id: string
|
||||
data: string
|
||||
seq?: number
|
||||
rawLength?: number
|
||||
transformed?: boolean
|
||||
background?: boolean
|
||||
droppedOutput?: boolean
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('pty:data', listener)
|
||||
return () => ipcRenderer.removeListener('pty:data', listener)
|
||||
},
|
||||
onReplay: (callback: (data: { id: string; data: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { id: string; data: string }) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('pty:replay', listener)
|
||||
return () => ipcRenderer.removeListener('pty:replay', listener)
|
||||
},
|
||||
onModelRestoreNeeded: (callback: (event: PtyModelRestoreNeededEvent) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, event: PtyModelRestoreNeededEvent) =>
|
||||
callback(event)
|
||||
ipcRenderer.on('pty:modelRestoreNeeded', listener)
|
||||
return () => ipcRenderer.removeListener('pty:modelRestoreNeeded', listener)
|
||||
},
|
||||
onSideEffect: (callback: (batch: TerminalSideEffectBatch) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, batch: TerminalSideEffectBatch) =>
|
||||
callback(batch)
|
||||
ipcRenderer.on('pty:sideEffect', listener)
|
||||
return () => ipcRenderer.removeListener('pty:sideEffect', listener)
|
||||
},
|
||||
getSideEffectSnapshot: (id: string): Promise<TerminalSideEffectBatch | null> =>
|
||||
ipcRenderer.invoke('pty:sideEffectSnapshot', { id }),
|
||||
onExit: (
|
||||
callback: (data: {
|
||||
id: string
|
||||
code: number
|
||||
preserveRendererBinding?: boolean
|
||||
/** Which lifetime of `id` died; absent when the execution host predates the field. */
|
||||
incarnationId?: string
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
id: string
|
||||
code: number
|
||||
preserveRendererBinding?: boolean
|
||||
incarnationId?: string
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('pty:exit', listener)
|
||||
return () => ipcRenderer.removeListener('pty:exit', listener)
|
||||
},
|
||||
onSpawned: (callback: (data: { id: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { id: string }) => callback(data)
|
||||
ipcRenderer.on('pty:spawned', listener)
|
||||
return () => ipcRenderer.removeListener('pty:spawned', listener)
|
||||
},
|
||||
onSerializeBufferRequest: (
|
||||
callback: (data: {
|
||||
requestId: string
|
||||
ptyId: string
|
||||
opts?: { scrollbackRows?: number; altScreenForcesZeroRows?: boolean }
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
requestId: string
|
||||
ptyId: string
|
||||
opts?: { scrollbackRows?: number; altScreenForcesZeroRows?: boolean }
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('pty:serializeBuffer:request', listener)
|
||||
return () => ipcRenderer.removeListener('pty:serializeBuffer:request', listener)
|
||||
},
|
||||
onClearBufferRequest: (callback: (data: { ptyId: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { ptyId: string }) => callback(data)
|
||||
ipcRenderer.on('pty:clearBuffer:request', listener)
|
||||
return () => ipcRenderer.removeListener('pty:clearBuffer:request', listener)
|
||||
},
|
||||
sendSerializedBuffer: (
|
||||
requestId: string,
|
||||
snapshot: {
|
||||
data: string
|
||||
cols: number
|
||||
rows: number
|
||||
seq?: number
|
||||
lastTitle?: string
|
||||
kittyKeyboardFlags?: number
|
||||
} | null
|
||||
): void => {
|
||||
ipcRenderer.send('pty:serializeBuffer:response', { requestId, snapshot })
|
||||
},
|
||||
declarePendingPaneSerializer: (paneKey: string): Promise<number> =>
|
||||
ipcRenderer.invoke('pty:declarePendingPaneSerializer', { paneKey }),
|
||||
settlePaneSerializer: (paneKey: string, gen: number): Promise<void> =>
|
||||
ipcRenderer.invoke('pty:settlePaneSerializer', { paneKey, gen }),
|
||||
clearPendingPaneSerializer: (paneKey: string, gen: number): Promise<void> =>
|
||||
ipcRenderer.invoke('pty:clearPendingPaneSerializer', { paneKey, gen }),
|
||||
reportRendererSerializerReady: (ptyId: string): Promise<void> =>
|
||||
ipcRenderer.invoke('pty:reportRendererSerializerReady', { ptyId }),
|
||||
management: {
|
||||
listSessions: () => ipcRenderer.invoke('pty:management:listSessions'),
|
||||
killAll: () => ipcRenderer.invoke('pty:management:killAll'),
|
||||
killOne: (args: { sessionId: string }) => ipcRenderer.invoke('pty:management:killOne', args),
|
||||
restart: () => ipcRenderer.invoke('pty:management:restart'),
|
||||
macTccAttribution: () => ipcRenderer.invoke('pty:management:macTccAttribution')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { ptyApiPart1 } from './pty-bridge-part-1'
|
||||
import { ptyApiPart2 } from './pty-bridge-part-2'
|
||||
|
||||
export const ptyApi = { ...ptyApiPart1, ...ptyApiPart2 }
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const pwshApi = {
|
||||
isAvailable: (): Promise<boolean> => ipcRenderer.invoke('pwsh:isAvailable')
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
CodexRateLimitResetResult,
|
||||
RateLimitRuntimeTarget,
|
||||
RateLimitState
|
||||
} from '../../shared/rate-limit-types'
|
||||
|
||||
export const rateLimitsApi = {
|
||||
get: (): Promise<RateLimitState> => ipcRenderer.invoke('rateLimits:get'),
|
||||
refresh: (): Promise<RateLimitState> => ipcRenderer.invoke('rateLimits:refresh'),
|
||||
refreshCodexForTarget: (target: RateLimitRuntimeTarget): Promise<RateLimitState> =>
|
||||
ipcRenderer.invoke('rateLimits:refreshCodexForTarget', target),
|
||||
consumeCodexResetCredit: (): Promise<CodexRateLimitResetResult> =>
|
||||
ipcRenderer.invoke('rateLimits:consumeCodexResetCredit'),
|
||||
refreshClaudeForTarget: (target: RateLimitRuntimeTarget): Promise<RateLimitState> =>
|
||||
ipcRenderer.invoke('rateLimits:refreshClaudeForTarget', target),
|
||||
setPollingInterval: (ms: number): Promise<void> =>
|
||||
ipcRenderer.invoke('rateLimits:setPollingInterval', ms),
|
||||
fetchInactiveClaudeAccounts: (): Promise<void> =>
|
||||
ipcRenderer.invoke('rateLimits:fetchInactiveClaudeAccounts'),
|
||||
fetchInactiveCodexAccounts: (): Promise<void> =>
|
||||
ipcRenderer.invoke('rateLimits:fetchInactiveCodexAccounts'),
|
||||
refreshMiniMax: (): Promise<RateLimitState> => ipcRenderer.invoke('rateLimits:refreshMiniMax'),
|
||||
refreshGrok: (): Promise<RateLimitState> => ipcRenderer.invoke('rateLimits:refreshGrok'),
|
||||
onUpdate: (callback: (state: RateLimitState) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, state: RateLimitState) => callback(state)
|
||||
ipcRenderer.on('rateLimits:update', listener)
|
||||
return () => ipcRenderer.removeListener('rateLimits:update', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { RemoteWorkspaceChangedEvent } from '../../shared/remote-workspace-types'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const remoteWorkspaceApi = {
|
||||
get: (args) => ipcRenderer.invoke('remoteWorkspace:get', args),
|
||||
setForConnectedTargets: (args) =>
|
||||
ipcRenderer.invoke('remoteWorkspace:setForConnectedTargets', args),
|
||||
listEnabledConnectedTargets: () =>
|
||||
ipcRenderer.invoke('remoteWorkspace:listEnabledConnectedTargets'),
|
||||
listConnectedClients: (args) => ipcRenderer.invoke('remoteWorkspace:listConnectedClients', args),
|
||||
clientId: () => ipcRenderer.invoke('remoteWorkspace:clientId'),
|
||||
onChanged: (callback) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: RemoteWorkspaceChangedEvent) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('remoteWorkspace:changed', listener)
|
||||
return () => ipcRenderer.removeListener('remoteWorkspace:changed', listener)
|
||||
}
|
||||
} satisfies PreloadApi['remoteWorkspace']
|
||||
@@ -0,0 +1,87 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
HostRepoCatalogSnapshot,
|
||||
ListReposForExecutionHostArgs
|
||||
} from '../../shared/host-repo-catalog-contract'
|
||||
import type { BaseRefDefaultResult, BaseRefSearchResult } from '../../shared/repo-types'
|
||||
import type { ExecutionHostId } from '../../shared/execution-host'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const reposApi = {
|
||||
list: () => ipcRenderer.invoke('repos:list'),
|
||||
|
||||
listForExecutionHost: (args: ListReposForExecutionHostArgs): Promise<HostRepoCatalogSnapshot> =>
|
||||
ipcRenderer.invoke('repos:listForExecutionHost', args),
|
||||
|
||||
add: (args) => ipcRenderer.invoke('repos:add', args),
|
||||
|
||||
addRemote: (args) => ipcRenderer.invoke('repos:addRemote', args),
|
||||
|
||||
create: (args) => ipcRenderer.invoke('repos:create', args),
|
||||
|
||||
isGitAvailable: (): Promise<boolean> => ipcRenderer.invoke('repos:isGitAvailable'),
|
||||
|
||||
getDefaultCreateProjectParent: (): Promise<string> =>
|
||||
ipcRenderer.invoke('repos:getDefaultCreateProjectParent'),
|
||||
|
||||
remove: (args) => ipcRenderer.invoke('repos:remove', args),
|
||||
|
||||
removeForHost: (args) => ipcRenderer.invoke('repos:removeForHost', args),
|
||||
|
||||
reorder: (args) => ipcRenderer.invoke('repos:reorder', args),
|
||||
|
||||
reorderForHost: (args) => ipcRenderer.invoke('repos:reorderForHost', args),
|
||||
|
||||
update: (args) => ipcRenderer.invoke('repos:update', args),
|
||||
|
||||
pickFolder: () => ipcRenderer.invoke('repos:pickFolder'),
|
||||
|
||||
pickFolders: () => ipcRenderer.invoke('repos:pickFolders'),
|
||||
|
||||
pickDirectory: () => ipcRenderer.invoke('repos:pickDirectory'),
|
||||
|
||||
clone: (args) => ipcRenderer.invoke('repos:clone', args),
|
||||
|
||||
cloneRemote: (args) => ipcRenderer.invoke('repos:cloneRemote', args),
|
||||
|
||||
createRemote: (args) => ipcRenderer.invoke('repos:createRemote', args),
|
||||
|
||||
cloneAbort: () => ipcRenderer.invoke('repos:cloneAbort'),
|
||||
|
||||
onCloneProgress: (callback: (data: { phase: string; percent: number }) => void): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { phase: string; percent: number }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('repos:clone-progress', listener)
|
||||
return () => ipcRenderer.removeListener('repos:clone-progress', listener)
|
||||
},
|
||||
|
||||
getGitUsername: (args: { repoId: string }): Promise<string> =>
|
||||
ipcRenderer.invoke('repos:getGitUsername', args),
|
||||
|
||||
getBaseRefDefault: (args: {
|
||||
repoId: string
|
||||
hostId?: ExecutionHostId
|
||||
}): Promise<BaseRefDefaultResult> => ipcRenderer.invoke('repos:getBaseRefDefault', args),
|
||||
|
||||
searchBaseRefs: (args: {
|
||||
repoId: string
|
||||
query: string
|
||||
limit?: number
|
||||
hostId?: ExecutionHostId
|
||||
}): Promise<string[]> => ipcRenderer.invoke('repos:searchBaseRefs', args),
|
||||
|
||||
searchBaseRefDetails: (args: {
|
||||
repoId: string
|
||||
query: string
|
||||
limit?: number
|
||||
hostId?: ExecutionHostId
|
||||
}): Promise<BaseRefSearchResult[]> => ipcRenderer.invoke('repos:searchBaseRefDetails', args),
|
||||
|
||||
onChanged: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('repos:changed', listener)
|
||||
return () => ipcRenderer.removeListener('repos:changed', listener)
|
||||
}
|
||||
} satisfies PreloadApi['repos']
|
||||
@@ -0,0 +1,144 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { ClientHostedBrowserRowsEvent } from '../../shared/client-hosted-browser-rows'
|
||||
import type {
|
||||
RuntimeBrowserDriverState,
|
||||
RuntimeRendererSyncWindowGraph,
|
||||
RuntimeStatus,
|
||||
RuntimeSyncWindowGraphResult,
|
||||
RuntimeTerminalDriverState
|
||||
} from '../../shared/runtime-types'
|
||||
import type { RuntimeRpcResponse } from '../../shared/runtime-rpc-envelope'
|
||||
import type { RuntimeEnvironmentSubscriptionHandle } from '../runtime-environment-subscriptions'
|
||||
|
||||
export const runtimeApi = {
|
||||
syncWindowGraph: (graph: RuntimeRendererSyncWindowGraph): Promise<RuntimeSyncWindowGraphResult> =>
|
||||
ipcRenderer.invoke('runtime:syncWindowGraph', graph),
|
||||
getStatus: (): Promise<RuntimeStatus> => ipcRenderer.invoke('runtime:getStatus'),
|
||||
call: (args: { method: string; params?: unknown }): Promise<RuntimeRpcResponse<unknown>> =>
|
||||
ipcRenderer.invoke('runtime:call', args),
|
||||
subscribe: async (
|
||||
args: { method: string; params?: unknown },
|
||||
callback: (response: RuntimeRpcResponse<unknown>) => void
|
||||
): Promise<RuntimeEnvironmentSubscriptionHandle> => {
|
||||
const subscriptionId = `desktop-${crypto.randomUUID()}`
|
||||
const channel = `runtime:subscription:${subscriptionId}`
|
||||
const listener = (_event: Electron.IpcRendererEvent, response: RuntimeRpcResponse<unknown>) =>
|
||||
callback(response)
|
||||
ipcRenderer.on(channel, listener)
|
||||
try {
|
||||
await ipcRenderer.invoke('runtime:subscribe', { subscriptionId, ...args })
|
||||
} catch (error) {
|
||||
ipcRenderer.removeListener(channel, listener)
|
||||
throw error
|
||||
}
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
ipcRenderer.removeListener(channel, listener)
|
||||
ipcRenderer.send('runtime:unsubscribe', { subscriptionId })
|
||||
},
|
||||
sendBinary: () => {
|
||||
throw new Error('Local runtime subscriptions do not accept binary input')
|
||||
}
|
||||
}
|
||||
},
|
||||
getTerminalFitOverrides: (): Promise<
|
||||
{ ptyId: string; mode: 'mobile-fit' | 'remote-desktop-fit'; cols: number; rows: number }[]
|
||||
> => ipcRenderer.invoke('runtime:getTerminalFitOverrides'),
|
||||
getTerminalDrivers: (): Promise<
|
||||
{
|
||||
ptyId: string
|
||||
driver: RuntimeTerminalDriverState
|
||||
}[]
|
||||
> => ipcRenderer.invoke('runtime:getTerminalDrivers'),
|
||||
getBrowserDrivers: (): Promise<
|
||||
{
|
||||
browserPageId: string
|
||||
driver: RuntimeBrowserDriverState
|
||||
}[]
|
||||
> => ipcRenderer.invoke('runtime:getBrowserDrivers'),
|
||||
getBrowserRemoteViewerPages: (): Promise<string[]> =>
|
||||
ipcRenderer.invoke('runtime:getBrowserRemoteViewerPages'),
|
||||
getClientHostedBrowserRows: (): Promise<ClientHostedBrowserRowsEvent[]> =>
|
||||
ipcRenderer.invoke('runtime:getClientHostedBrowserRows'),
|
||||
restoreTerminalFit: (ptyId: string): Promise<{ restored: boolean }> =>
|
||||
ipcRenderer.invoke('runtime:restoreTerminalFit', { ptyId }),
|
||||
reclaimBrowserForDesktop: (browserPageId: string): Promise<{ reclaimed: boolean }> =>
|
||||
ipcRenderer.invoke('runtime:reclaimBrowserForDesktop', { browserPageId }),
|
||||
onTerminalFitOverrideChanged: (
|
||||
callback: (event: {
|
||||
ptyId: string
|
||||
mode: 'mobile-fit' | 'remote-desktop-fit' | 'desktop-fit'
|
||||
cols: number
|
||||
rows: number
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
ptyId: string
|
||||
mode: 'mobile-fit' | 'remote-desktop-fit' | 'desktop-fit'
|
||||
cols: number
|
||||
rows: number
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('runtime:terminalFitOverrideChanged', listener)
|
||||
return () => ipcRenderer.removeListener('runtime:terminalFitOverrideChanged', listener)
|
||||
},
|
||||
onTerminalDriverChanged: (
|
||||
callback: (event: { ptyId: string; driver: RuntimeTerminalDriverState }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
ptyId: string
|
||||
driver: RuntimeTerminalDriverState
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('runtime:terminalDriverChanged', listener)
|
||||
return () => ipcRenderer.removeListener('runtime:terminalDriverChanged', listener)
|
||||
},
|
||||
onNativeChatLaunchDraftResolved: (
|
||||
callback: (event: { tabId: string; text: string; createdAt: number }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { tabId: string; text: string; createdAt: number }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('runtime:nativeChatLaunchDraftResolved', listener)
|
||||
return () => ipcRenderer.removeListener('runtime:nativeChatLaunchDraftResolved', listener)
|
||||
},
|
||||
onBrowserDriverChanged: (
|
||||
callback: (event: { browserPageId: string; driver: RuntimeBrowserDriverState }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
browserPageId: string
|
||||
driver: RuntimeBrowserDriverState
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('runtime:browserDriverChanged', listener)
|
||||
return () => ipcRenderer.removeListener('runtime:browserDriverChanged', listener)
|
||||
},
|
||||
onBrowserRemoteViewersChanged: (
|
||||
callback: (event: { browserPageId: string; hasRemoteViewers: boolean }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
browserPageId: string
|
||||
hasRemoteViewers: boolean
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('runtime:browserRemoteViewersChanged', listener)
|
||||
return () => ipcRenderer.removeListener('runtime:browserRemoteViewersChanged', listener)
|
||||
},
|
||||
onClientHostedBrowserRowsChanged: (
|
||||
callback: (event: ClientHostedBrowserRowsEvent) => void
|
||||
): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: ClientHostedBrowserRowsEvent) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('runtime:clientHostedBrowserRowsChanged', listener)
|
||||
return () => ipcRenderer.removeListener('runtime:clientHostedBrowserRowsChanged', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { VerifyAndAddRuntimeEnvironmentResult } from '../../shared/remote-pairing-verification'
|
||||
import type { RuntimeStatus } from '../../shared/runtime-types'
|
||||
import type { RuntimeRpcResponse } from '../../shared/runtime-rpc-envelope'
|
||||
import type { PublicKnownRuntimeEnvironment } from '../../shared/runtime-environments'
|
||||
import type { RemoteRuntimeSharedConnectionDiagnostics } from '../../shared/remote-runtime-shared-control-types'
|
||||
import { RUNTIME_ENVIRONMENT_DIAGNOSTICS_CHANNEL } from '../../shared/runtime-environment-diagnostics'
|
||||
import {
|
||||
subscribeRuntimeEnvironmentFromPreload,
|
||||
type RuntimeEnvironmentSubscriptionHandle
|
||||
} from '../runtime-environment-subscriptions'
|
||||
|
||||
export const runtimeEnvironmentsApi = {
|
||||
list: (): Promise<PublicKnownRuntimeEnvironment[]> =>
|
||||
ipcRenderer.invoke('runtimeEnvironments:list'),
|
||||
addFromPairingCode: (args: {
|
||||
name: string
|
||||
pairingCode: string
|
||||
}): Promise<{ environment: PublicKnownRuntimeEnvironment }> =>
|
||||
ipcRenderer.invoke('runtimeEnvironments:addFromPairingCode', args),
|
||||
verifyAndAddFromPairingCode: (args: {
|
||||
name: string
|
||||
pairingCode: string
|
||||
allowLoopback?: boolean
|
||||
}): Promise<VerifyAndAddRuntimeEnvironmentResult> =>
|
||||
ipcRenderer.invoke('runtimeEnvironments:verifyAndAddFromPairingCode', args),
|
||||
resolve: (args: { selector: string }): Promise<PublicKnownRuntimeEnvironment> =>
|
||||
ipcRenderer.invoke('runtimeEnvironments:resolve', args),
|
||||
remove: (args: { selector: string }): Promise<{ removed: PublicKnownRuntimeEnvironment }> =>
|
||||
ipcRenderer.invoke('runtimeEnvironments:remove', args),
|
||||
disconnect: (args: {
|
||||
selector: string
|
||||
}): Promise<{ disconnected: PublicKnownRuntimeEnvironment }> =>
|
||||
ipcRenderer.invoke('runtimeEnvironments:disconnect', args),
|
||||
connect: (args: {
|
||||
selector: string
|
||||
timeoutMs?: number
|
||||
}): Promise<RuntimeRpcResponse<RuntimeStatus>> =>
|
||||
ipcRenderer.invoke('runtimeEnvironments:connect', args),
|
||||
getStatus: (args: {
|
||||
selector: string
|
||||
timeoutMs?: number
|
||||
observeOnly?: true
|
||||
}): Promise<RuntimeRpcResponse<RuntimeStatus>> =>
|
||||
ipcRenderer.invoke('runtimeEnvironments:getStatus', args),
|
||||
retryControlConnection: (args: { selector: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('runtimeEnvironments:retryControlConnection', args),
|
||||
onSharedControlDiagnostics: (
|
||||
callback: (event: {
|
||||
environmentId: string
|
||||
transportGeneration: number
|
||||
diagnostics: RemoteRuntimeSharedConnectionDiagnostics
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
environmentId: string
|
||||
transportGeneration: number
|
||||
diagnostics: RemoteRuntimeSharedConnectionDiagnostics
|
||||
}
|
||||
): void => callback(data)
|
||||
ipcRenderer.on(RUNTIME_ENVIRONMENT_DIAGNOSTICS_CHANNEL, listener)
|
||||
return () => ipcRenderer.removeListener(RUNTIME_ENVIRONMENT_DIAGNOSTICS_CHANNEL, listener)
|
||||
},
|
||||
prepareBrowserClientHostPlacement: (args) =>
|
||||
ipcRenderer.invoke('runtimeEnvironments:prepareBrowserClientHostPlacement', args),
|
||||
retryConnectionsNow: (): Promise<void> =>
|
||||
ipcRenderer.invoke('runtimeEnvironments:retryConnectionsNow'),
|
||||
call: (args: {
|
||||
selector: string
|
||||
method: string
|
||||
params?: unknown
|
||||
timeoutMs?: number
|
||||
expectedEnvironmentPairingRevision?: number
|
||||
}): Promise<RuntimeRpcResponse<unknown>> => ipcRenderer.invoke('runtimeEnvironments:call', args),
|
||||
subscribe: async (
|
||||
args: {
|
||||
selector: string
|
||||
method: string
|
||||
params?: unknown
|
||||
timeoutMs?: number
|
||||
expectedEnvironmentPairingRevision?: number
|
||||
},
|
||||
callbacks: {
|
||||
onResponse: (response: RuntimeRpcResponse<unknown>) => void
|
||||
onBinary?: (bytes: Uint8Array<ArrayBufferLike>) => void
|
||||
onError?: (error: { code: string; message: string }) => void
|
||||
onClose?: () => void
|
||||
}
|
||||
): Promise<RuntimeEnvironmentSubscriptionHandle> =>
|
||||
subscribeRuntimeEnvironmentFromPreload(ipcRenderer, args, callbacks)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const sessionApi = {
|
||||
// hostId is optional; main defaults it to 'local' so existing omitting call sites keep the local session partition.
|
||||
get: (hostId) => ipcRenderer.invoke('session:get', hostId),
|
||||
set: (args, hostId) => ipcRenderer.invoke('session:set', args, hostId),
|
||||
patch: (args, hostId) => ipcRenderer.invoke('session:patch', args, hostId),
|
||||
flush: () => ipcRenderer.invoke('session:flush'),
|
||||
readTerminalScrollback: (args) =>
|
||||
ipcRenderer.sendSync('session:read-terminal-scrollback-sync', args),
|
||||
/** Synchronous session save for beforeunload — blocks until flushed to disk. */
|
||||
setSync: (args, hostId) => {
|
||||
ipcRenderer.sendSync('session:set-sync', args, hostId)
|
||||
}
|
||||
} satisfies PreloadApi['session']
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { GhosttyImportPreview } from '../../shared/global-settings-types'
|
||||
import type {
|
||||
WarpThemeImportPreview,
|
||||
WarpThemeImportSource
|
||||
} from '../../shared/terminal-custom-themes'
|
||||
|
||||
export const settingsApi = {
|
||||
get: (): Promise<unknown> => ipcRenderer.invoke('settings:get'),
|
||||
|
||||
// Why: blocking read for the few startup decisions (terminal side-effect authority) that can't wait for async hydration. Call sparingly.
|
||||
getSync: (): unknown => ipcRenderer.sendSync('settings:get-sync'),
|
||||
|
||||
set: (args: Record<string, unknown>): Promise<unknown> =>
|
||||
ipcRenderer.invoke('settings:set', args),
|
||||
|
||||
setActiveRuntimeEnvironmentPreference: (args: {
|
||||
environmentId: string | null
|
||||
}): Promise<unknown> =>
|
||||
ipcRenderer.invoke('settings:set-active-runtime-environment-preference', args),
|
||||
|
||||
updatePRBotAuthorOverride: (args: { author: string; isBot: boolean }): Promise<unknown> =>
|
||||
ipcRenderer.invoke('settings:update-pr-bot-author-override', args),
|
||||
|
||||
listFonts: (): Promise<string[]> => ipcRenderer.invoke('settings:listFonts'),
|
||||
|
||||
previewGhosttyImport: (): Promise<GhosttyImportPreview> =>
|
||||
ipcRenderer.invoke('settings:previewGhosttyImport'),
|
||||
|
||||
previewWarpThemeImport: (source: WarpThemeImportSource): Promise<WarpThemeImportPreview> =>
|
||||
ipcRenderer.invoke('settings:previewWarpThemeImport', source),
|
||||
|
||||
onChanged: (callback: (updates: Record<string, unknown>) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, updates: Record<string, unknown>): void =>
|
||||
callback(updates)
|
||||
ipcRenderer.on('settings:changed', listener)
|
||||
return () => ipcRenderer.removeListener('settings:changed', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
ShellOpenExternalEditorRequest,
|
||||
ShellOpenExternalEditorResult,
|
||||
ShellOpenLocalPathResult
|
||||
} from '../../shared/shell-open-types'
|
||||
|
||||
export const shellApi = {
|
||||
openPath: (path: string): Promise<void> => ipcRenderer.invoke('shell:openPath', path),
|
||||
|
||||
openInFileManager: (path: string): Promise<ShellOpenLocalPathResult> =>
|
||||
ipcRenderer.invoke('shell:openInFileManager', path),
|
||||
|
||||
openInExternalEditor: (
|
||||
request: ShellOpenExternalEditorRequest
|
||||
): Promise<ShellOpenExternalEditorResult> =>
|
||||
ipcRenderer.invoke('shell:openInExternalEditor', request),
|
||||
|
||||
openUrl: (url: string): Promise<void> => ipcRenderer.invoke('shell:openUrl', url),
|
||||
|
||||
openFilePath: (path: string): Promise<boolean> => ipcRenderer.invoke('shell:openFilePath', path),
|
||||
|
||||
openFileUri: (uri: string): Promise<void> => ipcRenderer.invoke('shell:openFileUri', uri),
|
||||
|
||||
pathExists: (path: string): Promise<boolean> => ipcRenderer.invoke('shell:pathExists', path),
|
||||
|
||||
pickAttachment: (): Promise<string | null> => ipcRenderer.invoke('shell:pickAttachment'),
|
||||
|
||||
pickImage: (): Promise<string | null> => ipcRenderer.invoke('shell:pickImage'),
|
||||
|
||||
pickRepoIconImage: (): Promise<{ dataUrl: string; fileName: string } | null> =>
|
||||
ipcRenderer.invoke('shell:pickRepoIconImage'),
|
||||
|
||||
pickAudio: (): Promise<string | null> => ipcRenderer.invoke('shell:pickAudio'),
|
||||
|
||||
pickDirectory: (args: { defaultPath?: string }): Promise<string | null> =>
|
||||
ipcRenderer.invoke('shell:pickDirectory', args),
|
||||
|
||||
copyFile: (args: { srcPath: string; destPath: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('shell:copyFile', args)
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
SkillDeletePlan,
|
||||
SkillDeleteRequest,
|
||||
SkillDeleteResult
|
||||
} from '../../shared/skill-delete-contract'
|
||||
import type { SkillDiscoveryResult, SkillDiscoveryTarget } from '../../shared/skills'
|
||||
import type {
|
||||
SkillCloudOwnedShare,
|
||||
SkillCloudOperation,
|
||||
SkillCloudPackageDetails
|
||||
} from '../../shared/skill-cloud-contract'
|
||||
import type {
|
||||
SkillBundleInstallPreviewInput,
|
||||
SkillBundleInstallPreviewOperation,
|
||||
SkillBundlePackageVersionInstallInput,
|
||||
SkillBundleShareInstallInput,
|
||||
SkillBundleShareInstallOperation,
|
||||
SkillInstallPreviewInput,
|
||||
SkillInstallPreviewOperation,
|
||||
ManagedSkillInstallListOperation,
|
||||
SkillPackageVersionInstallInput,
|
||||
SkillRemoveInput,
|
||||
SkillRemoveOperation,
|
||||
SkillShareInstallInput,
|
||||
SkillShareInstallOperation,
|
||||
SkillInstallCancelInput,
|
||||
SkillInstallProgress,
|
||||
SkillSharePreview,
|
||||
SkillShareProgress,
|
||||
SkillSharePublishInput,
|
||||
SkillSharePublishOperation,
|
||||
SkillShareResolvedOperation
|
||||
} from '../../shared/skill-sharing-contract'
|
||||
import type {
|
||||
SkillFreshnessInventory,
|
||||
SkillUpdateRun,
|
||||
SkillUpdateStartResult
|
||||
} from '../../shared/skill-freshness'
|
||||
|
||||
export const skillsApi = {
|
||||
discover: (target?: SkillDiscoveryTarget): Promise<SkillDiscoveryResult> =>
|
||||
ipcRenderer.invoke('skills:discover', target),
|
||||
freshnessInventory: (): Promise<SkillFreshnessInventory> =>
|
||||
ipcRenderer.invoke('skills:freshnessInventory'),
|
||||
startUpdateRun: (names: string[]): Promise<SkillUpdateStartResult> =>
|
||||
ipcRenderer.invoke('skills:startUpdateRun', names),
|
||||
cancelUpdateRun: (): Promise<void> => ipcRenderer.invoke('skills:cancelUpdateRun'),
|
||||
acknowledgeUpdateRun: (): Promise<void> => ipcRenderer.invoke('skills:acknowledgeUpdateRun'),
|
||||
getUpdateRun: (): Promise<SkillUpdateRun> => ipcRenderer.invoke('skills:getUpdateRun'),
|
||||
prepareShare: (input: {
|
||||
skillIds: string[]
|
||||
bundleName: string
|
||||
target?: SkillDiscoveryTarget
|
||||
packageId?: string
|
||||
}): Promise<SkillSharePreview> => ipcRenderer.invoke('skills:prepareShare', input),
|
||||
publishShare: (input: SkillSharePublishInput): Promise<SkillSharePublishOperation> =>
|
||||
ipcRenderer.invoke('skills:publishShare', input),
|
||||
cancelShare: (preparationId: string): Promise<void> =>
|
||||
ipcRenderer.invoke('skills:cancelShare', preparationId),
|
||||
releaseShare: (preparationId: string): Promise<void> =>
|
||||
ipcRenderer.invoke('skills:releaseShare', preparationId),
|
||||
resolveShare: (shareId: string): Promise<SkillShareResolvedOperation> =>
|
||||
ipcRenderer.invoke('skills:resolveShare', shareId),
|
||||
installShare: (input: SkillShareInstallInput): Promise<SkillShareInstallOperation> =>
|
||||
ipcRenderer.invoke('skills:installShare', input),
|
||||
installBundleShare: (
|
||||
input: SkillBundleShareInstallInput
|
||||
): Promise<SkillBundleShareInstallOperation> =>
|
||||
ipcRenderer.invoke('skills:installBundleShare', input),
|
||||
installBundlePackageVersion: (
|
||||
input: SkillBundlePackageVersionInstallInput
|
||||
): Promise<SkillBundleShareInstallOperation> =>
|
||||
ipcRenderer.invoke('skills:installBundlePackageVersion', input),
|
||||
installPackageVersion: (
|
||||
input: SkillPackageVersionInstallInput
|
||||
): Promise<SkillShareInstallOperation> =>
|
||||
ipcRenderer.invoke('skills:installPackageVersion', input),
|
||||
cancelInstall: (input: SkillInstallCancelInput): Promise<{ cancelled: boolean }> =>
|
||||
ipcRenderer.invoke('skills:cancelInstall', input),
|
||||
previewInstall: (input: SkillInstallPreviewInput): Promise<SkillInstallPreviewOperation> =>
|
||||
ipcRenderer.invoke('skills:previewInstall', input),
|
||||
previewBundleInstall: (
|
||||
input: SkillBundleInstallPreviewInput
|
||||
): Promise<SkillBundleInstallPreviewOperation> =>
|
||||
ipcRenderer.invoke('skills:previewBundleInstall', input),
|
||||
removeInstall: (input: SkillRemoveInput): Promise<SkillRemoveOperation> =>
|
||||
ipcRenderer.invoke('skills:removeInstall', input),
|
||||
// Desktop always registers the delete IPC handlers in its own main process.
|
||||
deleteSupported: (): Promise<boolean> => Promise.resolve(true),
|
||||
previewDelete: (request: SkillDeleteRequest): Promise<SkillDeletePlan> =>
|
||||
ipcRenderer.invoke('skills:previewDelete', request),
|
||||
delete: (request: SkillDeleteRequest): Promise<SkillDeleteResult> =>
|
||||
ipcRenderer.invoke('skills:delete', request),
|
||||
listManagedInstalls: (environmentId?: string): Promise<ManagedSkillInstallListOperation> =>
|
||||
ipcRenderer.invoke('skills:listManagedInstalls', environmentId),
|
||||
getPackage: (packageId: string): Promise<SkillCloudOperation<SkillCloudPackageDetails>> =>
|
||||
ipcRenderer.invoke('skills:getPackage', packageId),
|
||||
listOwnedShares: (): Promise<SkillCloudOperation<SkillCloudOwnedShare[]>> =>
|
||||
ipcRenderer.invoke('skills:listOwnedShares'),
|
||||
revokeShare: (shareId: string): Promise<SkillCloudOperation<void>> =>
|
||||
ipcRenderer.invoke('skills:revokeShare', shareId),
|
||||
deletePackageVersion: (input: {
|
||||
packageId: string
|
||||
versionId: string
|
||||
}): Promise<SkillCloudOperation<void>> =>
|
||||
ipcRenderer.invoke('skills:deletePackageVersion', input),
|
||||
deletePackage: (packageId: string): Promise<SkillCloudOperation<void>> =>
|
||||
ipcRenderer.invoke('skills:deletePackage', packageId),
|
||||
listWslDistros: (environmentId?: string): Promise<string[]> =>
|
||||
ipcRenderer.invoke('skills:listWslDistros', environmentId),
|
||||
onInstallProgress: (callback: (progress: SkillInstallProgress) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, progress: SkillInstallProgress): void =>
|
||||
callback(progress)
|
||||
ipcRenderer.on('skills:installProgress', listener)
|
||||
return () => ipcRenderer.removeListener('skills:installProgress', listener)
|
||||
},
|
||||
onShareProgress: (callback: (progress: SkillShareProgress) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, progress: SkillShareProgress): void =>
|
||||
callback(progress)
|
||||
ipcRenderer.on('skills:shareProgress', listener)
|
||||
return () => ipcRenderer.removeListener('skills:shareProgress', listener)
|
||||
},
|
||||
onUpdateRun: (callback: (run: SkillUpdateRun) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, run: SkillUpdateRun): void => callback(run)
|
||||
ipcRenderer.on('skills:updateRun', listener)
|
||||
return () => ipcRenderer.removeListener('skills:updateRun', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const sparsePresetsApi = {
|
||||
list: (args) => ipcRenderer.invoke('sparsePresets:list', args),
|
||||
|
||||
save: (args) => ipcRenderer.invoke('sparsePresets:save', args),
|
||||
|
||||
remove: (args) => ipcRenderer.invoke('sparsePresets:remove', args),
|
||||
|
||||
onChanged: (callback: (data: { repoId: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { repoId: string }) => callback(data)
|
||||
ipcRenderer.on('sparsePresets:changed', listener)
|
||||
return () => ipcRenderer.removeListener('sparsePresets:changed', listener)
|
||||
}
|
||||
} satisfies PreloadApi['sparsePresets']
|
||||
@@ -0,0 +1,81 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
SpeechErrorEvent,
|
||||
SpeechLifecycleEvent,
|
||||
SpeechModelManifest,
|
||||
SpeechModelState,
|
||||
SpeechTranscriptEvent
|
||||
} from '../../shared/speech-types'
|
||||
|
||||
export const speechApi = {
|
||||
getCatalog: (): Promise<SpeechModelManifest[]> => ipcRenderer.invoke('speech:getCatalog'),
|
||||
getModelStates: (): Promise<SpeechModelState[]> => ipcRenderer.invoke('speech:getModelStates'),
|
||||
getOpenAiApiKeyStatus: (): Promise<{ configured: boolean }> =>
|
||||
ipcRenderer.invoke('speech:getOpenAiApiKeyStatus'),
|
||||
saveOpenAiApiKey: (apiKey: string): Promise<{ configured: boolean }> =>
|
||||
ipcRenderer.invoke('speech:saveOpenAiApiKey', apiKey),
|
||||
clearOpenAiApiKey: (): Promise<{ configured: boolean }> =>
|
||||
ipcRenderer.invoke('speech:clearOpenAiApiKey'),
|
||||
downloadModel: (modelId: string): Promise<void> =>
|
||||
ipcRenderer.invoke('speech:downloadModel', modelId),
|
||||
cancelDownload: (modelId: string): Promise<void> =>
|
||||
ipcRenderer.invoke('speech:cancelDownload', modelId),
|
||||
deleteModel: (modelId: string): Promise<void> =>
|
||||
ipcRenderer.invoke('speech:deleteModel', modelId),
|
||||
startDictation: (
|
||||
modelId: string,
|
||||
hotwords: string[] | undefined,
|
||||
sessionId: string
|
||||
): Promise<void> => ipcRenderer.invoke('speech:startDictation', modelId, hotwords, sessionId),
|
||||
feedAudio: (samples: Float32Array, sampleRate: number, sessionId = 'desktop'): Promise<void> =>
|
||||
// Why: Float32Array is zeroed crossing the contextBridge/IPC boundary; wrap in a Buffer to preserve bytes.
|
||||
ipcRenderer.invoke(
|
||||
'speech:feedAudio',
|
||||
Buffer.from(samples.buffer, samples.byteOffset, samples.byteLength),
|
||||
sampleRate,
|
||||
sessionId
|
||||
),
|
||||
stopDictation: (sessionId = 'desktop'): Promise<void> =>
|
||||
ipcRenderer.invoke('speech:stopDictation', sessionId),
|
||||
|
||||
onPartialTranscript: (callback: (data: SpeechTranscriptEvent) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: SpeechTranscriptEvent): void =>
|
||||
callback(data)
|
||||
ipcRenderer.on('speech:partial', listener)
|
||||
return () => ipcRenderer.removeListener('speech:partial', listener)
|
||||
},
|
||||
onFinalTranscript: (callback: (data: SpeechTranscriptEvent) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: SpeechTranscriptEvent): void =>
|
||||
callback(data)
|
||||
ipcRenderer.on('speech:final', listener)
|
||||
return () => ipcRenderer.removeListener('speech:final', listener)
|
||||
},
|
||||
onDownloadProgress: (
|
||||
callback: (data: { modelId: string; progress: number }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { modelId: string; progress: number }
|
||||
): void => callback(data)
|
||||
ipcRenderer.on('speech:downloadProgress', listener)
|
||||
return () => ipcRenderer.removeListener('speech:downloadProgress', listener)
|
||||
},
|
||||
onReady: (callback: (data: SpeechLifecycleEvent) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: SpeechLifecycleEvent): void =>
|
||||
callback(data)
|
||||
ipcRenderer.on('speech:ready', listener)
|
||||
return () => ipcRenderer.removeListener('speech:ready', listener)
|
||||
},
|
||||
onStopped: (callback: (data: SpeechLifecycleEvent) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: SpeechLifecycleEvent): void =>
|
||||
callback(data)
|
||||
ipcRenderer.on('speech:stopped', listener)
|
||||
return () => ipcRenderer.removeListener('speech:stopped', listener)
|
||||
},
|
||||
onError: (callback: (data: SpeechErrorEvent) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: SpeechErrorEvent): void =>
|
||||
callback(data)
|
||||
ipcRenderer.on('speech:error', listener)
|
||||
return () => ipcRenderer.removeListener('speech:error', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
SshConnectionState,
|
||||
SshConfigHostListArgs,
|
||||
SshConfigHostListResult,
|
||||
SshConfigHostResolution,
|
||||
SshConfigImportResult,
|
||||
SshTargetAddResult,
|
||||
SshTargetCreateInput,
|
||||
SshTarget,
|
||||
SshTargetUpdateInput,
|
||||
PortForwardEntry,
|
||||
EnrichedDetectedPort
|
||||
} from '../../shared/ssh-types'
|
||||
import {
|
||||
admitSshConnectionStateForAuthorityReconciliation,
|
||||
admitSshDetectedPorts
|
||||
} from '../../shared/ssh-retained-payload-admission'
|
||||
import type { FilesystemPathFlavor } from '../../shared/filesystem-entry-types'
|
||||
|
||||
export const sshApi = {
|
||||
listTargets: (): Promise<SshTarget[]> => ipcRenderer.invoke('ssh:listTargets'),
|
||||
|
||||
listRemovedTargetLabels: (): Promise<Record<string, string>> =>
|
||||
ipcRenderer.invoke('ssh:listRemovedTargetLabels'),
|
||||
|
||||
addTarget: (args: { target: SshTargetCreateInput }): Promise<SshTargetAddResult> =>
|
||||
ipcRenderer.invoke('ssh:addTarget', args),
|
||||
|
||||
updateTarget: (args: { id: string; updates: SshTargetUpdateInput }): Promise<SshTarget> =>
|
||||
ipcRenderer.invoke('ssh:updateTarget', args),
|
||||
|
||||
removeTarget: (args: { id: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('ssh:removeTarget', args),
|
||||
|
||||
importConfig: (args?: { reAdopt?: boolean }): Promise<SshConfigImportResult> =>
|
||||
ipcRenderer.invoke('ssh:importConfig', args),
|
||||
|
||||
listConfigHosts: (args?: SshConfigHostListArgs): Promise<SshConfigHostListResult> =>
|
||||
ipcRenderer.invoke('ssh:listConfigHosts', args),
|
||||
|
||||
resolveConfigHost: (args: { alias: string }): Promise<SshConfigHostResolution | null> =>
|
||||
ipcRenderer.invoke('ssh:resolveConfigHost', args),
|
||||
|
||||
connect: async (args: { targetId: string }): Promise<SshConnectionState | null> => {
|
||||
const state: unknown = await ipcRenderer.invoke('ssh:connect', args)
|
||||
return state ? admitSshConnectionStateForAuthorityReconciliation(state, args.targetId) : null
|
||||
},
|
||||
|
||||
disconnect: (args: { targetId: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('ssh:disconnect', args),
|
||||
|
||||
terminateSessions: (args: { targetId: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('ssh:terminateSessions', args),
|
||||
|
||||
resetRelay: (args: { targetId: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('ssh:resetRelay', args),
|
||||
|
||||
getState: async (args: { targetId: string }): Promise<SshConnectionState | null> => {
|
||||
const state: unknown = await ipcRenderer.invoke('ssh:getState', args)
|
||||
return state ? admitSshConnectionStateForAuthorityReconciliation(state, args.targetId) : null
|
||||
},
|
||||
|
||||
needsPassphrasePrompt: (args: { targetId: string }): Promise<boolean> =>
|
||||
ipcRenderer.invoke('ssh:needsPassphrasePrompt', args),
|
||||
|
||||
testConnection: async (args: {
|
||||
targetId: string
|
||||
}): Promise<{ success: boolean; error?: string; state?: SshConnectionState }> => {
|
||||
const result: { success: boolean; error?: string; state?: unknown } = await ipcRenderer.invoke(
|
||||
'ssh:testConnection',
|
||||
args
|
||||
)
|
||||
const state = result.state
|
||||
? admitSshConnectionStateForAuthorityReconciliation(result.state, args.targetId)
|
||||
: null
|
||||
return { ...result, ...(state ? { state } : { state: undefined }) }
|
||||
},
|
||||
|
||||
onStateChanged: (
|
||||
callback: (data: { targetId: string; state: SshConnectionState }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { targetId: string; state: unknown }
|
||||
): void => {
|
||||
const state = admitSshConnectionStateForAuthorityReconciliation(data.state, data.targetId)
|
||||
if (state) {
|
||||
callback({ targetId: data.targetId, state })
|
||||
}
|
||||
}
|
||||
ipcRenderer.on('ssh:state-changed', listener)
|
||||
return () => ipcRenderer.removeListener('ssh:state-changed', listener)
|
||||
},
|
||||
|
||||
addPortForward: (args: {
|
||||
targetId: string
|
||||
localPort: number
|
||||
remoteHost: string
|
||||
remotePort: number
|
||||
label?: string
|
||||
}): Promise<PortForwardEntry> => ipcRenderer.invoke('ssh:addPortForward', args),
|
||||
|
||||
updatePortForward: (args: {
|
||||
id: string
|
||||
targetId: string
|
||||
localPort: number
|
||||
remoteHost: string
|
||||
remotePort: number
|
||||
label?: string
|
||||
}): Promise<PortForwardEntry> => ipcRenderer.invoke('ssh:updatePortForward', args),
|
||||
|
||||
removePortForward: (args: { id: string }): Promise<PortForwardEntry | null> =>
|
||||
ipcRenderer.invoke('ssh:removePortForward', args),
|
||||
|
||||
listPortForwards: (args?: { targetId?: string }): Promise<PortForwardEntry[]> =>
|
||||
ipcRenderer.invoke('ssh:listPortForwards', args),
|
||||
|
||||
listDetectedPorts: async (args: { targetId: string }): Promise<EnrichedDetectedPort[]> =>
|
||||
admitSshDetectedPorts(await ipcRenderer.invoke('ssh:listDetectedPorts', args)),
|
||||
|
||||
onPortForwardsChanged: (
|
||||
callback: (data: { targetId: string; forwards: PortForwardEntry[] }) => void
|
||||
): (() => void) => {
|
||||
const handler = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { targetId: string; forwards: PortForwardEntry[] }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ssh:port-forwards-changed', handler)
|
||||
return () => ipcRenderer.removeListener('ssh:port-forwards-changed', handler)
|
||||
},
|
||||
|
||||
onDetectedPortsChanged: (
|
||||
callback: (data: { targetId: string; ports: EnrichedDetectedPort[] }) => void
|
||||
): (() => void) => {
|
||||
const handler = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { targetId: string; ports: unknown }
|
||||
) => callback({ targetId: data.targetId, ports: admitSshDetectedPorts(data.ports) })
|
||||
ipcRenderer.on('ssh:detected-ports-changed', handler)
|
||||
return () => ipcRenderer.removeListener('ssh:detected-ports-changed', handler)
|
||||
},
|
||||
|
||||
browseDir: (args: {
|
||||
targetId: string
|
||||
dirPath: string
|
||||
}): Promise<{
|
||||
entries: { name: string; isDirectory: boolean }[]
|
||||
resolvedPath: string
|
||||
pathFlavor: FilesystemPathFlavor
|
||||
}> => ipcRenderer.invoke('ssh:browseDir', args),
|
||||
|
||||
onCredentialRequest: (
|
||||
callback: (data: {
|
||||
requestId: string
|
||||
targetId: string
|
||||
kind: 'passphrase' | 'password' | 'keyboard-interactive'
|
||||
detail: string
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
requestId: string
|
||||
targetId: string
|
||||
kind: 'passphrase' | 'password' | 'keyboard-interactive'
|
||||
detail: string
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ssh:credential-request', listener)
|
||||
return () => ipcRenderer.removeListener('ssh:credential-request', listener)
|
||||
},
|
||||
|
||||
onCredentialResolved: (callback: (data: { requestId: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { requestId: string }) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('ssh:credential-resolved', listener)
|
||||
return () => ipcRenderer.removeListener('ssh:credential-resolved', listener)
|
||||
},
|
||||
|
||||
submitCredential: (args: { requestId: string; value: string | null }): Promise<void> =>
|
||||
ipcRenderer.invoke('ssh:submitCredential', args)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const starNagApi = {
|
||||
onShow: (
|
||||
callback: (payload?: { mode?: 'gh' | 'web'; surface?: 'card' | 'toast' }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
payload?: { mode?: 'gh' | 'web'; surface?: 'card' | 'toast' }
|
||||
): void => callback(payload)
|
||||
ipcRenderer.on('star-nag:show', listener)
|
||||
return () => ipcRenderer.removeListener('star-nag:show', listener)
|
||||
},
|
||||
onHide: (callback: () => void): (() => void) => {
|
||||
const listener = (): void => callback()
|
||||
ipcRenderer.on('star-nag:hide', listener)
|
||||
return () => ipcRenderer.removeListener('star-nag:hide', listener)
|
||||
},
|
||||
dismiss: (): Promise<void> => ipcRenderer.invoke('star-nag:dismiss'),
|
||||
later: (): Promise<void> => ipcRenderer.invoke('star-nag:later'),
|
||||
complete: (): Promise<void> => ipcRenderer.invoke('star-nag:complete'),
|
||||
disable: (): Promise<void> => ipcRenderer.invoke('star-nag:disable'),
|
||||
openWeb: (): Promise<void> => ipcRenderer.invoke('star-nag:openWeb'),
|
||||
starOrca: (): Promise<boolean> => ipcRenderer.invoke('star-nag:starOrca'),
|
||||
forceShow: (): Promise<void> => ipcRenderer.invoke('star-nag:forceShow'),
|
||||
agentValueMoment: (): Promise<{ status: 'ready'; mode: 'gh' | 'web' } | { status: 'skipped' }> =>
|
||||
ipcRenderer.invoke('star-nag:agentValueMoment'),
|
||||
showAgentValueMoment: (): Promise<void> => ipcRenderer.invoke('star-nag:showAgentValueMoment'),
|
||||
onboardingCompleted: (): Promise<void> => ipcRenderer.invoke('star-nag:onboardingCompleted')
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const statsApi = {
|
||||
getSummary: (): Promise<{
|
||||
totalAgentsSpawned: number
|
||||
totalPRsCreated: number
|
||||
totalAgentTimeMs: number
|
||||
firstEventAt: number | null
|
||||
}> => ipcRenderer.invoke('stats:summary')
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
TerminalPreviewConnectResult,
|
||||
TerminalPreviewDataPayload
|
||||
} from '../../shared/terminal-preview'
|
||||
|
||||
export const terminalPreviewApi = {
|
||||
connect: (
|
||||
ptyId: string,
|
||||
opts?: { scrollbackRows?: number }
|
||||
): Promise<TerminalPreviewConnectResult> =>
|
||||
ipcRenderer.invoke('terminalPreview:connect', { ptyId, opts }),
|
||||
input: (ptyId: string, data: string): Promise<boolean> =>
|
||||
ipcRenderer.invoke('terminalPreview:input', { ptyId, data }),
|
||||
fit: (
|
||||
ptyId: string,
|
||||
cols: number,
|
||||
rows: number
|
||||
): Promise<{ cols: number; rows: number } | null> =>
|
||||
ipcRenderer.invoke('terminalPreview:fit', { ptyId, cols, rows }),
|
||||
ack: (ptyId: string, bytes: number): Promise<void> =>
|
||||
ipcRenderer.invoke('terminalPreview:ack', { ptyId, bytes }),
|
||||
unsubscribe: (ptyId: string): Promise<void> =>
|
||||
ipcRenderer.invoke('terminalPreview:unsubscribe', { ptyId }),
|
||||
onData: (callback: (payload: TerminalPreviewDataPayload) => void): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
payload: TerminalPreviewDataPayload
|
||||
): void => callback(payload)
|
||||
ipcRenderer.on('terminalPreview:data', listener)
|
||||
return () => ipcRenderer.removeListener('terminalPreview:data', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { PersistedUIState } from '../../shared/persisted-ui-state-types'
|
||||
import type { KeybindingActionId } from '../../shared/keybindings'
|
||||
|
||||
export const uiApiPart1 = {
|
||||
get: () => ipcRenderer.invoke('ui:get'),
|
||||
set: (args) => ipcRenderer.invoke('ui:set', args),
|
||||
setWithAck: (args) => ipcRenderer.invoke('ui:set', args),
|
||||
recordFeatureInteraction: (id) => ipcRenderer.invoke('ui:recordFeatureInteraction', id),
|
||||
onStateChanged: (callback: (ui: PersistedUIState) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, ui: PersistedUIState): void => callback(ui)
|
||||
ipcRenderer.on('ui:stateChanged', listener)
|
||||
return () => ipcRenderer.removeListener('ui:stateChanged', listener)
|
||||
},
|
||||
onOpenSettings: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:openSettings', listener)
|
||||
return () => ipcRenderer.removeListener('ui:openSettings', listener)
|
||||
},
|
||||
consumePendingOpenSettings: (): Promise<boolean> =>
|
||||
ipcRenderer.invoke('ui:consumePendingOpenSettings'),
|
||||
onOpenSkillShare: (callback: (shareId: string) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, shareId: string): void => callback(shareId)
|
||||
ipcRenderer.on('ui:openSkillShare', listener)
|
||||
return () => ipcRenderer.removeListener('ui:openSkillShare', listener)
|
||||
},
|
||||
consumePendingSkillShare: (): Promise<string | null> =>
|
||||
ipcRenderer.invoke('ui:consumePendingSkillShare'),
|
||||
onOpenSetupGuide: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:openSetupGuide', listener)
|
||||
return () => ipcRenderer.removeListener('ui:openSetupGuide', listener)
|
||||
},
|
||||
onOpenFeatureTour: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:openFeatureTour', listener)
|
||||
return () => ipcRenderer.removeListener('ui:openFeatureTour', listener)
|
||||
},
|
||||
onOpenCrashReport: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:openCrashReport', listener)
|
||||
return () => ipcRenderer.removeListener('ui:openCrashReport', listener)
|
||||
},
|
||||
onToggleLeftSidebar: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:toggleLeftSidebar', listener)
|
||||
return () => ipcRenderer.removeListener('ui:toggleLeftSidebar', listener)
|
||||
},
|
||||
onToggleRightSidebar: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:toggleRightSidebar', listener)
|
||||
return () => ipcRenderer.removeListener('ui:toggleRightSidebar', listener)
|
||||
},
|
||||
onToggleWorktreePalette: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:toggleWorktreePalette', listener)
|
||||
return () => ipcRenderer.removeListener('ui:toggleWorktreePalette', listener)
|
||||
},
|
||||
onToggleFloatingTerminal: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:toggleFloatingTerminal', listener)
|
||||
return () => ipcRenderer.removeListener('ui:toggleFloatingTerminal', listener)
|
||||
},
|
||||
onTerminalShortcutCaptured: (
|
||||
callback: (data: { actionId: KeybindingActionId }) => void
|
||||
): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { actionId: KeybindingActionId }) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('ui:terminalShortcutCaptured', listener)
|
||||
return () => ipcRenderer.removeListener('ui:terminalShortcutCaptured', listener)
|
||||
},
|
||||
onOpenQuickOpen: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:openQuickOpen', listener)
|
||||
return () => ipcRenderer.removeListener('ui:openQuickOpen', listener)
|
||||
},
|
||||
onToggleQuickCommandsMenu: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:toggleQuickCommandsMenu', listener)
|
||||
return () => ipcRenderer.removeListener('ui:toggleQuickCommandsMenu', listener)
|
||||
},
|
||||
onOpenNewWorkspace: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:openNewWorkspace', listener)
|
||||
return () => ipcRenderer.removeListener('ui:openNewWorkspace', listener)
|
||||
},
|
||||
onDeleteCurrentWorkspace: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:deleteCurrentWorkspace', listener)
|
||||
return () => ipcRenderer.removeListener('ui:deleteCurrentWorkspace', listener)
|
||||
},
|
||||
onOpenWorkspaceBoard: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:openWorkspaceBoard', listener)
|
||||
return () => ipcRenderer.removeListener('ui:openWorkspaceBoard', listener)
|
||||
},
|
||||
onOpenTasks: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:openTasks', listener)
|
||||
return () => ipcRenderer.removeListener('ui:openTasks', listener)
|
||||
},
|
||||
onToggleAgentDashboard: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:toggleAgentDashboard', listener)
|
||||
return () => ipcRenderer.removeListener('ui:toggleAgentDashboard', listener)
|
||||
},
|
||||
onJumpToWorktreeIndex: (callback: (index: number) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, index: number) => callback(index)
|
||||
ipcRenderer.on('ui:jumpToWorktreeIndex', listener)
|
||||
return () => ipcRenderer.removeListener('ui:jumpToWorktreeIndex', listener)
|
||||
},
|
||||
onJumpToTabIndex: (callback: (index: number) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, index: number) => callback(index)
|
||||
ipcRenderer.on('ui:jumpToTabIndex', listener)
|
||||
return () => ipcRenderer.removeListener('ui:jumpToTabIndex', listener)
|
||||
},
|
||||
onWorktreeHistoryNavigate: (callback: (direction: 'back' | 'forward') => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, direction: 'back' | 'forward') =>
|
||||
callback(direction)
|
||||
ipcRenderer.on('ui:worktreeHistoryNavigate', listener)
|
||||
return () => ipcRenderer.removeListener('ui:worktreeHistoryNavigate', listener)
|
||||
},
|
||||
onNewBrowserTab: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:newBrowserTab', listener)
|
||||
return () => ipcRenderer.removeListener('ui:newBrowserTab', listener)
|
||||
},
|
||||
onNewMarkdownTab: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:newMarkdownTab', listener)
|
||||
return () => ipcRenderer.removeListener('ui:newMarkdownTab', listener)
|
||||
},
|
||||
onNewSimulatorTab: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:newSimulatorTab', listener)
|
||||
return () => ipcRenderer.removeListener('ui:newSimulatorTab', listener)
|
||||
},
|
||||
onRequestTabCreate: (
|
||||
callback: (data: {
|
||||
requestId: string
|
||||
url: string
|
||||
worktreeId?: string
|
||||
browserPageId?: string
|
||||
sessionProfileId?: string | null
|
||||
sessionPartition?: string
|
||||
activate?: boolean
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
requestId: string
|
||||
url: string
|
||||
worktreeId?: string
|
||||
browserPageId?: string
|
||||
sessionProfileId?: string | null
|
||||
sessionPartition?: string
|
||||
activate?: boolean
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:requestTabCreate', listener)
|
||||
return () => ipcRenderer.removeListener('browser:requestTabCreate', listener)
|
||||
},
|
||||
replyTabCreate: (reply: { requestId: string; browserPageId?: string; error?: string }): void => {
|
||||
ipcRenderer.send('browser:tabCreateReply', reply)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { admitCloseActiveTabPayload } from '../close-active-tab-payload-admission'
|
||||
import type { CloseActiveTabPayload } from '../api/ui-command-event-api'
|
||||
import type {
|
||||
WorktreeDefaultTabsLaunch,
|
||||
WorktreeSetupLaunch
|
||||
} from '../../shared/worktree/launch-types'
|
||||
import { browserFindSubscriptions } from '../preload-runtime-support'
|
||||
|
||||
export const uiApiPart2 = {
|
||||
onRequestTabSetProfile: (
|
||||
callback: (data: {
|
||||
requestId: string
|
||||
browserPageId: string
|
||||
profileId: string
|
||||
sessionPartition?: string
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
requestId: string
|
||||
browserPageId: string
|
||||
profileId: string
|
||||
sessionPartition?: string
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:requestTabSetProfile', listener)
|
||||
return () => ipcRenderer.removeListener('browser:requestTabSetProfile', listener)
|
||||
},
|
||||
replyTabSetProfile: (reply: { requestId: string; error?: string }): void => {
|
||||
ipcRenderer.send('browser:tabSetProfileReply', reply)
|
||||
},
|
||||
onRequestTabClose: (
|
||||
callback: (data: { requestId: string; tabId: string | null; worktreeId?: string }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { requestId: string; tabId: string | null; worktreeId?: string }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('browser:requestTabClose', listener)
|
||||
return () => ipcRenderer.removeListener('browser:requestTabClose', listener)
|
||||
},
|
||||
replyTabClose: (reply: {
|
||||
requestId: string
|
||||
error?: string
|
||||
code?: 'browser_tab_not_found'
|
||||
}): void => {
|
||||
ipcRenderer.send('browser:tabCloseReply', reply)
|
||||
},
|
||||
onNewTerminalTab: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:newTerminalTab', listener)
|
||||
return () => ipcRenderer.removeListener('ui:newTerminalTab', listener)
|
||||
},
|
||||
onFocusBrowserAddressBar: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:focusBrowserAddressBar', listener)
|
||||
return () => ipcRenderer.removeListener('ui:focusBrowserAddressBar', listener)
|
||||
},
|
||||
onFindInBrowserPage: browserFindSubscriptions.subscribe,
|
||||
onReloadBrowserPage: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:reloadBrowserPage', listener)
|
||||
return () => ipcRenderer.removeListener('ui:reloadBrowserPage', listener)
|
||||
},
|
||||
onBrowserHistoryNavigate: (callback: (direction: 'back' | 'forward') => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, direction: 'back' | 'forward'): void =>
|
||||
callback(direction)
|
||||
ipcRenderer.on('ui:browserHistoryNavigate', listener)
|
||||
return () => ipcRenderer.removeListener('ui:browserHistoryNavigate', listener)
|
||||
},
|
||||
onZoomBrowserPage: (callback: (direction: 'in' | 'out' | 'reset') => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, direction: 'in' | 'out' | 'reset') =>
|
||||
callback(direction)
|
||||
ipcRenderer.on('ui:zoomBrowserPage', listener)
|
||||
return () => ipcRenderer.removeListener('ui:zoomBrowserPage', listener)
|
||||
},
|
||||
onScrollBrowserPage: (
|
||||
callback: (event: { browserPageId: string; deltaX: number; deltaY: number }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
payload: { browserPageId: string; deltaX: number; deltaY: number }
|
||||
) => callback(payload)
|
||||
ipcRenderer.on('ui:scrollBrowserPage', listener)
|
||||
return () => ipcRenderer.removeListener('ui:scrollBrowserPage', listener)
|
||||
},
|
||||
onHardReloadBrowserPage: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:hardReloadBrowserPage', listener)
|
||||
return () => ipcRenderer.removeListener('ui:hardReloadBrowserPage', listener)
|
||||
},
|
||||
onCloseActiveTab: (callback: (payload?: CloseActiveTabPayload) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, payload?: unknown): void => {
|
||||
const admitted = admitCloseActiveTabPayload(payload)
|
||||
if (admitted.kind === 'legacy') {
|
||||
callback()
|
||||
} else if (admitted.kind === 'source') {
|
||||
callback(admitted.payload)
|
||||
}
|
||||
}
|
||||
ipcRenderer.on('ui:closeActiveTab', listener)
|
||||
return () => ipcRenderer.removeListener('ui:closeActiveTab', listener)
|
||||
},
|
||||
onCloseFloatingItem: (callback: (payload: { sourceId: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, payload: { sourceId: string }) =>
|
||||
callback(payload)
|
||||
ipcRenderer.on('ui:closeFloatingItem', listener)
|
||||
return () => ipcRenderer.removeListener('ui:closeFloatingItem', listener)
|
||||
},
|
||||
onSelectFloatingIndex: (callback: (payload: { index: number }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, payload: { index: number }) =>
|
||||
callback(payload)
|
||||
ipcRenderer.on('ui:selectFloatingIndex', listener)
|
||||
return () => ipcRenderer.removeListener('ui:selectFloatingIndex', listener)
|
||||
},
|
||||
onSwitchTab: (callback: (direction: 1 | -1) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, direction: 1 | -1) => callback(direction)
|
||||
ipcRenderer.on('ui:switchTab', listener)
|
||||
return () => ipcRenderer.removeListener('ui:switchTab', listener)
|
||||
},
|
||||
onSwitchTabAcrossAllTypes: (callback: (direction: 1 | -1) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, direction: 1 | -1) => callback(direction)
|
||||
ipcRenderer.on('ui:switchTabAcrossAllTypes', listener)
|
||||
return () => ipcRenderer.removeListener('ui:switchTabAcrossAllTypes', listener)
|
||||
},
|
||||
onSwitchRecentTab: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:switchRecentTab', listener)
|
||||
return () => ipcRenderer.removeListener('ui:switchRecentTab', listener)
|
||||
},
|
||||
onSwitchTerminalTab: (callback: (direction: 1 | -1) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, direction: 1 | -1) => callback(direction)
|
||||
ipcRenderer.on('ui:switchTerminalTab', listener)
|
||||
return () => ipcRenderer.removeListener('ui:switchTerminalTab', listener)
|
||||
},
|
||||
onCtrlTabKeyDown: (callback: (data: { shiftKey: boolean }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { shiftKey: boolean }) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('ui:ctrlTabKeyDown', listener)
|
||||
return () => ipcRenderer.removeListener('ui:ctrlTabKeyDown', listener)
|
||||
},
|
||||
onCtrlTabKeyUp: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:ctrlTabKeyUp', listener)
|
||||
return () => ipcRenderer.removeListener('ui:ctrlTabKeyUp', listener)
|
||||
},
|
||||
onToggleStatusBar: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:toggleStatusBar', listener)
|
||||
return () => ipcRenderer.removeListener('ui:toggleStatusBar', listener)
|
||||
},
|
||||
onExportPdfRequested: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('export:requestPdf', listener)
|
||||
return () => ipcRenderer.removeListener('export:requestPdf', listener)
|
||||
},
|
||||
onAppMenuPaste: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:appMenuPaste', listener)
|
||||
return () => ipcRenderer.removeListener('ui:appMenuPaste', listener)
|
||||
},
|
||||
onAppMenuSelectionAction: (callback: (action: 'copy' | 'select-all') => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, action: 'copy' | 'select-all'): void =>
|
||||
callback(action)
|
||||
ipcRenderer.on('ui:appMenuSelectionAction', listener)
|
||||
return () => ipcRenderer.removeListener('ui:appMenuSelectionAction', listener)
|
||||
},
|
||||
onEditableContextPaste: (callback: (data: { plainTextOnly: boolean }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { plainTextOnly: boolean }): void =>
|
||||
callback({ plainTextOnly: data?.plainTextOnly === true })
|
||||
ipcRenderer.on('ui:editableContextPaste', listener)
|
||||
return () => ipcRenderer.removeListener('ui:editableContextPaste', listener)
|
||||
},
|
||||
onDictationKeyDown: (callback: () => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('ui:dictationKeyDown', listener)
|
||||
return () => ipcRenderer.removeListener('ui:dictationKeyDown', listener)
|
||||
},
|
||||
onActivateWorktree: (
|
||||
callback: (data: {
|
||||
repoId: string
|
||||
worktreeId: string
|
||||
setup?: WorktreeSetupLaunch
|
||||
startup?: { command: string; env?: Record<string, string> }
|
||||
defaultTabs?: WorktreeDefaultTabsLaunch
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
repoId: string
|
||||
worktreeId: string
|
||||
setup?: WorktreeSetupLaunch
|
||||
startup?: { command: string; env?: Record<string, string> }
|
||||
defaultTabs?: WorktreeDefaultTabsLaunch
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ui:activateWorktree', listener)
|
||||
return () => ipcRenderer.removeListener('ui:activateWorktree', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { TerminalPaneSplitSource } from '../../shared/feature-education-telemetry'
|
||||
import type { TerminalTabCreateReply } from '../../shared/terminal-reveal-identity'
|
||||
import type {
|
||||
AgentProviderSessionMetadata,
|
||||
SleepingAgentLaunchConfig
|
||||
} from '../../shared/agent-session-resume'
|
||||
import type { TuiAgent } from '../../shared/tui-agent'
|
||||
import type {
|
||||
RuntimeMobileSessionTabMove,
|
||||
RuntimeTerminalCreateRequestPayload,
|
||||
RuntimeTerminalPresentation
|
||||
} from '../../shared/runtime-types'
|
||||
|
||||
export const uiApiPart3 = {
|
||||
onCreateTerminal: (
|
||||
callback: (data: {
|
||||
requestId?: string
|
||||
worktreeId: string
|
||||
command?: string
|
||||
cwd?: string
|
||||
env?: Record<string, string>
|
||||
launchConfig?: SleepingAgentLaunchConfig
|
||||
resumeProviderSession?: AgentProviderSessionMetadata
|
||||
launchToken?: string
|
||||
launchAgent?: TuiAgent
|
||||
viewMode?: 'terminal' | 'chat'
|
||||
title?: string
|
||||
ptyId?: string
|
||||
activate?: boolean
|
||||
focus?: boolean
|
||||
presentation?: RuntimeTerminalPresentation
|
||||
surfaceOwner?: false
|
||||
tabId?: string
|
||||
leafId?: string
|
||||
splitFromLeafId?: string
|
||||
splitDirection?: 'horizontal' | 'vertical'
|
||||
splitTelemetrySource?: TerminalPaneSplitSource
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
requestId?: string
|
||||
worktreeId: string
|
||||
command?: string
|
||||
cwd?: string
|
||||
env?: Record<string, string>
|
||||
launchConfig?: SleepingAgentLaunchConfig
|
||||
resumeProviderSession?: AgentProviderSessionMetadata
|
||||
launchToken?: string
|
||||
launchAgent?: TuiAgent
|
||||
viewMode?: 'terminal' | 'chat'
|
||||
title?: string
|
||||
ptyId?: string
|
||||
activate?: boolean
|
||||
focus?: boolean
|
||||
presentation?: RuntimeTerminalPresentation
|
||||
surfaceOwner?: false
|
||||
tabId?: string
|
||||
leafId?: string
|
||||
splitFromLeafId?: string
|
||||
splitDirection?: 'horizontal' | 'vertical'
|
||||
splitTelemetrySource?: TerminalPaneSplitSource
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ui:createTerminal', listener)
|
||||
return () => ipcRenderer.removeListener('ui:createTerminal', listener)
|
||||
},
|
||||
onRequestTerminalCreate: (
|
||||
callback: (data: RuntimeTerminalCreateRequestPayload) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: RuntimeTerminalCreateRequestPayload
|
||||
) => callback(data)
|
||||
ipcRenderer.on('terminal:requestTabCreate', listener)
|
||||
return () => ipcRenderer.removeListener('terminal:requestTabCreate', listener)
|
||||
},
|
||||
onRequestTerminalTabMount: (
|
||||
callback: (data: { worktreeId: string; tabId?: string; ptyId?: string }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { worktreeId: string; tabId?: string; ptyId?: string }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('terminal:requestTabMount', listener)
|
||||
return () => ipcRenderer.removeListener('terminal:requestTabMount', listener)
|
||||
},
|
||||
replyTerminalCreate: (reply: TerminalTabCreateReply): void => {
|
||||
ipcRenderer.send('terminal:tabCreateReply', reply)
|
||||
},
|
||||
onSplitTerminal: (
|
||||
callback: (data: {
|
||||
tabId: string
|
||||
paneRuntimeId: number
|
||||
direction: 'horizontal' | 'vertical'
|
||||
command?: string
|
||||
worktreeId?: string
|
||||
sourceLeafId?: string
|
||||
telemetrySource?: TerminalPaneSplitSource
|
||||
newLeafId?: string
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
tabId: string
|
||||
paneRuntimeId: number
|
||||
direction: 'horizontal' | 'vertical'
|
||||
command?: string
|
||||
worktreeId?: string
|
||||
sourceLeafId?: string
|
||||
telemetrySource?: TerminalPaneSplitSource
|
||||
newLeafId?: string
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ui:splitTerminal', listener)
|
||||
return () => ipcRenderer.removeListener('ui:splitTerminal', listener)
|
||||
},
|
||||
onRenameTerminal: (
|
||||
callback: (data: { tabId: string; title: string | null }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { tabId: string; title: string | null }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ui:renameTerminal', listener)
|
||||
return () => ipcRenderer.removeListener('ui:renameTerminal', listener)
|
||||
},
|
||||
onFocusTerminal: (
|
||||
callback: (data: {
|
||||
tabId: string
|
||||
worktreeId: string
|
||||
leafId?: string | null
|
||||
ackPaneKeyOnSuccess?: string
|
||||
flashFocusedPane?: boolean
|
||||
scrollToBottomIfOutputSinceLastView?: boolean
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
tabId: string
|
||||
worktreeId: string
|
||||
leafId?: string | null
|
||||
ackPaneKeyOnSuccess?: string
|
||||
flashFocusedPane?: boolean
|
||||
scrollToBottomIfOutputSinceLastView?: boolean
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ui:focusTerminal', listener)
|
||||
return () => ipcRenderer.removeListener('ui:focusTerminal', listener)
|
||||
},
|
||||
onFocusEditorTab: (
|
||||
callback: (data: { tabId: string; worktreeId: string }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { tabId: string; worktreeId: string }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ui:focusEditorTab', listener)
|
||||
return () => ipcRenderer.removeListener('ui:focusEditorTab', listener)
|
||||
},
|
||||
onCloseSessionTab: (
|
||||
callback: (data: { tabId: string; worktreeId: string }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { tabId: string; worktreeId: string }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ui:closeSessionTab', listener)
|
||||
return () => ipcRenderer.removeListener('ui:closeSessionTab', listener)
|
||||
},
|
||||
onSessionTabCloseRequest: (callback) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, request: Parameters<typeof callback>[0]) =>
|
||||
callback(request)
|
||||
ipcRenderer.on('ui:sessionTabCloseRequest', listener)
|
||||
return () => ipcRenderer.removeListener('ui:sessionTabCloseRequest', listener)
|
||||
},
|
||||
respondSessionTabClose: (response) => {
|
||||
ipcRenderer.send('ui:sessionTabCloseResponse', response)
|
||||
},
|
||||
onMoveSessionTab: (
|
||||
callback: (data: { worktreeId: string } & RuntimeMobileSessionTabMove) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { worktreeId: string } & RuntimeMobileSessionTabMove
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ui:moveSessionTab', listener)
|
||||
return () => ipcRenderer.removeListener('ui:moveSessionTab', listener)
|
||||
},
|
||||
onOpenFileFromMobile: (
|
||||
callback: (data: {
|
||||
worktreeId: string
|
||||
filePath: string
|
||||
relativePath: string
|
||||
runtimeEnvironmentId?: string
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
worktreeId: string
|
||||
filePath: string
|
||||
relativePath: string
|
||||
runtimeEnvironmentId?: string
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ui:openFileFromMobile', listener)
|
||||
return () => ipcRenderer.removeListener('ui:openFileFromMobile', listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import { ipcRenderer, webFrame } from 'electron'
|
||||
import type {
|
||||
RuntimeMobileMarkdownRequest,
|
||||
RuntimeMobileMarkdownResponse
|
||||
} from '../../shared/mobile-markdown-document'
|
||||
import {
|
||||
richMarkdownContextMenuCommandChannel,
|
||||
richMarkdownContextMenuTargetChannel,
|
||||
type RichMarkdownContextMenuCommandPayload,
|
||||
type RichMarkdownContextMenuTableTarget
|
||||
} from '../../shared/rich-markdown-context-menu'
|
||||
import type { NativeFileDropPayload } from '../../shared/native-file-drop'
|
||||
import type { ReadClipboardTextOptions } from '../../shared/clipboard-text'
|
||||
import { subscribeNativeFileDrop } from '../preload-runtime-support'
|
||||
|
||||
export const uiApiPart4 = {
|
||||
onOpenDiffFromMobile: (
|
||||
callback: (data: {
|
||||
worktreeId: string
|
||||
filePath: string
|
||||
relativePath: string
|
||||
staged: boolean
|
||||
runtimeEnvironmentId?: string
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: {
|
||||
worktreeId: string
|
||||
filePath: string
|
||||
relativePath: string
|
||||
staged: boolean
|
||||
runtimeEnvironmentId?: string
|
||||
}
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ui:openDiffFromMobile', listener)
|
||||
return () => ipcRenderer.removeListener('ui:openDiffFromMobile', listener)
|
||||
},
|
||||
onMobileMarkdownRequest: (
|
||||
callback: (request: RuntimeMobileMarkdownRequest) => void
|
||||
): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, request: RuntimeMobileMarkdownRequest) =>
|
||||
callback(request)
|
||||
ipcRenderer.on('ui:mobileMarkdownRequest', listener)
|
||||
return () => ipcRenderer.removeListener('ui:mobileMarkdownRequest', listener)
|
||||
},
|
||||
respondMobileMarkdownRequest: (response: RuntimeMobileMarkdownResponse): void => {
|
||||
ipcRenderer.send('ui:mobileMarkdownResponse', response)
|
||||
},
|
||||
onCloseTerminal: (
|
||||
callback: (data: { tabId: string; paneRuntimeId?: number }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { tabId: string; paneRuntimeId?: number }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('ui:closeTerminal', listener)
|
||||
return () => ipcRenderer.removeListener('ui:closeTerminal', listener)
|
||||
},
|
||||
onTerminalTabCloseRequest: (callback) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, request: Parameters<typeof callback>[0]) =>
|
||||
callback(request)
|
||||
ipcRenderer.on('ui:terminalTabCloseRequest', listener)
|
||||
return () => ipcRenderer.removeListener('ui:terminalTabCloseRequest', listener)
|
||||
},
|
||||
respondTerminalTabClose: (response) => {
|
||||
ipcRenderer.send('ui:terminalTabCloseResponse', response)
|
||||
},
|
||||
onSleepWorktree: (callback: (data: { worktreeId: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { worktreeId: string }) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('ui:sleepWorktree', listener)
|
||||
return () => ipcRenderer.removeListener('ui:sleepWorktree', listener)
|
||||
},
|
||||
onResumeSleepingAgents: (callback: (data: { worktreeId: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { worktreeId: string }) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('ui:resumeSleepingAgents', listener)
|
||||
return () => ipcRenderer.removeListener('ui:resumeSleepingAgents', listener)
|
||||
},
|
||||
onTerminalZoom: (callback: (direction: 'in' | 'out' | 'reset') => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, direction: 'in' | 'out' | 'reset') =>
|
||||
callback(direction)
|
||||
ipcRenderer.on('terminal:zoom', listener)
|
||||
return () => ipcRenderer.removeListener('terminal:zoom', listener)
|
||||
},
|
||||
readClipboardText: (options?: ReadClipboardTextOptions): Promise<string> =>
|
||||
ipcRenderer.invoke('clipboard:readText', options),
|
||||
readSelectionClipboardText: (options?: ReadClipboardTextOptions): Promise<string> =>
|
||||
ipcRenderer.invoke('clipboard:readSelectionText', options),
|
||||
saveClipboardImageAsTempFile: (args?: {
|
||||
connectionId?: string | null
|
||||
runtimeEnvironmentId?: string | null
|
||||
}): Promise<string | null> => ipcRenderer.invoke('clipboard:saveImageAsTempFile', args),
|
||||
writeClipboardText: (text: string): Promise<void> =>
|
||||
ipcRenderer.invoke('clipboard:writeText', text),
|
||||
writeTerminalClipboardText: (text: string): Promise<void> =>
|
||||
ipcRenderer.invoke('clipboard:writeTerminalText', text),
|
||||
writeSelectionClipboardText: (text: string): Promise<void> =>
|
||||
ipcRenderer.invoke('clipboard:writeSelectionText', text),
|
||||
writeClipboardImage: (dataUrl: string): Promise<void> =>
|
||||
ipcRenderer.invoke('clipboard:writeImage', dataUrl),
|
||||
performNativePaste: (options?: { mode?: 'paste' | 'paste-and-match-style' }): void => {
|
||||
ipcRenderer.send('ui:performNativePaste', {
|
||||
mode: options?.mode === 'paste-and-match-style' ? 'paste-and-match-style' : 'paste'
|
||||
})
|
||||
},
|
||||
performNativeSelectionAction: (action: 'copy' | 'select-all'): void => {
|
||||
ipcRenderer.send('ui:performNativeSelectionAction', action)
|
||||
},
|
||||
writeClipboardFile: (
|
||||
args:
|
||||
| {
|
||||
filePath: string
|
||||
connectionId?: string | null
|
||||
}
|
||||
| string
|
||||
): Promise<{ ok: boolean; reason?: string }> => ipcRenderer.invoke('clipboard:writeFile', args),
|
||||
onFileDrop: (callback: (data: NativeFileDropPayload) => void): (() => void) =>
|
||||
subscribeNativeFileDrop(callback),
|
||||
getZoomLevel: (): number => webFrame.getZoomLevel(),
|
||||
setZoomLevel: (level: number): void => webFrame.setZoomLevel(level),
|
||||
syncTrafficLights: (zoomFactor: number): void =>
|
||||
ipcRenderer.send('ui:sync-traffic-lights', zoomFactor),
|
||||
setMarkdownEditorFocused: (focused: boolean): void => {
|
||||
ipcRenderer.send('ui:setMarkdownEditorFocused', focused)
|
||||
},
|
||||
setRichMarkdownContextMenuTarget: (target: RichMarkdownContextMenuTableTarget | null): void => {
|
||||
ipcRenderer.send(richMarkdownContextMenuTargetChannel, target)
|
||||
},
|
||||
setTerminalInputFocused: (focused: boolean): void => {
|
||||
ipcRenderer.send('ui:setTerminalInputFocused', focused)
|
||||
},
|
||||
setFloatingFocus: (state: { panelFocused: boolean; terminalFocused: boolean }): void => {
|
||||
ipcRenderer.send('ui:setFloatingFocus', state)
|
||||
},
|
||||
setShortcutRecorderFocused: (focused: boolean): void => {
|
||||
ipcRenderer.send('ui:setShortcutRecorderFocused', focused)
|
||||
},
|
||||
onRichMarkdownContextCommand: (
|
||||
callback: (payload: RichMarkdownContextMenuCommandPayload) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
payload: RichMarkdownContextMenuCommandPayload
|
||||
) => callback(payload)
|
||||
ipcRenderer.on(richMarkdownContextMenuCommandChannel, listener)
|
||||
return () => ipcRenderer.removeListener(richMarkdownContextMenuCommandChannel, listener)
|
||||
},
|
||||
onFullscreenChanged: (callback: (isFullScreen: boolean) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, isFullScreen: boolean) =>
|
||||
callback(isFullScreen)
|
||||
ipcRenderer.on('window:fullscreen-changed', listener)
|
||||
return () => ipcRenderer.removeListener('window:fullscreen-changed', listener)
|
||||
},
|
||||
onSystemResumed: (callback: () => void): (() => void) => {
|
||||
const listener = () => callback()
|
||||
ipcRenderer.on('system:resumed', listener)
|
||||
return () => ipcRenderer.removeListener('system:resumed', listener)
|
||||
},
|
||||
minimize: (): void => {
|
||||
ipcRenderer.send('window:minimize')
|
||||
},
|
||||
maximize: (): void => {
|
||||
ipcRenderer.send('window:maximize')
|
||||
},
|
||||
isMaximized: (): Promise<boolean> => ipcRenderer.invoke('window:isMaximized'),
|
||||
onMaximizeChanged: (callback: (isMaximized: boolean) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, isMaximized: boolean) =>
|
||||
callback(isMaximized)
|
||||
ipcRenderer.on('window:maximize-changed', listener)
|
||||
return () => ipcRenderer.removeListener('window:maximize-changed', listener)
|
||||
},
|
||||
requestClose: (): void => {
|
||||
ipcRenderer.send('window:request-close')
|
||||
},
|
||||
popupMenu: (): void => {
|
||||
ipcRenderer.send('menu:popup')
|
||||
},
|
||||
onWindowCloseRequested: (callback: (data: { isQuitting: boolean }) => void): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { isQuitting: boolean; requestId?: number }
|
||||
): void => {
|
||||
// Why: main cannot reach will-quit while a frozen renderer owns the window close handshake.
|
||||
ipcRenderer.send('window:close-request-received', data?.requestId)
|
||||
callback({ isQuitting: data?.isQuitting ?? false })
|
||||
}
|
||||
ipcRenderer.on('window:close-requested', listener)
|
||||
return () => ipcRenderer.removeListener('window:close-requested', listener)
|
||||
},
|
||||
confirmWindowClose: (): void => {
|
||||
ipcRenderer.send('window:confirm-close')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const uiApiPart5 = {
|
||||
notifyWindowRevealed: (): void => {
|
||||
ipcRenderer.send('ui:window-revealed')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { PreloadApi } from '../api-types'
|
||||
import { uiApiPart1 } from './ui-bridge-part-1'
|
||||
import { uiApiPart2 } from './ui-bridge-part-2'
|
||||
import { uiApiPart3 } from './ui-bridge-part-3'
|
||||
import { uiApiPart4 } from './ui-bridge-part-4'
|
||||
import { uiApiPart5 } from './ui-bridge-part-5'
|
||||
|
||||
export const uiApi = {
|
||||
...uiApiPart1,
|
||||
...uiApiPart2,
|
||||
...uiApiPart3,
|
||||
...uiApiPart4,
|
||||
...uiApiPart5
|
||||
} satisfies PreloadApi['ui']
|
||||
@@ -0,0 +1,36 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { UpdateStatus } from '../../shared/update-status-types'
|
||||
import { prepareAndInvokeUpdaterInstall } from '../renderer-restart-wiring'
|
||||
import { awaitBeforeUnloadCheckpoint, updaterQuitAbortRelay } from '../preload-runtime-support'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const updaterApi = {
|
||||
getStatus: () => ipcRenderer.invoke('updater:getStatus'),
|
||||
getVersion: () => ipcRenderer.invoke('updater:getVersion'),
|
||||
check: (options) => ipcRenderer.invoke('updater:check', options),
|
||||
download: () => ipcRenderer.invoke('updater:download'),
|
||||
dismissNudge: () => ipcRenderer.invoke('updater:dismissNudge'),
|
||||
dismissAvailableUpdate: () => ipcRenderer.invoke('updater:dismissAvailableUpdate'),
|
||||
getLinuxPackageInstallInstructions: () =>
|
||||
ipcRenderer.invoke('updater:getLinuxPackageInstallInstructions'),
|
||||
showLinuxPackage: () => ipcRenderer.invoke('updater:showLinuxPackage'),
|
||||
listBuilds: (channel) => ipcRenderer.invoke('updater:listBuilds', channel),
|
||||
quitAndInstall: (): Promise<void> =>
|
||||
prepareAndInvokeUpdaterInstall(
|
||||
window,
|
||||
updaterQuitAbortRelay,
|
||||
() => ipcRenderer.invoke('updater:quitAndInstall'),
|
||||
awaitBeforeUnloadCheckpoint
|
||||
),
|
||||
|
||||
onStatus: (callback) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, status: UpdateStatus) => callback(status)
|
||||
ipcRenderer.on('updater:status', listener)
|
||||
return () => ipcRenderer.removeListener('updater:status', listener)
|
||||
},
|
||||
onClearDismissal: (callback) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => callback()
|
||||
ipcRenderer.on('updater:clearDismissal', listener)
|
||||
return () => ipcRenderer.removeListener('updater:clearDismissal', listener)
|
||||
}
|
||||
} satisfies PreloadApi['updater']
|
||||
@@ -0,0 +1,36 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { WorkspaceCleanupScanProgress } from '../../shared/workspace-cleanup'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const workspaceCleanupApi = {
|
||||
scan: (args, onProgress) => {
|
||||
if (!onProgress) {
|
||||
return ipcRenderer.invoke('workspaceCleanup:scan', args)
|
||||
}
|
||||
const scanId = args?.scanId ?? crypto.randomUUID()
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
progress: WorkspaceCleanupScanProgress
|
||||
): void => {
|
||||
if (progress.scanId === scanId) {
|
||||
onProgress(progress)
|
||||
}
|
||||
}
|
||||
ipcRenderer.on('workspaceCleanup:scanProgress', listener)
|
||||
return ipcRenderer
|
||||
.invoke('workspaceCleanup:scan', { ...args, scanId })
|
||||
.finally(() => ipcRenderer.removeListener('workspaceCleanup:scanProgress', listener))
|
||||
},
|
||||
cancelScan: (scanId) => ipcRenderer.invoke('workspaceCleanup:cancelScan', scanId),
|
||||
getCachedScan: () => ipcRenderer.invoke('workspaceCleanup:getCachedScan'),
|
||||
dismiss: (args) => ipcRenderer.invoke('workspaceCleanup:dismiss', args),
|
||||
clearDismissals: () => ipcRenderer.invoke('workspaceCleanup:clearDismissals'),
|
||||
hasKillableLocalProcesses: (args) =>
|
||||
ipcRenderer.invoke('workspaceCleanup:hasKillableLocalProcesses', args),
|
||||
beginRemovalSnapshotPruneBatch: (args) =>
|
||||
ipcRenderer.invoke('workspaceCleanup:beginRemovalSnapshotPruneBatch', args),
|
||||
recordRemovalSnapshotPrune: (args) =>
|
||||
ipcRenderer.invoke('workspaceCleanup:recordRemovalSnapshotPrune', args),
|
||||
finishRemovalSnapshotPruneBatch: (args) =>
|
||||
ipcRenderer.invoke('workspaceCleanup:finishRemovalSnapshotPruneBatch', args)
|
||||
} satisfies PreloadApi['workspaceCleanup']
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { WorkspacePortAdvertisedUrlChangedEvent } from '../../shared/workspace-ports'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const workspacePortsApi = {
|
||||
scan: (args) => ipcRenderer.invoke('workspacePorts:scan', args),
|
||||
kill: (args) => ipcRenderer.invoke('workspacePorts:kill', args),
|
||||
onAdvertisedUrlChanged: (callback) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
event: WorkspacePortAdvertisedUrlChangedEvent
|
||||
): void => callback(event)
|
||||
ipcRenderer.on('workspacePorts:advertised-url-changed', listener)
|
||||
return () => ipcRenderer.removeListener('workspacePorts:advertised-url-changed', listener)
|
||||
}
|
||||
} satisfies PreloadApi['workspacePorts']
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { WorkspaceSpaceScanProgress } from '../../shared/workspace-space-types'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const workspaceSpaceApi = {
|
||||
analyze: () => ipcRenderer.invoke('workspaceSpace:analyze'),
|
||||
getCachedAnalysis: () => ipcRenderer.invoke('workspaceSpace:getCachedAnalysis'),
|
||||
cancel: () => ipcRenderer.invoke('workspaceSpace:cancel'),
|
||||
onProgress: (callback) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
progress: WorkspaceSpaceScanProgress
|
||||
): void => callback(progress)
|
||||
ipcRenderer.on('workspaceSpace:progress', listener)
|
||||
return () => ipcRenderer.removeListener('workspaceSpace:progress', listener)
|
||||
}
|
||||
} satisfies PreloadApi['workspaceSpace']
|
||||
@@ -0,0 +1,117 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type {
|
||||
HostLineageSnapshot,
|
||||
ListDesktopLineageForHostArgs
|
||||
} from '../../shared/host-lineage-contract'
|
||||
import type {
|
||||
WorktreeBaseStatusEvent,
|
||||
WorktreeRemoteBranchConflictEvent
|
||||
} from '../../shared/worktree/base-ref-drift-types'
|
||||
import type { WorktreeHeadIdentity } from '../../shared/worktree/types'
|
||||
import type { PreloadApi } from '../api-types'
|
||||
|
||||
export const worktreesApi = {
|
||||
list: (args) => ipcRenderer.invoke('worktrees:list', args),
|
||||
listRetiredNames: (args) => ipcRenderer.invoke('worktrees:listRetiredNames', args),
|
||||
|
||||
listDetected: (args) => ipcRenderer.invoke('worktrees:listDetected', args),
|
||||
|
||||
listKnownForExecutionHost: (args) =>
|
||||
ipcRenderer.invoke('worktrees:listKnownForExecutionHost', args),
|
||||
|
||||
forgetRemovedForExecutionHost: (args) =>
|
||||
ipcRenderer.invoke('worktrees:forgetRemovedForExecutionHost', args),
|
||||
|
||||
cancelListDetected: (args) => ipcRenderer.invoke('worktrees:cancelListDetected', args),
|
||||
|
||||
listAll: () => ipcRenderer.invoke('worktrees:listAll'),
|
||||
|
||||
create: (args) => ipcRenderer.invoke('worktrees:create', args),
|
||||
|
||||
adoptProvisionedRoot: (args) => ipcRenderer.invoke('worktrees:adoptProvisionedRoot', args),
|
||||
|
||||
onCreateProgress: (
|
||||
callback: (data: { creationId?: string; phase: 'fetching' | 'creating' }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { creationId?: string; phase: 'fetching' | 'creating' }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('createWorktree:progress', listener)
|
||||
return () => ipcRenderer.removeListener('createWorktree:progress', listener)
|
||||
},
|
||||
|
||||
prefetchCreateBase: (args) => ipcRenderer.invoke('worktrees:prefetchCreateBase', args),
|
||||
|
||||
resolvePrBase: (args) => ipcRenderer.invoke('worktrees:resolvePrBase', args),
|
||||
|
||||
resolveMrBase: (args) => ipcRenderer.invoke('worktrees:resolveMrBase', args),
|
||||
|
||||
remove: (args) => ipcRenderer.invoke('worktrees:remove', args),
|
||||
|
||||
forgetLocal: (args) => ipcRenderer.invoke('worktrees:forgetLocal', args),
|
||||
|
||||
forceDeletePreservedBranch: (args) =>
|
||||
ipcRenderer.invoke('worktrees:forceDeletePreservedBranch', args),
|
||||
|
||||
updateMeta: (args) => ipcRenderer.invoke('worktrees:updateMeta', args),
|
||||
|
||||
listLineage: () => ipcRenderer.invoke('worktrees:listLineage'),
|
||||
|
||||
listLineageForHost: (args: ListDesktopLineageForHostArgs): Promise<HostLineageSnapshot> =>
|
||||
ipcRenderer.invoke('worktrees:listLineageForHost', args),
|
||||
|
||||
updateLineage: (args) => ipcRenderer.invoke('worktrees:updateLineage', args),
|
||||
|
||||
persistSortOrder: (args) => ipcRenderer.invoke('worktrees:persistSortOrder', args),
|
||||
|
||||
getBranchRenameFailureOutput: (args) =>
|
||||
ipcRenderer.invoke('worktrees:getBranchRenameFailureOutput', args),
|
||||
|
||||
onChanged: (
|
||||
callback: (data: {
|
||||
repoId: string
|
||||
renamed?: { oldWorktreeId: string; newWorktreeId: string }
|
||||
}) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { repoId: string; renamed?: { oldWorktreeId: string; newWorktreeId: string } }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('worktrees:changed', listener)
|
||||
return () => ipcRenderer.removeListener('worktrees:changed', listener)
|
||||
},
|
||||
|
||||
onGitStatusMetadataChanged: (callback: (data: { repoId: string }) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: { repoId: string }) => callback(data)
|
||||
ipcRenderer.on('worktrees:gitStatusMetadataChanged', listener)
|
||||
return () => ipcRenderer.removeListener('worktrees:gitStatusMetadataChanged', listener)
|
||||
},
|
||||
|
||||
onHeadIdentitiesChanged: (
|
||||
callback: (data: { repoId: string; identities: WorktreeHeadIdentity[] }) => void
|
||||
): (() => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { repoId: string; identities: WorktreeHeadIdentity[] }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('worktrees:headIdentitiesChanged', listener)
|
||||
return () => ipcRenderer.removeListener('worktrees:headIdentitiesChanged', listener)
|
||||
},
|
||||
|
||||
onBaseStatus: (callback: (data: WorktreeBaseStatusEvent) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: WorktreeBaseStatusEvent) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('worktree:baseStatus', listener)
|
||||
return () => ipcRenderer.removeListener('worktree:baseStatus', listener)
|
||||
},
|
||||
|
||||
onRemoteBranchConflict: (
|
||||
callback: (data: WorktreeRemoteBranchConflictEvent) => void
|
||||
): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, data: WorktreeRemoteBranchConflictEvent) =>
|
||||
callback(data)
|
||||
ipcRenderer.on('worktree:remoteBranchConflict', listener)
|
||||
return () => ipcRenderer.removeListener('worktree:remoteBranchConflict', listener)
|
||||
}
|
||||
} satisfies PreloadApi['worktrees']
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export const wslApi = {
|
||||
isAvailable: (): Promise<boolean> => ipcRenderer.invoke('wsl:isAvailable'),
|
||||
listDistros: (): Promise<string[]> => ipcRenderer.invoke('wsl:listDistros')
|
||||
}
|
||||
+178
-5340
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,165 @@
|
||||
import { ipcRenderer, webUtils } from 'electron'
|
||||
import { createBrowserClientPageRendererRequests } from './browser-client-page-renderer-requests'
|
||||
import { createBrowserFindSubscriptions } from './browser-find-subscriptions'
|
||||
import { registerRendererRestartIpcRelays } from './renderer-restart-wiring'
|
||||
import { createUpdaterQuitAbortRelay } from '../shared/renderer-restart-preparation'
|
||||
import { ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT } from '../shared/updater-renderer-events'
|
||||
import {
|
||||
ORCA_INTERNAL_FILE_DRAG_TYPE,
|
||||
createNativeFileDropPayload,
|
||||
createRejectedNativeFileDropPayload,
|
||||
hasNativeFileDragTypes,
|
||||
NATIVE_FILE_DROP_MAX_PATHS,
|
||||
resolveNativeFileDropPath,
|
||||
type NativeDropResolution,
|
||||
type NativeFileDropPayload,
|
||||
type NativeFileDropPathEntry
|
||||
} from '../shared/native-file-drop'
|
||||
|
||||
/** Joins the synchronous unload checkpoint with its durable renderer write. */
|
||||
export async function awaitBeforeUnloadCheckpoint(): Promise<void> {
|
||||
const result = (await ipcRenderer.invoke('app:await-before-unload-checkpoint')) as {
|
||||
ok?: unknown
|
||||
}
|
||||
if (result?.ok !== true) {
|
||||
throw new Error('Failed to persist renderer state before unload.')
|
||||
}
|
||||
}
|
||||
|
||||
export const startupDiagnosticsEnabled = process.env.ORCA_STARTUP_DIAGNOSTICS === '1'
|
||||
|
||||
export function getLinuxDisplayServer(): 'wayland' | 'x11' | null {
|
||||
if (process.platform !== 'linux') {
|
||||
return null
|
||||
}
|
||||
if (
|
||||
process.env.WAYLAND_DISPLAY ||
|
||||
process.env.XDG_SESSION_TYPE?.toLowerCase() === 'wayland' ||
|
||||
process.env.ELECTRON_OZONE_PLATFORM_HINT?.toLowerCase() === 'wayland'
|
||||
) {
|
||||
return 'wayland'
|
||||
}
|
||||
return process.env.DISPLAY ? 'x11' : null
|
||||
}
|
||||
|
||||
type NativeFileDropCallback = (data: NativeFileDropPayload) => void
|
||||
const nativeFileDropCallbacks: NativeFileDropCallback[] = []
|
||||
let nativeFileDropListenerRegistered = false
|
||||
|
||||
const onNativeFileDrop = (_event: Electron.IpcRendererEvent, data: NativeFileDropPayload): void => {
|
||||
for (const callback of Array.from(nativeFileDropCallbacks)) {
|
||||
callback(data)
|
||||
}
|
||||
}
|
||||
|
||||
export function subscribeNativeFileDrop(callback: NativeFileDropCallback): () => void {
|
||||
nativeFileDropCallbacks.push(callback)
|
||||
if (!nativeFileDropListenerRegistered) {
|
||||
ipcRenderer.on('terminal:file-drop', onNativeFileDrop)
|
||||
nativeFileDropListenerRegistered = true
|
||||
}
|
||||
return () => {
|
||||
const callbackIndex = nativeFileDropCallbacks.indexOf(callback)
|
||||
if (callbackIndex !== -1) {
|
||||
nativeFileDropCallbacks.splice(callbackIndex, 1)
|
||||
}
|
||||
if (nativeFileDropCallbacks.length === 0 && nativeFileDropListenerRegistered) {
|
||||
ipcRenderer.removeListener('terminal:file-drop', onNativeFileDrop)
|
||||
nativeFileDropListenerRegistered = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveNativeFileDrop(event: DragEvent): NativeDropResolution | null {
|
||||
const pathEntries: NativeFileDropPathEntry[] = []
|
||||
for (const entry of event.composedPath()) {
|
||||
if (entry instanceof HTMLElement) {
|
||||
pathEntries.push({
|
||||
nativeFileDropTarget: entry.dataset.nativeFileDropTarget,
|
||||
nativeFileDropDir: entry.dataset.nativeFileDropDir,
|
||||
terminalTabId: entry.dataset.terminalTabId,
|
||||
terminalPaneLeafId: entry.dataset.terminalPaneLeafId ?? entry.dataset.leafId
|
||||
})
|
||||
}
|
||||
}
|
||||
return resolveNativeFileDropPath(pathEntries)
|
||||
}
|
||||
|
||||
/** Installs the one preload-side listener that converts native File objects to paths. */
|
||||
export function installNativeFileDropHandlers(): void {
|
||||
document.addEventListener(
|
||||
'dragover',
|
||||
(event) => {
|
||||
if (event.dataTransfer && !hasNativeFileDragTypes(event.dataTransfer.types)) {
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'copy'
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
document.addEventListener(
|
||||
'drop',
|
||||
(event) => {
|
||||
if (event.dataTransfer?.types.includes(ORCA_INTERNAL_FILE_DRAG_TYPE)) {
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const files = event.dataTransfer?.files
|
||||
if (!files || files.length === 0) {
|
||||
return
|
||||
}
|
||||
const resolution = resolveNativeFileDrop(event)
|
||||
if (files.length > NATIVE_FILE_DROP_MAX_PATHS) {
|
||||
ipcRenderer.send(
|
||||
'terminal:file-dropped-from-preload',
|
||||
createRejectedNativeFileDropPayload({
|
||||
byteLength: 0,
|
||||
pathCount: files.length,
|
||||
reason: 'too-many-paths',
|
||||
status: 'rejected'
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
const paths: string[] = []
|
||||
for (let index = 0; index < files.length; index += 1) {
|
||||
const filePath = webUtils.getPathForFile(files[index])
|
||||
if (filePath) {
|
||||
paths.push(filePath)
|
||||
}
|
||||
}
|
||||
if (paths.length === 0 || resolution?.target === 'rejected') {
|
||||
return
|
||||
}
|
||||
const payload = createNativeFileDropPayload(resolution, paths)
|
||||
if (payload) {
|
||||
ipcRenderer.send('terminal:file-dropped-from-preload', payload)
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
export const browserFindSubscriptions = createBrowserFindSubscriptions()
|
||||
export const browserClientPageRendererRequests = createBrowserClientPageRendererRequests({
|
||||
ipc: ipcRenderer,
|
||||
isTopFrame: () => window.top === window
|
||||
})
|
||||
|
||||
/** Registers browser find forwarding once for this preload context. */
|
||||
export function installBrowserFindListener(): void {
|
||||
ipcRenderer.on('ui:findInBrowserPage', (_event, source: unknown) => {
|
||||
browserFindSubscriptions.dispatch(source)
|
||||
})
|
||||
}
|
||||
|
||||
export const updaterQuitAbortRelay = createUpdaterQuitAbortRelay(
|
||||
window,
|
||||
ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT
|
||||
)
|
||||
|
||||
registerRendererRestartIpcRelays(ipcRenderer, window, updaterQuitAbortRelay)
|
||||
Reference in New Issue
Block a user