refactor(ipc): generate preload forwarders from registry
This commit is contained in:
1 parent
7478769bfe
commit
9e712f2746
4 files changed
+244
-115
No files matched your search
+16
-115
@@ -1,116 +1,27 @@
|
|||||||
import { contextBridge, ipcRenderer } from "electron";
|
import { contextBridge, ipcRenderer } from "electron";
|
||||||
import {
|
import {
|
||||||
IPC_CHANNELS,
|
IPC_CHANNELS,
|
||||||
|
PRELOAD_FORWARDERS,
|
||||||
|
mapPreloadInvokeArgs,
|
||||||
type DesktopApi,
|
type DesktopApi,
|
||||||
|
type PreloadForwardedApi,
|
||||||
} from "@shared/contracts";
|
} from "@shared/contracts";
|
||||||
import type { DesktopEvent } from "@shared/types";
|
import type { DesktopEvent } from "@shared/types";
|
||||||
|
|
||||||
|
function createForwardedApi(): PreloadForwardedApi {
|
||||||
|
const forwardedEntries = PRELOAD_FORWARDERS.map(({ api, channel, payload }) => [
|
||||||
|
api,
|
||||||
|
(...args: unknown[]) => ipcRenderer.invoke(
|
||||||
|
IPC_CHANNELS[channel],
|
||||||
|
...mapPreloadInvokeArgs(payload, args),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Object.fromEntries(forwardedEntries) as PreloadForwardedApi;
|
||||||
|
}
|
||||||
|
|
||||||
const api: DesktopApi = {
|
const api: DesktopApi = {
|
||||||
loadSnapshot: () => ipcRenderer.invoke(IPC_CHANNELS.loadSnapshot),
|
...createForwardedApi(),
|
||||||
pickDirectory: () => ipcRenderer.invoke(IPC_CHANNELS.pickDirectory),
|
|
||||||
openPath: (targetPath) => ipcRenderer.invoke(IPC_CHANNELS.openPath, targetPath),
|
|
||||||
openWorkProduct: (input) => ipcRenderer.invoke(IPC_CHANNELS.openWorkProduct, input),
|
|
||||||
saveCompany: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveCompany, input),
|
|
||||||
setCurrentCompany: (companyId) => ipcRenderer.invoke(IPC_CHANNELS.setCurrentCompany, companyId),
|
|
||||||
saveConnector: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveConnector, input),
|
|
||||||
testConnector: (id) => ipcRenderer.invoke(IPC_CHANNELS.testConnector, id),
|
|
||||||
saveWorkspace: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveWorkspace, input),
|
|
||||||
saveAgent: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveAgent, input),
|
|
||||||
requestHire: (input) => ipcRenderer.invoke(IPC_CHANNELS.requestHire, input),
|
|
||||||
saveGoal: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveGoal, input),
|
|
||||||
saveProject: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveProject, input),
|
|
||||||
saveTask: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveTask, input),
|
|
||||||
requestApproval: (input) => ipcRenderer.invoke(IPC_CHANNELS.requestApproval, input),
|
|
||||||
decideApproval: (input) => ipcRenderer.invoke(IPC_CHANNELS.decideApproval, input),
|
|
||||||
startTaskRun: (input) => ipcRenderer.invoke(IPC_CHANNELS.startTaskRun, input),
|
|
||||||
cancelRun: (input) => ipcRenderer.invoke(IPC_CHANNELS.cancelRun, input),
|
|
||||||
getRunLog: (input) => ipcRenderer.invoke(IPC_CHANNELS.getRunLog, input),
|
|
||||||
getRunLogChunk: (input) => ipcRenderer.invoke(IPC_CHANNELS.getRunLogChunk, input),
|
|
||||||
backupProfile: () => ipcRenderer.invoke(IPC_CHANNELS.backupProfile),
|
|
||||||
restoreProfile: () => ipcRenderer.invoke(IPC_CHANNELS.restoreProfile),
|
|
||||||
updateSettings: (input) => ipcRenderer.invoke(IPC_CHANNELS.updateSettings, input),
|
|
||||||
checkForUpdates: () => ipcRenderer.invoke(IPC_CHANNELS.checkForUpdates),
|
|
||||||
deleteCompany: (companyId) => ipcRenderer.invoke(IPC_CHANNELS.deleteCompany, companyId),
|
|
||||||
deleteAgent: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteAgent, input),
|
|
||||||
deleteTask: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteTask, input),
|
|
||||||
deleteGoal: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteGoal, input),
|
|
||||||
deleteProject: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteProject, input),
|
|
||||||
deleteWorkspace: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteWorkspace, input),
|
|
||||||
deleteApproval: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteApproval, input),
|
|
||||||
deleteSecret: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteSecret, input),
|
|
||||||
listConnectorModels: (connectorId) => ipcRenderer.invoke(IPC_CHANNELS.listConnectorModels, connectorId),
|
|
||||||
getConnectorDisabledModels: (connectorId) => ipcRenderer.invoke(IPC_CHANNELS.getConnectorDisabledModels, connectorId),
|
|
||||||
setConnectorDisabledModels: (input) => ipcRenderer.invoke(IPC_CHANNELS.setConnectorDisabledModels, input),
|
|
||||||
addComment: (input) => ipcRenderer.invoke(IPC_CHANNELS.addComment, input),
|
|
||||||
setHeartbeat: (input) => ipcRenderer.invoke(IPC_CHANNELS.setHeartbeat, input),
|
|
||||||
triggerHeartbeat: (input) => ipcRenderer.invoke(IPC_CHANNELS.triggerHeartbeat, input),
|
|
||||||
getCompanyMetrics: (input) => ipcRenderer.invoke(IPC_CHANNELS.getCompanyMetrics, input),
|
|
||||||
getStandupReport: (companyId) => ipcRenderer.invoke(IPC_CHANNELS.getStandupReport, companyId),
|
|
||||||
bootstrapOnboarding: (input) => ipcRenderer.invoke(IPC_CHANNELS.bootstrapOnboarding, input),
|
|
||||||
checkNodeInstalled: () => ipcRenderer.invoke(IPC_CHANNELS.checkNodeInstalled),
|
|
||||||
openTerminalCommand: (input) => ipcRenderer.invoke(IPC_CHANNELS.openTerminalCommand, input),
|
|
||||||
saveSocialAccount: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveSocialAccount, input),
|
|
||||||
deleteSocialAccount: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteSocialAccount, input),
|
|
||||||
listBrowserActions: (input) => ipcRenderer.invoke(IPC_CHANNELS.listBrowserActions, input),
|
|
||||||
cancelBrowserAction: (input) => ipcRenderer.invoke(IPC_CHANNELS.cancelBrowserAction, input),
|
|
||||||
triggerBrowserLogin: (input) => ipcRenderer.invoke(IPC_CHANNELS.triggerBrowserLogin, input),
|
|
||||||
saveMeeting: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveMeeting, input),
|
|
||||||
deleteMeeting: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteMeeting, input),
|
|
||||||
saveDocument: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveDocument, input),
|
|
||||||
deleteDocument: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteDocument, input),
|
|
||||||
saveKnowledgeEntry: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveKnowledgeEntry, input),
|
|
||||||
deleteKnowledgeEntry: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteKnowledgeEntry, input),
|
|
||||||
saveSprint: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveSprint, input),
|
|
||||||
deleteSprint: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteSprint, input),
|
|
||||||
saveAutomationRule: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveAutomationRule, input),
|
|
||||||
deleteAutomationRule: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteAutomationRule, input),
|
|
||||||
toggleAutomationRule: (input) => ipcRenderer.invoke(IPC_CHANNELS.toggleAutomationRule, input),
|
|
||||||
getAutomationLog: (companyId) => ipcRenderer.invoke(IPC_CHANNELS.getAutomationLog, companyId),
|
|
||||||
sendAgentMessage: (input) => ipcRenderer.invoke(IPC_CHANNELS.sendAgentMessage, input),
|
|
||||||
searchMessages: (input) => ipcRenderer.invoke(IPC_CHANNELS.searchMessages, input),
|
|
||||||
markMessageRead: (input) => ipcRenderer.invoke(IPC_CHANNELS.markMessageRead, input),
|
|
||||||
saveWorkflow: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveWorkflow, input),
|
|
||||||
deleteWorkflow: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteWorkflow, input),
|
|
||||||
runWorkflow: (input) => ipcRenderer.invoke(IPC_CHANNELS.runWorkflow, input),
|
|
||||||
importSkill: (companyId, source) =>
|
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.importSkill, { companyId, source }),
|
|
||||||
listSkills: (companyId) =>
|
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.listSkills, { companyId }),
|
|
||||||
getSkillDetail: (companyId, skillId) =>
|
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.getSkillDetail, { companyId, skillId }),
|
|
||||||
getSkillMarkdown: (companyId, slug) =>
|
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.getSkillMarkdown, { companyId, slug }),
|
|
||||||
deleteSkill: (companyId, skillId) =>
|
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.deleteSkill, { companyId, skillId }),
|
|
||||||
assignSkillToAgent: (companyId, agentId, skillId) =>
|
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.assignSkillToAgent, { companyId, agentId, skillId }),
|
|
||||||
removeSkillFromAgent: (companyId, agentId, skillId) =>
|
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.removeSkillFromAgent, { companyId, agentId, skillId }),
|
|
||||||
checkSkillUpdate: (companyId, skillId) =>
|
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.checkSkillUpdate, { companyId, skillId }),
|
|
||||||
listSkillAgents: (companyId, skillId) =>
|
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.listSkillAgents, { companyId, skillId }),
|
|
||||||
listAgentSkills: (companyId, agentId) =>
|
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.listAgentSkills, { companyId, agentId }),
|
|
||||||
saveBudgetPolicy: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveBudgetPolicy, input),
|
|
||||||
deleteBudgetPolicy: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteBudgetPolicy, input),
|
|
||||||
listBudgetPolicies: (companyId) => ipcRenderer.invoke(IPC_CHANNELS.listBudgetPolicies, { companyId }),
|
|
||||||
listBudgetIncidents: (input) => ipcRenderer.invoke(IPC_CHANNELS.listBudgetIncidents, input),
|
|
||||||
resolveBudgetIncident: (input) => ipcRenderer.invoke(IPC_CHANNELS.resolveBudgetIncident, input),
|
|
||||||
getBudgetStatus: (companyId) => ipcRenderer.invoke(IPC_CHANNELS.getBudgetStatus, { companyId }),
|
|
||||||
recoveryRetry: (taskId: string, agentId: string, companyId: string) =>
|
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.recoveryRetry, { taskId, agentId, companyId }),
|
|
||||||
recoveryFail: (taskId: string, companyId: string) =>
|
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.recoveryFail, { taskId, companyId }),
|
|
||||||
getTask: (input) => ipcRenderer.invoke(IPC_CHANNELS.getTask, input),
|
|
||||||
getAgent: (input) => ipcRenderer.invoke(IPC_CHANNELS.getAgent, input),
|
|
||||||
getRun: (input) => ipcRenderer.invoke(IPC_CHANNELS.getRun, input),
|
|
||||||
getApproval: (input) => ipcRenderer.invoke(IPC_CHANNELS.getApproval, input),
|
|
||||||
// Thread runtime (read-only)
|
|
||||||
getThread: (input) => ipcRenderer.invoke(IPC_CHANNELS.getThread, input),
|
|
||||||
listThreadEvents: (input) => ipcRenderer.invoke(IPC_CHANNELS.listThreadEvents, input),
|
|
||||||
listThreadWaitStates: (input) => ipcRenderer.invoke(IPC_CHANNELS.listThreadWaitStates, input),
|
|
||||||
listThreadParticipants: (input) => ipcRenderer.invoke(IPC_CHANNELS.listThreadParticipants, input),
|
|
||||||
subscribe: (callback: (event: DesktopEvent) => void) => {
|
subscribe: (callback: (event: DesktopEvent) => void) => {
|
||||||
const listener = (_event: Electron.IpcRendererEvent, payload: DesktopEvent) => callback(payload);
|
const listener = (_event: Electron.IpcRendererEvent, payload: DesktopEvent) => callback(payload);
|
||||||
ipcRenderer.on(IPC_CHANNELS.rendererEvent, listener);
|
ipcRenderer.on(IPC_CHANNELS.rendererEvent, listener);
|
||||||
@@ -132,16 +43,6 @@ const api: DesktopApi = {
|
|||||||
const result = (await ipcRenderer.invoke(IPC_CHANNELS.windowIsMaximized)) as { ok: true; data: boolean } | { ok: false };
|
const result = (await ipcRenderer.invoke(IPC_CHANNELS.windowIsMaximized)) as { ok: true; data: boolean } | { ok: false };
|
||||||
return result.ok ? result.data : false;
|
return result.ok ? result.data : false;
|
||||||
},
|
},
|
||||||
// Credentials (pi-mono)
|
|
||||||
listCredentials: () => ipcRenderer.invoke(IPC_CHANNELS.credentialsList),
|
|
||||||
saveCredential: (input) => ipcRenderer.invoke(IPC_CHANNELS.credentialsSave, input),
|
|
||||||
removeCredential: (input) => ipcRenderer.invoke(IPC_CHANNELS.credentialsRemove, input),
|
|
||||||
testCredential: (input) => ipcRenderer.invoke(IPC_CHANNELS.credentialsTest, input),
|
|
||||||
// OAuth (pi-mono)
|
|
||||||
startOAuth: (input) => ipcRenderer.invoke(IPC_CHANNELS.oauthStart, input),
|
|
||||||
cancelOAuth: (input) => ipcRenderer.invoke(IPC_CHANNELS.oauthCancel, input),
|
|
||||||
submitOAuthPrompt: (input) => ipcRenderer.invoke(IPC_CHANNELS.oauthPromptResponse, input),
|
|
||||||
submitOAuthManualCode: (input) => ipcRenderer.invoke(IPC_CHANNELS.oauthManualCodeResponse, input),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("agentCompany", api);
|
contextBridge.exposeInMainWorld("agentCompany", api);
|
||||||
@@ -8,3 +8,4 @@ export * from "./contracts/schemas-messages";
|
|||||||
export * from "./contracts/schemas-execution";
|
export * from "./contracts/schemas-execution";
|
||||||
export * from "./contracts/schemas-misc";
|
export * from "./contracts/schemas-misc";
|
||||||
export * from "./contracts/desktop-api";
|
export * from "./contracts/desktop-api";
|
||||||
|
export * from "./contracts/preload-forwarders";
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import { IPC_CHANNELS } from "./channels";
|
||||||
|
import type { DesktopApi } from "./desktop-api";
|
||||||
|
|
||||||
|
type DesktopFunctionKey = {
|
||||||
|
[Key in keyof DesktopApi]: DesktopApi[Key] extends (...args: infer _Args) => unknown ? Key : never;
|
||||||
|
}[keyof DesktopApi];
|
||||||
|
|
||||||
|
type IpcChannelKey = keyof typeof IPC_CHANNELS;
|
||||||
|
|
||||||
|
export type PreloadPayloadSpec =
|
||||||
|
| Readonly<{ kind: "none" }>
|
||||||
|
| Readonly<{ kind: "single" }>
|
||||||
|
| Readonly<{ kind: "object"; keys: readonly string[] }>;
|
||||||
|
|
||||||
|
export type PreloadForwarder = Readonly<{
|
||||||
|
api: DesktopFunctionKey;
|
||||||
|
channel: IpcChannelKey;
|
||||||
|
payload: PreloadPayloadSpec;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const noPayload = { kind: "none" } as const;
|
||||||
|
const singlePayload = { kind: "single" } as const;
|
||||||
|
const objectPayload = <const Keys extends readonly string[]>(...keys: Keys) =>
|
||||||
|
({ kind: "object", keys } as const);
|
||||||
|
const forwardSame = <const Api extends Extract<DesktopFunctionKey, IpcChannelKey>>(
|
||||||
|
api: Api,
|
||||||
|
payload: PreloadPayloadSpec,
|
||||||
|
) => ({ api, channel: api, payload } as const);
|
||||||
|
const forwardAlias = <const Api extends DesktopFunctionKey, const Channel extends IpcChannelKey>(
|
||||||
|
api: Api,
|
||||||
|
channel: Channel,
|
||||||
|
payload: PreloadPayloadSpec,
|
||||||
|
) => ({ api, channel, payload } as const);
|
||||||
|
|
||||||
|
export const PRELOAD_FORWARDERS = [
|
||||||
|
forwardSame("loadSnapshot", noPayload),
|
||||||
|
forwardSame("pickDirectory", noPayload),
|
||||||
|
forwardSame("openPath", singlePayload),
|
||||||
|
forwardSame("openWorkProduct", singlePayload),
|
||||||
|
forwardSame("saveCompany", singlePayload),
|
||||||
|
forwardSame("setCurrentCompany", singlePayload),
|
||||||
|
forwardSame("saveConnector", singlePayload),
|
||||||
|
forwardSame("testConnector", singlePayload),
|
||||||
|
forwardSame("saveWorkspace", singlePayload),
|
||||||
|
forwardSame("saveAgent", singlePayload),
|
||||||
|
forwardSame("requestHire", singlePayload),
|
||||||
|
forwardSame("saveGoal", singlePayload),
|
||||||
|
forwardSame("saveProject", singlePayload),
|
||||||
|
forwardSame("saveTask", singlePayload),
|
||||||
|
forwardSame("requestApproval", singlePayload),
|
||||||
|
forwardSame("decideApproval", singlePayload),
|
||||||
|
forwardSame("startTaskRun", singlePayload),
|
||||||
|
forwardSame("cancelRun", singlePayload),
|
||||||
|
forwardSame("getRunLog", singlePayload),
|
||||||
|
forwardSame("getRunLogChunk", singlePayload),
|
||||||
|
forwardSame("backupProfile", noPayload),
|
||||||
|
forwardSame("restoreProfile", noPayload),
|
||||||
|
forwardSame("updateSettings", singlePayload),
|
||||||
|
forwardSame("checkForUpdates", noPayload),
|
||||||
|
forwardSame("deleteCompany", singlePayload),
|
||||||
|
forwardSame("deleteAgent", singlePayload),
|
||||||
|
forwardSame("deleteTask", singlePayload),
|
||||||
|
forwardSame("deleteGoal", singlePayload),
|
||||||
|
forwardSame("deleteProject", singlePayload),
|
||||||
|
forwardSame("deleteWorkspace", singlePayload),
|
||||||
|
forwardSame("deleteApproval", singlePayload),
|
||||||
|
forwardSame("deleteSecret", singlePayload),
|
||||||
|
forwardSame("listConnectorModels", singlePayload),
|
||||||
|
forwardSame("getConnectorDisabledModels", singlePayload),
|
||||||
|
forwardSame("setConnectorDisabledModels", singlePayload),
|
||||||
|
forwardSame("addComment", singlePayload),
|
||||||
|
forwardSame("setHeartbeat", singlePayload),
|
||||||
|
forwardSame("triggerHeartbeat", singlePayload),
|
||||||
|
forwardSame("getCompanyMetrics", singlePayload),
|
||||||
|
forwardSame("getStandupReport", singlePayload),
|
||||||
|
forwardSame("bootstrapOnboarding", singlePayload),
|
||||||
|
forwardSame("checkNodeInstalled", noPayload),
|
||||||
|
forwardSame("openTerminalCommand", singlePayload),
|
||||||
|
forwardSame("saveSocialAccount", singlePayload),
|
||||||
|
forwardSame("deleteSocialAccount", singlePayload),
|
||||||
|
forwardSame("listBrowserActions", singlePayload),
|
||||||
|
forwardSame("cancelBrowserAction", singlePayload),
|
||||||
|
forwardSame("triggerBrowserLogin", singlePayload),
|
||||||
|
forwardSame("saveMeeting", singlePayload),
|
||||||
|
forwardSame("deleteMeeting", singlePayload),
|
||||||
|
forwardSame("saveDocument", singlePayload),
|
||||||
|
forwardSame("deleteDocument", singlePayload),
|
||||||
|
forwardSame("saveKnowledgeEntry", singlePayload),
|
||||||
|
forwardSame("deleteKnowledgeEntry", singlePayload),
|
||||||
|
forwardSame("saveSprint", singlePayload),
|
||||||
|
forwardSame("deleteSprint", singlePayload),
|
||||||
|
forwardSame("saveAutomationRule", singlePayload),
|
||||||
|
forwardSame("deleteAutomationRule", singlePayload),
|
||||||
|
forwardSame("toggleAutomationRule", singlePayload),
|
||||||
|
forwardSame("getAutomationLog", singlePayload),
|
||||||
|
forwardSame("sendAgentMessage", singlePayload),
|
||||||
|
forwardSame("searchMessages", singlePayload),
|
||||||
|
forwardSame("markMessageRead", singlePayload),
|
||||||
|
forwardSame("saveWorkflow", singlePayload),
|
||||||
|
forwardSame("deleteWorkflow", singlePayload),
|
||||||
|
forwardSame("runWorkflow", singlePayload),
|
||||||
|
forwardSame("importSkill", objectPayload("companyId", "source")),
|
||||||
|
forwardSame("listSkills", objectPayload("companyId")),
|
||||||
|
forwardSame("getSkillDetail", objectPayload("companyId", "skillId")),
|
||||||
|
forwardSame("getSkillMarkdown", objectPayload("companyId", "slug")),
|
||||||
|
forwardSame("deleteSkill", objectPayload("companyId", "skillId")),
|
||||||
|
forwardSame("assignSkillToAgent", objectPayload("companyId", "agentId", "skillId")),
|
||||||
|
forwardSame("removeSkillFromAgent", objectPayload("companyId", "agentId", "skillId")),
|
||||||
|
forwardSame("checkSkillUpdate", objectPayload("companyId", "skillId")),
|
||||||
|
forwardSame("listSkillAgents", objectPayload("companyId", "skillId")),
|
||||||
|
forwardSame("listAgentSkills", objectPayload("companyId", "agentId")),
|
||||||
|
forwardSame("saveBudgetPolicy", singlePayload),
|
||||||
|
forwardSame("deleteBudgetPolicy", singlePayload),
|
||||||
|
forwardSame("listBudgetPolicies", objectPayload("companyId")),
|
||||||
|
forwardSame("listBudgetIncidents", singlePayload),
|
||||||
|
forwardSame("resolveBudgetIncident", singlePayload),
|
||||||
|
forwardSame("getBudgetStatus", objectPayload("companyId")),
|
||||||
|
forwardSame("recoveryRetry", objectPayload("taskId", "agentId", "companyId")),
|
||||||
|
forwardSame("recoveryFail", objectPayload("taskId", "companyId")),
|
||||||
|
forwardSame("getTask", singlePayload),
|
||||||
|
forwardSame("getAgent", singlePayload),
|
||||||
|
forwardSame("getRun", singlePayload),
|
||||||
|
forwardSame("getApproval", singlePayload),
|
||||||
|
forwardSame("getThread", singlePayload),
|
||||||
|
forwardSame("listThreadEvents", singlePayload),
|
||||||
|
forwardSame("listThreadWaitStates", singlePayload),
|
||||||
|
forwardSame("listThreadParticipants", singlePayload),
|
||||||
|
forwardAlias("listCredentials", "credentialsList", noPayload),
|
||||||
|
forwardAlias("saveCredential", "credentialsSave", singlePayload),
|
||||||
|
forwardAlias("removeCredential", "credentialsRemove", singlePayload),
|
||||||
|
forwardAlias("testCredential", "credentialsTest", singlePayload),
|
||||||
|
forwardAlias("startOAuth", "oauthStart", singlePayload),
|
||||||
|
forwardAlias("cancelOAuth", "oauthCancel", singlePayload),
|
||||||
|
forwardAlias("submitOAuthPrompt", "oauthPromptResponse", singlePayload),
|
||||||
|
forwardAlias("submitOAuthManualCode", "oauthManualCodeResponse", singlePayload),
|
||||||
|
] as const satisfies readonly PreloadForwarder[];
|
||||||
|
|
||||||
|
export type PreloadForwardedMethod = (typeof PRELOAD_FORWARDERS)[number]["api"];
|
||||||
|
export type PreloadForwardedApi = Pick<DesktopApi, PreloadForwardedMethod>;
|
||||||
|
|
||||||
|
export function mapPreloadInvokeArgs(
|
||||||
|
payload: PreloadPayloadSpec,
|
||||||
|
args: readonly unknown[],
|
||||||
|
): readonly unknown[] {
|
||||||
|
if (payload.kind === "none") {
|
||||||
|
assertArgCount(payload.kind, args.length, 0);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.kind === "single") {
|
||||||
|
assertArgCount(payload.kind, args.length, 1);
|
||||||
|
return [args[0]];
|
||||||
|
}
|
||||||
|
|
||||||
|
assertArgCount(payload.kind, args.length, payload.keys.length);
|
||||||
|
return [Object.fromEntries(payload.keys.map((key, index) => [key, args[index]]))];
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertArgCount(kind: PreloadPayloadSpec["kind"], actual: number, expected: number): void {
|
||||||
|
if (actual !== expected) {
|
||||||
|
throw new Error(`Invalid preload ${kind} argument count: expected ${expected}, received ${actual}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
IPC_CHANNELS,
|
||||||
|
PRELOAD_FORWARDERS,
|
||||||
|
mapPreloadInvokeArgs,
|
||||||
|
type PreloadPayloadSpec,
|
||||||
|
} from "../../src/shared/contracts";
|
||||||
|
|
||||||
|
describe("preload forwarders", () => {
|
||||||
|
it("keeps API methods and IPC channels unique", () => {
|
||||||
|
const apiNames = new Set<string>();
|
||||||
|
const channelNames = new Set<string>();
|
||||||
|
const ipcChannelKeys = new Set(Object.keys(IPC_CHANNELS));
|
||||||
|
|
||||||
|
for (const forwarder of PRELOAD_FORWARDERS) {
|
||||||
|
expect(ipcChannelKeys.has(forwarder.channel)).toBe(true);
|
||||||
|
expect(apiNames.has(forwarder.api)).toBe(false);
|
||||||
|
expect(channelNames.has(forwarder.channel)).toBe(false);
|
||||||
|
apiNames.add(forwarder.api);
|
||||||
|
channelNames.add(forwarder.channel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maps zero, single, and object payloads", () => {
|
||||||
|
const noArgs = PRELOAD_FORWARDERS.find((forwarder) => forwarder.api === "loadSnapshot");
|
||||||
|
const singleArg = PRELOAD_FORWARDERS.find((forwarder) => forwarder.api === "openPath");
|
||||||
|
const objectArg = PRELOAD_FORWARDERS.find((forwarder) => forwarder.api === "importSkill");
|
||||||
|
|
||||||
|
expect(noArgs).toBeDefined();
|
||||||
|
expect(singleArg).toBeDefined();
|
||||||
|
expect(objectArg).toBeDefined();
|
||||||
|
|
||||||
|
expect(mapPreloadInvokeArgs(noArgs!.payload, [])).toEqual([]);
|
||||||
|
expect(mapPreloadInvokeArgs(singleArg!.payload, ["/tmp/output"])).toEqual(["/tmp/output"]);
|
||||||
|
expect(mapPreloadInvokeArgs(objectArg!.payload, ["company-1", "/skills/local"])).toEqual([
|
||||||
|
{ companyId: "company-1", source: "/skills/local" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects payload arity drift", () => {
|
||||||
|
const payload: PreloadPayloadSpec = { kind: "object", keys: ["companyId", "skillId"] };
|
||||||
|
|
||||||
|
expect(() => mapPreloadInvokeArgs(payload, ["company-1"])).toThrow(
|
||||||
|
"Invalid preload object argument count: expected 2, received 1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps preload invoking through the shared registry", () => {
|
||||||
|
const preloadSource = readFileSync(join(process.cwd(), "src/preload/index.ts"), "utf8");
|
||||||
|
const staticChannelAccesses = [...preloadSource.matchAll(/IPC_CHANNELS\.([A-Za-z0-9_]+)/g)]
|
||||||
|
.map((match) => match[1]);
|
||||||
|
|
||||||
|
expect(staticChannelAccesses.sort()).toEqual([
|
||||||
|
"rendererEvent",
|
||||||
|
"rendererEvent",
|
||||||
|
"windowClose",
|
||||||
|
"windowIsMaximized",
|
||||||
|
"windowMaximize",
|
||||||
|
"windowMinimize",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user