Files
adiuva/src/renderer/lib/ipcLink.ts
Roberto Musso 6c498c5f40 feat(floating-ai): step 5 — add ai:action IPC side-channel
Add a new ai:action IPC channel so the renderer can react to AI tool
side-effects (task creation, checkpoint/task suggestions). Also mark
AI-created tasks with isAiSuggested: 1 in both project and global
add_task tools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 09:23:04 +01:00

83 lines
2.2 KiB
TypeScript

/**
* Renderer-side tRPC IPC link for Electron.
*
* Replaces electron-trpc's ipcLink with a custom implementation that
* works with our custom IPC handler + tRPC v11.
*/
import { observable } from '@trpc/server/observable';
import type { TRPCLink } from '@trpc/client';
import type { AnyRouter } from '@trpc/server';
interface ElectronTRPC {
sendMessage: (msg: unknown) => void;
onMessage: (cb: (data: unknown) => void) => (() => void) | void;
}
interface ElectronAI {
onStreamChunk: (cb: (data: { token: string; done: boolean }) => void) => () => void;
onAction: (cb: (data: { type: string; taskId?: string; count?: number }) => void) => () => void;
}
declare global {
interface Window {
electronTRPC: ElectronTRPC;
electronAI: ElectronAI;
}
}
type TRPCResponse = {
id: number | null;
result?: { type: string; data?: unknown };
error?: unknown;
};
let nextId = 0;
export function ipcLink<TRouter extends AnyRouter>(): TRPCLink<TRouter> {
return () =>
({ op }) =>
observable((observer) => {
const id = ++nextId;
const { electronTRPC } = window;
if (!electronTRPC) {
observer.error(
new Error(
'Could not find `electronTRPC` global. ' +
'Check that the preload script has been loaded.',
) as any, // eslint-disable-line @typescript-eslint/no-explicit-any
);
return;
}
const unsubscribe = electronTRPC.onMessage((response: unknown) => {
const msg = response as TRPCResponse;
if (msg.id !== id) return;
if ('error' in msg) {
observer.error(msg.error as any); // eslint-disable-line @typescript-eslint/no-explicit-any
return;
}
observer.next({
result: msg.result as { type: 'data'; data: unknown },
});
observer.complete();
});
electronTRPC.sendMessage({
method: 'request',
operation: {
id,
type: op.type,
path: op.path,
input: op.input,
},
});
return () => {
if (typeof unsubscribe === 'function') unsubscribe();
};
});
}