diff --git a/apps/server/src/auth/RpcAuthorization.test.ts b/apps/server/src/auth/RpcAuthorization.test.ts index 857c1fbb659c4..9a79f00b347ea 100644 --- a/apps/server/src/auth/RpcAuthorization.test.ts +++ b/apps/server/src/auth/RpcAuthorization.test.ts @@ -150,6 +150,50 @@ describe("RPC scope middleware", () => { ); }); +describe("integration RPC authorization", () => { + it.effect.each([ + WS_METHODS.linearUnlinkThread, + WS_METHODS.notionUnlinkThread, + WS_METHODS.googleUnlinkThread, + WS_METHODS.granolaUnlinkThread, + WS_METHODS.slackUnlinkThread, + WS_METHODS.discordUnlinkThread, + WS_METHODS.githubIssuesUnlinkThread, + ] as const)("requires operate permission for %s", (method) => + Effect.gen(function* () { + const group = WsRpcGroup.omit( + ...[...WsRpcGroup.requests.keys()].filter( + (tag): tag is Exclude => tag !== method, + ), + ); + let mutations = 0; + const handlers = group.toLayerHandler(method, () => + Effect.sync(() => { + mutations++; + return {}; + }), + ); + const clientFor = (scopes: Parameters[0]) => + RpcTest.makeClient(group).pipe( + Effect.provide(Layer.merge(handlers, RpcAuthorization.layer(scopes))), + ); + const input = { threadId: ThreadId.make("linked-thread") }; + const rejected = Effect.gen(function* () { + const reader = yield* clientFor([AuthOrchestrationReadScope]); + return yield* reader[method](input); + }); + expect(yield* rejected.pipe(Effect.flip)).toMatchObject({ + _tag: "EnvironmentAuthorizationError", + requiredPermission: AuthOrchestrationOperateScope, + }); + expect(mutations).toBe(0); + const operator = yield* clientFor([AuthOrchestrationOperateScope]); + expect(yield* operator[method](input)).toEqual({}); + expect(mutations).toBe(1); + }).pipe(Effect.scoped), + ); +}); + describe("settings mutation authorization", () => { const group = WsRpcGroup.omit( ...[...WsRpcGroup.requests.keys()].filter( diff --git a/apps/server/src/integrations/rpcHandlers.ts b/apps/server/src/integrations/rpcHandlers.ts new file mode 100644 index 0000000000000..f8c7f97e7e32b --- /dev/null +++ b/apps/server/src/integrations/rpcHandlers.ts @@ -0,0 +1,135 @@ +import { INTEGRATION_WS_METHODS, IntegrationRpcGroup } from "@t3tools/contracts"; +import * as Effect from "effect/Effect"; +import * as LinearApi from "../linear/LinearApi.ts"; +import * as LinearAuth from "../linear/LinearAuth.ts"; +import * as LinearThreadLinks from "../linear/LinearThreadLinks.ts"; +import * as GranolaApi from "../granola/GranolaApi.ts"; +import * as GranolaAuth from "../granola/GranolaAuth.ts"; +import * as GranolaThreadLinks from "../granola/GranolaThreadLinks.ts"; +import * as NotionApi from "../notion/NotionApi.ts"; +import * as GoogleApi from "../google/GoogleApi.ts"; +import * as NotionAuth from "../notion/NotionAuth.ts"; +import * as GoogleAuth from "../google/GoogleAuth.ts"; +import * as SlackApi from "../slack/SlackApi.ts"; +import * as SlackAuth from "../slack/SlackAuth.ts"; +import * as SlackThreadLinks from "../slack/SlackThreadLinks.ts"; +import * as DiscordApi from "../discord/DiscordApi.ts"; +import * as DiscordAuth from "../discord/DiscordAuth.ts"; +import * as DiscordThreadLinks from "../discord/DiscordThreadLinks.ts"; +import * as ThreadLinkWatches from "../threadLinkWatch/ThreadLinkWatches.ts"; +import * as NotionThreadLinks from "../notion/NotionThreadLinks.ts"; +import * as GoogleThreadLinks from "../google/GoogleThreadLinks.ts"; +import * as GitHubIssues from "../githubIssues/GitHubIssues.ts"; +import * as GitHubIssueThreadLinks from "../githubIssues/GitHubIssueThreadLinks.ts"; + +export const makeIntegrationRpcHandlers = Effect.fn("IntegrationRpc.handlers")(function* () { + const linearAuth = yield* LinearAuth.LinearAuth; + const linearApi = yield* LinearApi.LinearApi; + const linearThreadLinks = yield* LinearThreadLinks.LinearThreadLinks; + const slackAuth = yield* SlackAuth.SlackAuth; + const slackApi = yield* SlackApi.SlackApi; + const notionApi = yield* NotionApi.NotionApi; + const googleApi = yield* GoogleApi.GoogleApi; + const notionAuth = yield* NotionAuth.NotionAuth; + const googleAuth = yield* GoogleAuth.GoogleAuth; + const notionThreadLinks = yield* NotionThreadLinks.NotionThreadLinks; + const googleThreadLinks = yield* GoogleThreadLinks.GoogleThreadLinks; + const granolaApi = yield* GranolaApi.GranolaApi; + const granolaAuth = yield* GranolaAuth.GranolaAuth; + const granolaThreadLinks = yield* GranolaThreadLinks.GranolaThreadLinks; + const slackThreadLinks = yield* SlackThreadLinks.SlackThreadLinks; + const discordAuth = yield* DiscordAuth.DiscordAuth; + const discordApi = yield* DiscordApi.DiscordApi; + const discordThreadLinks = yield* DiscordThreadLinks.DiscordThreadLinks; + const threadLinkWatches = yield* ThreadLinkWatches.ThreadLinkWatches; + const githubIssues = yield* GitHubIssues.GitHubIssues; + const githubIssueThreadLinks = yield* GitHubIssueThreadLinks.GitHubIssueThreadLinks; + return IntegrationRpcGroup.of({ + [INTEGRATION_WS_METHODS.linearSubscribeState]: (_input) => linearAuth.state, + [INTEGRATION_WS_METHODS.linearStartLogin]: (_input) => linearAuth.startLogin, + [INTEGRATION_WS_METHODS.linearCompleteLogin]: (input) => linearAuth.completeLogin(input), + [INTEGRATION_WS_METHODS.linearCancelLogin]: (input) => linearAuth.cancelLogin(input), + [INTEGRATION_WS_METHODS.linearDisconnect]: (_input) => linearAuth.disconnect, + [INTEGRATION_WS_METHODS.linearListIssues]: (input) => linearApi.listIssues(input), + [INTEGRATION_WS_METHODS.linearGetFilterOptions]: (_input) => linearApi.getFilterOptions, + [INTEGRATION_WS_METHODS.linearGetIssue]: (input) => linearApi.getIssue(input), + [INTEGRATION_WS_METHODS.linearGetIssueSummary]: (input) => linearApi.getIssueSummary(input), + [INTEGRATION_WS_METHODS.linearGetObject]: (input) => linearApi.getObject(input), + [INTEGRATION_WS_METHODS.linearSubscribeThreadLinks]: (_input) => linearThreadLinks.links, + [INTEGRATION_WS_METHODS.linearLinkThread]: (input) => linearThreadLinks.link(input), + [INTEGRATION_WS_METHODS.linearUnlinkThread]: (input) => + linearThreadLinks.unlink(input).pipe(Effect.as({})), + [INTEGRATION_WS_METHODS.notionSubscribeState]: (_input) => notionAuth.state, + [INTEGRATION_WS_METHODS.notionStartLogin]: (input) => notionAuth.startLogin(input), + [INTEGRATION_WS_METHODS.notionCompleteLogin]: (input) => notionAuth.completeLogin(input), + [INTEGRATION_WS_METHODS.notionCancelLogin]: (input) => notionAuth.cancelLogin(input), + [INTEGRATION_WS_METHODS.notionDisconnect]: (_input) => notionAuth.disconnect, + [INTEGRATION_WS_METHODS.notionSearchPages]: (input) => notionApi.searchPages(input), + [INTEGRATION_WS_METHODS.notionGetPage]: (input) => notionApi.getPage(input), + [INTEGRATION_WS_METHODS.notionSubscribeThreadLinks]: (_input) => notionThreadLinks.links, + [INTEGRATION_WS_METHODS.notionLinkThread]: (input) => notionThreadLinks.link(input), + [INTEGRATION_WS_METHODS.notionUnlinkThread]: (input) => + notionThreadLinks.unlink(input).pipe(Effect.as({})), + [INTEGRATION_WS_METHODS.googleSubscribeState]: (_input) => googleAuth.state, + [INTEGRATION_WS_METHODS.googleStartLogin]: (input) => googleAuth.startLogin(input), + [INTEGRATION_WS_METHODS.googleCompleteLogin]: (input) => googleAuth.completeLogin(input), + [INTEGRATION_WS_METHODS.googleCancelLogin]: (input) => googleAuth.cancelLogin(input), + [INTEGRATION_WS_METHODS.googleDisconnect]: (_input) => googleAuth.disconnect, + [INTEGRATION_WS_METHODS.googleSearchFiles]: (input) => googleApi.searchFiles(input), + [INTEGRATION_WS_METHODS.googleListCalendars]: (_input) => googleApi.listCalendars, + [INTEGRATION_WS_METHODS.googleSearchPeople]: (input) => googleApi.searchPeople(input), + [INTEGRATION_WS_METHODS.googleGetFile]: (input) => googleApi.getFile(input), + [INTEGRATION_WS_METHODS.googleSubscribeThreadLinks]: (_input) => googleThreadLinks.links, + [INTEGRATION_WS_METHODS.googleLinkThread]: (input) => googleThreadLinks.link(input), + [INTEGRATION_WS_METHODS.googleUnlinkThread]: (input) => + googleThreadLinks.unlink(input).pipe(Effect.as({})), + [INTEGRATION_WS_METHODS.granolaSubscribeState]: (_input) => granolaAuth.state, + [INTEGRATION_WS_METHODS.granolaStartLogin]: (_input) => granolaAuth.startLogin, + [INTEGRATION_WS_METHODS.granolaCancelLogin]: (input) => granolaAuth.cancelLogin(input), + [INTEGRATION_WS_METHODS.granolaDisconnect]: (_input) => granolaAuth.disconnect, + [INTEGRATION_WS_METHODS.granolaSearchNotes]: (input) => granolaApi.searchNotes(input), + [INTEGRATION_WS_METHODS.granolaGetNote]: (input) => granolaApi.getNote(input), + [INTEGRATION_WS_METHODS.granolaGetNoteSummary]: (input) => granolaApi.getNoteSummary(input), + [INTEGRATION_WS_METHODS.granolaSubscribeThreadLinks]: (_input) => granolaThreadLinks.links, + [INTEGRATION_WS_METHODS.granolaLinkThread]: (input) => granolaThreadLinks.link(input), + [INTEGRATION_WS_METHODS.granolaUnlinkThread]: (input) => + granolaThreadLinks.unlink(input).pipe(Effect.as({})), + [INTEGRATION_WS_METHODS.slackSubscribeState]: (_input) => slackAuth.state, + [INTEGRATION_WS_METHODS.slackStartLogin]: (input) => slackAuth.startLogin(input), + [INTEGRATION_WS_METHODS.slackCompleteLogin]: (input) => slackAuth.completeLogin(input), + [INTEGRATION_WS_METHODS.slackCancelLogin]: (input) => slackAuth.cancelLogin(input), + [INTEGRATION_WS_METHODS.slackDisconnect]: (_input) => slackAuth.disconnect, + [INTEGRATION_WS_METHODS.slackSearchMessages]: (input) => slackApi.searchMessages(input), + [INTEGRATION_WS_METHODS.slackSuggestions]: (_input) => slackApi.suggestions, + [INTEGRATION_WS_METHODS.slackGetThread]: (input) => slackApi.getThread(input), + [INTEGRATION_WS_METHODS.slackGetLinkPreview]: (input) => slackApi.getLinkPreview(input), + [INTEGRATION_WS_METHODS.slackSubscribeThreadLinks]: (_input) => slackThreadLinks.links, + [INTEGRATION_WS_METHODS.slackLinkThread]: (input) => slackThreadLinks.link(input), + [INTEGRATION_WS_METHODS.slackUnlinkThread]: (input) => + slackThreadLinks.unlink(input).pipe(Effect.as({})), + [INTEGRATION_WS_METHODS.discordSubscribeState]: (_input) => discordAuth.state, + [INTEGRATION_WS_METHODS.discordConnect]: (input) => discordAuth.connect(input), + [INTEGRATION_WS_METHODS.discordDisconnect]: (_input) => discordAuth.disconnect, + [INTEGRATION_WS_METHODS.discordListServers]: (_input) => discordApi.listServers, + [INTEGRATION_WS_METHODS.discordLeaveServer]: (input) => + discordApi.leaveServer(input).pipe(Effect.as({})), + [INTEGRATION_WS_METHODS.discordSearchMessages]: (input) => discordApi.searchMessages(input), + [INTEGRATION_WS_METHODS.discordGetThread]: (input) => discordApi.getThread(input), + [INTEGRATION_WS_METHODS.discordGetLinkPreview]: (input) => discordApi.getLinkPreview(input), + [INTEGRATION_WS_METHODS.discordSubscribeThreadLinks]: (_input) => discordThreadLinks.links, + [INTEGRATION_WS_METHODS.discordLinkThread]: (input) => discordThreadLinks.link(input), + [INTEGRATION_WS_METHODS.discordUnlinkThread]: (input) => + discordThreadLinks.unlink(input).pipe(Effect.as({})), + [INTEGRATION_WS_METHODS.threadLinkWatchesSubscribe]: (_input) => threadLinkWatches.watches, + [INTEGRATION_WS_METHODS.threadLinkWatchSet]: (input) => threadLinkWatches.set(input), + [INTEGRATION_WS_METHODS.githubIssuesList]: (input) => githubIssues.listIssues(input), + [INTEGRATION_WS_METHODS.githubIssuesGet]: (input) => githubIssues.getIssue(input), + [INTEGRATION_WS_METHODS.githubIssuesGetSummary]: (input) => githubIssues.getIssueSummary(input), + [INTEGRATION_WS_METHODS.githubRefsGet]: (input) => githubIssues.getRef(input), + [INTEGRATION_WS_METHODS.githubIssuesSubscribeThreadLinks]: (_input) => + githubIssueThreadLinks.links, + [INTEGRATION_WS_METHODS.githubIssuesLinkThread]: (input) => githubIssueThreadLinks.link(input), + [INTEGRATION_WS_METHODS.githubIssuesUnlinkThread]: (input) => + githubIssueThreadLinks.unlink(input).pipe(Effect.as({})), + }); +}); diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index 91a1dfd5870a3..275357a6f2533 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -176,27 +176,7 @@ import * as ModelManifest from "./provider/ModelManifest.ts"; import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions"; import { latestProviderVersionCacheKeys } from "@t3tools/provider-core/server/maintenanceResolver"; import * as ProviderMaintenanceRunner from "./provider/providerMaintenanceRunner.ts"; -import { LinearApi } from "./linear/LinearApi.ts"; -import { LinearAuth } from "./linear/LinearAuth.ts"; -import { LinearThreadLinks } from "./linear/LinearThreadLinks.ts"; -import { GranolaApi } from "./granola/GranolaApi.ts"; -import { GranolaAuth } from "./granola/GranolaAuth.ts"; -import { GranolaThreadLinks } from "./granola/GranolaThreadLinks.ts"; -import { NotionApi } from "./notion/NotionApi.ts"; -import { GoogleApi } from "./google/GoogleApi.ts"; -import { NotionAuth } from "./notion/NotionAuth.ts"; -import { GoogleAuth } from "./google/GoogleAuth.ts"; -import { SlackApi } from "./slack/SlackApi.ts"; -import { SlackAuth } from "./slack/SlackAuth.ts"; -import { SlackThreadLinks } from "./slack/SlackThreadLinks.ts"; -import { DiscordApi } from "./discord/DiscordApi.ts"; -import { DiscordAuth } from "./discord/DiscordAuth.ts"; -import { DiscordThreadLinks } from "./discord/DiscordThreadLinks.ts"; -import { ThreadLinkWatches } from "./threadLinkWatch/ThreadLinkWatches.ts"; -import { NotionThreadLinks } from "./notion/NotionThreadLinks.ts"; -import { GoogleThreadLinks } from "./google/GoogleThreadLinks.ts"; -import { GitHubIssues } from "./githubIssues/GitHubIssues.ts"; -import { GitHubIssueThreadLinks } from "./githubIssues/GitHubIssueThreadLinks.ts"; +import { makeIntegrationRpcHandlers } from "./integrations/rpcHandlers.ts"; import * as MessageDrafts from "./messageDrafts/MessageDrafts.ts"; import * as ProviderAuthService from "./provider/ProviderAuthService.ts"; import { makeProviderInstallation } from "./provider/providerInstallation.ts"; @@ -1370,27 +1350,6 @@ export const layerWsRpc = ( yield* AcpRegistryRuntimeCoordinator.AcpRegistryRuntimeCoordinator; const providerMaintenanceRunner = yield* ProviderMaintenanceRunner.ProviderMaintenanceRunner; const providerAuth = yield* ProviderAuthService.ProviderAuthService; - const linearAuth = yield* LinearAuth; - const linearApi = yield* LinearApi; - const linearThreadLinks = yield* LinearThreadLinks; - const slackAuth = yield* SlackAuth; - const slackApi = yield* SlackApi; - const notionApi = yield* NotionApi; - const googleApi = yield* GoogleApi; - const notionAuth = yield* NotionAuth; - const googleAuth = yield* GoogleAuth; - const notionThreadLinks = yield* NotionThreadLinks; - const googleThreadLinks = yield* GoogleThreadLinks; - const granolaApi = yield* GranolaApi; - const granolaAuth = yield* GranolaAuth; - const granolaThreadLinks = yield* GranolaThreadLinks; - const slackThreadLinks = yield* SlackThreadLinks; - const discordAuth = yield* DiscordAuth; - const discordApi = yield* DiscordApi; - const discordThreadLinks = yield* DiscordThreadLinks; - const threadLinkWatches = yield* ThreadLinkWatches; - const githubIssues = yield* GitHubIssues; - const githubIssueThreadLinks = yield* GitHubIssueThreadLinks; const messageDrafts = yield* MessageDrafts.MessageDrafts; const providerInstallation = yield* makeProviderInstallation(); const serverSelfUpdate = yield* ServerSelfUpdate.ServerSelfUpdate; @@ -2868,91 +2827,7 @@ export const layerWsRpc = ( [WS_METHODS.projectCloneRetry]: (input) => projectCloneTracker.retry(input.projectId).pipe(Effect.map((applied) => ({ applied }))), [WS_METHODS.subscribeProjectClones]: () => projectCloneTracker.stream, - [WS_METHODS.linearSubscribeState]: (_input) => linearAuth.state, - [WS_METHODS.linearStartLogin]: (_input) => linearAuth.startLogin, - [WS_METHODS.linearCompleteLogin]: (input) => linearAuth.completeLogin(input), - [WS_METHODS.linearCancelLogin]: (input) => linearAuth.cancelLogin(input), - [WS_METHODS.linearDisconnect]: (_input) => linearAuth.disconnect, - [WS_METHODS.linearListIssues]: (input) => linearApi.listIssues(input), - [WS_METHODS.linearGetFilterOptions]: (_input) => linearApi.getFilterOptions, - [WS_METHODS.linearGetIssue]: (input) => linearApi.getIssue(input), - [WS_METHODS.linearGetIssueSummary]: (input) => linearApi.getIssueSummary(input), - [WS_METHODS.linearGetObject]: (input) => linearApi.getObject(input), - [WS_METHODS.linearSubscribeThreadLinks]: (_input) => linearThreadLinks.links, - [WS_METHODS.linearLinkThread]: (input) => linearThreadLinks.link(input), - [WS_METHODS.linearUnlinkThread]: (input) => - linearThreadLinks.unlink(input).pipe(Effect.as({})), - [WS_METHODS.notionSubscribeState]: (_input) => notionAuth.state, - [WS_METHODS.notionStartLogin]: (input) => notionAuth.startLogin(input), - [WS_METHODS.notionCompleteLogin]: (input) => notionAuth.completeLogin(input), - [WS_METHODS.notionCancelLogin]: (input) => notionAuth.cancelLogin(input), - [WS_METHODS.notionDisconnect]: (_input) => notionAuth.disconnect, - [WS_METHODS.notionSearchPages]: (input) => notionApi.searchPages(input), - [WS_METHODS.notionGetPage]: (input) => notionApi.getPage(input), - [WS_METHODS.notionSubscribeThreadLinks]: (_input) => notionThreadLinks.links, - [WS_METHODS.notionLinkThread]: (input) => notionThreadLinks.link(input), - [WS_METHODS.notionUnlinkThread]: (input) => - notionThreadLinks.unlink(input).pipe(Effect.as({})), - [WS_METHODS.googleSubscribeState]: (_input) => googleAuth.state, - [WS_METHODS.googleStartLogin]: (input) => googleAuth.startLogin(input), - [WS_METHODS.googleCompleteLogin]: (input) => googleAuth.completeLogin(input), - [WS_METHODS.googleCancelLogin]: (input) => googleAuth.cancelLogin(input), - [WS_METHODS.googleDisconnect]: (_input) => googleAuth.disconnect, - [WS_METHODS.googleSearchFiles]: (input) => googleApi.searchFiles(input), - [WS_METHODS.googleListCalendars]: (_input) => googleApi.listCalendars, - [WS_METHODS.googleSearchPeople]: (input) => googleApi.searchPeople(input), - [WS_METHODS.googleGetFile]: (input) => googleApi.getFile(input), - [WS_METHODS.googleSubscribeThreadLinks]: (_input) => googleThreadLinks.links, - [WS_METHODS.googleLinkThread]: (input) => googleThreadLinks.link(input), - [WS_METHODS.googleUnlinkThread]: (input) => - googleThreadLinks.unlink(input).pipe(Effect.as({})), - [WS_METHODS.granolaSubscribeState]: (_input) => granolaAuth.state, - [WS_METHODS.granolaStartLogin]: (_input) => granolaAuth.startLogin, - [WS_METHODS.granolaCancelLogin]: (input) => granolaAuth.cancelLogin(input), - [WS_METHODS.granolaDisconnect]: (_input) => granolaAuth.disconnect, - [WS_METHODS.granolaSearchNotes]: (input) => granolaApi.searchNotes(input), - [WS_METHODS.granolaGetNote]: (input) => granolaApi.getNote(input), - [WS_METHODS.granolaGetNoteSummary]: (input) => granolaApi.getNoteSummary(input), - [WS_METHODS.granolaSubscribeThreadLinks]: (_input) => granolaThreadLinks.links, - [WS_METHODS.granolaLinkThread]: (input) => granolaThreadLinks.link(input), - [WS_METHODS.granolaUnlinkThread]: (input) => - granolaThreadLinks.unlink(input).pipe(Effect.as({})), - [WS_METHODS.slackSubscribeState]: (_input) => slackAuth.state, - [WS_METHODS.slackStartLogin]: (input) => slackAuth.startLogin(input), - [WS_METHODS.slackCompleteLogin]: (input) => slackAuth.completeLogin(input), - [WS_METHODS.slackCancelLogin]: (input) => slackAuth.cancelLogin(input), - [WS_METHODS.slackDisconnect]: (_input) => slackAuth.disconnect, - [WS_METHODS.slackSearchMessages]: (input) => slackApi.searchMessages(input), - [WS_METHODS.slackSuggestions]: (_input) => slackApi.suggestions, - [WS_METHODS.slackGetThread]: (input) => slackApi.getThread(input), - [WS_METHODS.slackGetLinkPreview]: (input) => slackApi.getLinkPreview(input), - [WS_METHODS.slackSubscribeThreadLinks]: (_input) => slackThreadLinks.links, - [WS_METHODS.slackLinkThread]: (input) => slackThreadLinks.link(input), - [WS_METHODS.slackUnlinkThread]: (input) => - slackThreadLinks.unlink(input).pipe(Effect.as({})), - [WS_METHODS.discordSubscribeState]: (_input) => discordAuth.state, - [WS_METHODS.discordConnect]: (input) => discordAuth.connect(input), - [WS_METHODS.discordDisconnect]: (_input) => discordAuth.disconnect, - [WS_METHODS.discordListServers]: (_input) => discordApi.listServers, - [WS_METHODS.discordLeaveServer]: (input) => - discordApi.leaveServer(input).pipe(Effect.as({})), - [WS_METHODS.discordSearchMessages]: (input) => discordApi.searchMessages(input), - [WS_METHODS.discordGetThread]: (input) => discordApi.getThread(input), - [WS_METHODS.discordGetLinkPreview]: (input) => discordApi.getLinkPreview(input), - [WS_METHODS.discordSubscribeThreadLinks]: (_input) => discordThreadLinks.links, - [WS_METHODS.discordLinkThread]: (input) => discordThreadLinks.link(input), - [WS_METHODS.discordUnlinkThread]: (input) => - discordThreadLinks.unlink(input).pipe(Effect.as({})), - [WS_METHODS.threadLinkWatchesSubscribe]: (_input) => threadLinkWatches.watches, - [WS_METHODS.threadLinkWatchSet]: (input) => threadLinkWatches.set(input), - [WS_METHODS.githubIssuesList]: (input) => githubIssues.listIssues(input), - [WS_METHODS.githubIssuesGet]: (input) => githubIssues.getIssue(input), - [WS_METHODS.githubIssuesGetSummary]: (input) => githubIssues.getIssueSummary(input), - [WS_METHODS.githubRefsGet]: (input) => githubIssues.getRef(input), - [WS_METHODS.githubIssuesSubscribeThreadLinks]: (_input) => githubIssueThreadLinks.links, - [WS_METHODS.githubIssuesLinkThread]: (input) => githubIssueThreadLinks.link(input), - [WS_METHODS.githubIssuesUnlinkThread]: (input) => - githubIssueThreadLinks.unlink(input).pipe(Effect.as({})), + ...(yield* makeIntegrationRpcHandlers()), [WS_METHODS.messageDraftsSubscribe]: (_input) => messageDrafts.drafts, [WS_METHODS.messageDraftsSave]: (input) => messageDrafts.save(input), [WS_METHODS.messageDraftsTake]: (input) => messageDrafts.take(input.draftId), diff --git a/apps/web/src/components/CommandPalette.focus.browser.test.tsx b/apps/web/src/components/CommandPalette.focus.browser.test.tsx index 7df2c1204d1ff..254b4f58e58da 100644 --- a/apps/web/src/components/CommandPalette.focus.browser.test.tsx +++ b/apps/web/src/components/CommandPalette.focus.browser.test.tsx @@ -1,18 +1,13 @@ -import { act, useImperativeHandle, useRef } from "react"; -import { createRoot, type Root } from "react-dom/client"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; -import { DEFAULT_RESOLVED_KEYBINDINGS } from "@t3tools/shared/keybindings"; +import { useImperativeHandle, useRef } from "react"; +import { describe, expect, it, vi } from "vite-plus/test"; +import { page, userEvent } from "vite-plus/test/browser"; +import { act, render } from "../../test/render"; +import { isMacPlatform, DEFAULT_RESOLVED_KEYBINDINGS } from "@t3tools/shared/keybindings"; import { CommandPalette } from "./CommandPalette"; import { useComposerHandleContext } from "../composerHandleContext"; import { openCommandPalette } from "../commandPaletteBus"; import { primaryServerKeybindingsAtom } from "../state/server"; -vi.mock("@tanstack/react-router", async (importOriginal) => ({ - ...(await importOriginal()), - useNavigate: () => vi.fn(), - useParams: () => null, - useLocation: () => "/settings", -})); vi.mock("@effect/atom-react", async (importOriginal) => { const original = await importOriginal(); return { @@ -22,6 +17,8 @@ vi.mock("@effect/atom-react", async (importOriginal) => { }; }); vi.mock("../hooks/useTheme", () => ({ + getRenderedAppearance: () => "light", + subscribeRenderedAppearance: () => () => {}, useRenderedAppearance: () => "light", useTheme: () => ({ theme: "default", resolvedTheme: "light", appearanceMode: "light" }), })); @@ -36,7 +33,12 @@ vi.mock("../hooks/useScratchProject", () => ({ })); vi.mock("../hooks/useNewProject", () => ({ useNewProject: () => vi.fn() })); vi.mock("../hooks/useHandleNewThread", () => ({ - useHandleNewThread: () => ({}), + useHandleNewThread: () => ({ + activeThread: null, + activeDraftThread: null, + defaultProjectRef: null, + handleNewThread: vi.fn(), + }), useNewThreadHandler: () => vi.fn(), })); vi.mock("../hooks/useOpenPanelPullRequestUrl", () => ({ useOpenPanelPullRequestUrl: () => null })); @@ -56,7 +58,11 @@ vi.mock("../state/entities", async (importOriginal) => ({ })); vi.mock("../state/queries", async (importOriginal) => ({ ...(await importOriginal()), - useThreadSearch: () => ({ matches: [] }), + useThreadSearch: (_environments: unknown, query: string) => ({ + query, + matches: [], + isPending: false, + }), })); vi.mock("../state/session", async (importOriginal) => ({ ...(await importOriginal()), @@ -70,40 +76,11 @@ vi.mock("./settings/useAvailableSettingsSearchItems", () => ({ useAvailableSettingsSearchItems: () => [], })); -let root: Root; -let container: HTMLDivElement; - -beforeEach(() => { - vi.useFakeTimers(); - vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); - container = document.createElement("div"); - document.body.append(container); - root = createRoot(container); -}); - -afterEach(async () => { - await act(async () => { - root.unmount(); - await vi.runOnlyPendingTimersAsync(); - }); - container.remove(); - vi.unstubAllGlobals(); - vi.useRealTimers(); -}); - -async function flushFocus() { - await act(() => vi.advanceTimersByTimeAsync(0)); - await act(() => vi.advanceTimersToNextFrame()); -} - async function dismissPalette() { - await act(() => - document.activeElement?.dispatchEvent( - new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true }), - ), - ); - await flushFocus(); - expect(document.querySelector("[data-command-palette]")).toBeNull(); + await userEvent.keyboard("{Escape}"); + await expect + .element(page.getByRole("dialog", { name: "Command palette" })) + .not.toBeInTheDocument(); } function Composer() { @@ -124,76 +101,64 @@ function Composer() { describe("command palette focus restoration", () => { it("returns to the previously focused Settings field when no composer is mounted", async () => { - await act(() => - root.render( - - - , - ), + await using screen = await render( + + + , + { router: "/settings" }, ); - const field = container.querySelector("input")!; - await act(() => field.focus()); + const field = screen.getByRole("textbox", { name: "Settings field" }); + await userEvent.click(field); await act(() => openCommandPalette()); - await flushFocus(); - expect(document.activeElement?.closest("[data-command-palette]")).not.toBeNull(); + await expect.element(page.getByRole("combobox")).toHaveFocus(); await dismissPalette(); - expect(document.activeElement).toBe(field); + await expect.element(field).toHaveFocus(); }); it("focuses the replacement command input after leaving the file picker", async () => { - await act(() => - root.render( - - - , - ), + await using screen = await render( + + + , + { router: "/settings" }, ); - const field = container.querySelector("input")!; - await act(() => field.focus()); + const field = screen.getByRole("textbox", { name: "Settings field" }); + await userEvent.click(field); + const mac = isMacPlatform(navigator.platform); await act(() => - field.dispatchEvent( + field.element().dispatchEvent( new KeyboardEvent("keydown", { key: "p", - ctrlKey: true, + ctrlKey: !mac, + metaKey: mac, bubbles: true, cancelable: true, }), ), ); - await flushFocus(); - expect(document.querySelector('[data-palette-mode="files"]')).not.toBeNull(); - await act(() => - document.activeElement?.dispatchEvent( - new KeyboardEvent("keydown", { - key: "Escape", - bubbles: true, - cancelable: true, - }), - ), - ); - await flushFocus(); - expect(document.activeElement?.closest('[data-palette-mode="command"]')).not.toBeNull(); - expect(document.activeElement?.tagName).toBe("INPUT"); + await expect.element(page.getByRole("dialog", { name: "File picker" })).toBeInTheDocument(); + await userEvent.keyboard("{Escape}"); + await expect.element(page.getByRole("dialog", { name: "Command palette" })).toBeInTheDocument(); + await expect.element(page.getByRole("combobox")).toHaveFocus(); await dismissPalette(); - expect(document.activeElement).toBe(field); + await expect.element(field).toHaveFocus(); }); it("focuses the mounted composer at the end instead of the previous field", async () => { - await act(() => - root.render( - - - - , - ), + await using screen = await render( + + + + , + { router: "/settings" }, ); - await act(() => container.querySelector("input")!.focus()); + await userEvent.click(screen.getByRole("textbox", { name: "Previous field" })); await act(() => openCommandPalette()); - await flushFocus(); - expect(document.activeElement?.closest("[data-command-palette]")).not.toBeNull(); + await expect.element(page.getByRole("combobox")).toHaveFocus(); await dismissPalette(); - const composer = container.querySelector("textarea")!; - expect(document.activeElement).toBe(composer); - expect(composer.selectionStart).toBe(composer.value.length); + const composer = screen.getByRole("textbox", { name: "Composer" }); + await expect.element(composer).toHaveFocus(); + const input = composer.element() as HTMLTextAreaElement; + expect(input.selectionStart).toBe(input.value.length); }); }); diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index a61a0bd7edaf3..95bc43b13d475 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -50,21 +50,7 @@ import { resolveEnvironmentMachineKind, } from "@t3tools/contracts"; import { useLocation, useNavigate } from "@tanstack/react-router"; -import { isGitHubProject } from "@t3tools/client-runtime/state/github-issues"; -import { openGitHubIssuePicker } from "./chat/GitHubIssuePicker"; -import { useThreadGitHubIssueLink, useUnlinkGitHubIssue } from "./chat/GitHubIssueThreadLink"; -import { openNotionPagePicker } from "./chat/NotionPagePicker"; -import { openGoogleFilePicker } from "./chat/GoogleFilePicker"; -import { openGranolaNotePicker } from "./chat/GranolaNotePicker"; -import { openSlackMessagePicker } from "./chat/SlackMessagePicker"; -import { openDiscordMessagePicker } from "./chat/DiscordMessagePicker"; -import { openLinearIssuePicker } from "./chat/LinearIssuePicker"; -import { useThreadLinearLink, useUnlinkLinearIssue } from "./chat/LinearThreadLink"; -import { useThreadNotionLink, useUnlinkNotionPage } from "./chat/NotionThreadLink"; -import { useThreadGoogleLink, useUnlinkGoogleFile } from "./chat/GoogleThreadLink"; -import { useThreadGranolaLink, useUnlinkGranolaNote } from "./chat/GranolaThreadLink"; -import { useThreadSlackLink, useUnlinkSlackThread } from "./chat/SlackThreadLink"; -import { useThreadDiscordLink, useUnlinkDiscordThread } from "./chat/DiscordThreadLink"; +import { useIntegrationCommandPaletteActions } from "../integrations/useIntegrationCommandPaletteActions"; import { openThreadAttachPicker } from "./chat/ThreadAttachPicker"; import { openPullRequestAttachPicker } from "./chat/PullRequestAttachPicker"; import { openRepositoryAttachPicker } from "./chat/RepositoryAttachPicker"; @@ -136,13 +122,6 @@ import { filesystemEnvironment, useFilesystemReadAccess } from "../state/filesys import { projectEnvironment } from "../state/projects"; import { useEnvironmentQuery } from "../state/query"; import { serverEnvironment } from "../state/server"; -import { githubIssueEnvironment } from "../state/githubIssues"; -import { linearEnvironment } from "../state/linear"; -import { notionEnvironment } from "../state/notion"; -import { googleEnvironment } from "../state/google"; -import { granolaEnvironment } from "../state/granola"; -import { slackEnvironment } from "../state/slack"; -import { discordEnvironment } from "../state/discord"; import { readEnvironmentScope, useEnvironmentScope } from "~/state/session"; import { sourceControlEnvironment } from "../state/sourceControl"; import { useAtomCommand } from "../state/use-atom-command"; @@ -266,15 +245,6 @@ import { scrollCommandPaletteRowIntoView, } from "./CommandPaletteResults"; import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon"; -import { - DiscordIcon, - GitHubIcon, - GranolaIcon, - LinearIcon, - NotionIcon, - GoogleIcon, - SlackIcon, -} from "./Icons"; import { openImportConversationDialog } from "./ImportConversationDialog"; import { ATTENTION_REASON_PRESENTATION, @@ -661,41 +631,6 @@ function OpenCommandPaletteDialog(props: { : null, ); const runScheduledTask = useRunScheduledTask(); - const activeLinearLink = useThreadLinearLink(activeLinkThreadRef); - const unlinkLinearIssue = useUnlinkLinearIssue(); - const canLinkLinearIssue = useAtomValue( - linearEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), - ); - const activeSlackLink = useThreadSlackLink(activeLinkThreadRef); - const unlinkSlackThread = useUnlinkSlackThread(); - const canLinkSlack = useAtomValue( - slackEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), - ); - const activeDiscordLink = useThreadDiscordLink(activeLinkThreadRef); - const unlinkDiscordThread = useUnlinkDiscordThread(); - const canLinkDiscord = useAtomValue( - discordEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), - ); - const activeNotionLink = useThreadNotionLink(activeLinkThreadRef); - const activeGoogleLink = useThreadGoogleLink(activeLinkThreadRef); - const unlinkNotionPage = useUnlinkNotionPage(); - const unlinkGoogleFile = useUnlinkGoogleFile(); - const canLinkNotion = useAtomValue( - notionEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), - ); - const canLinkGoogle = useAtomValue( - googleEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), - ); - const activeGranolaLink = useThreadGranolaLink(activeLinkThreadRef); - const unlinkGranolaNote = useUnlinkGranolaNote(); - const canLinkGranola = useAtomValue( - granolaEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), - ); - const activeGitHubIssueLink = useThreadGitHubIssueLink(activeLinkThreadRef); - const unlinkGitHubIssue = useUnlinkGitHubIssue(); - const canLinkGitHubIssue = useAtomValue( - githubIssueEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), - ); const serverConfigs = useServerConfigs(); const activeThreadServerConfig = serverConfigs.get( activeThread?.environmentId ?? ("" as EnvironmentId), @@ -894,6 +829,13 @@ function OpenCommandPaletteDialog(props: { const currentProjectEnvironmentId = activeThread?.environmentId ?? activeDraftThread?.environmentId ?? null; const currentProjectId = activeThread?.projectId ?? activeDraftThread?.projectId ?? null; + const integrationActionItems = useIntegrationCommandPaletteActions( + activeLinkThreadRef, + projectByKey.get(`${activeLinkThreadRef?.environmentId ?? ""}:${currentProjectId ?? ""}`), + activeLinkThreadRef + ? serverConfigs.get(activeLinkThreadRef.environmentId)?.settings + : undefined, + ); const scratchTargetEnvironmentId = scratchEnvironmentId( currentProjectEnvironmentId ?? primaryEnvironmentId, ); @@ -1686,7 +1628,6 @@ function OpenCommandPaletteDialog(props: { if (activeLinkThreadRef !== null) { const composerThreadRef = activeLinkThreadRef; - const composerSettings = serverConfigs.get(composerThreadRef.environmentId)?.settings; actionItems.push({ kind: "action", value: "action:attach-thread", @@ -1697,295 +1638,7 @@ function OpenCommandPaletteDialog(props: { openThreadAttachPicker(composerThreadRef); }, }); - actionItems.push({ - kind: "action", - value: "action:attach-linear-issue", - searchTerms: ["linear", "issue", "ticket", "attach", "context"], - title: "Attach Linear issue", - icon: , - run: async () => { - openLinearIssuePicker(composerThreadRef); - }, - }); - if (composerSettings?.enableNotionIntegration !== false) { - actionItems.push({ - kind: "action", - value: "action:attach-notion-page", - searchTerms: ["notion", "page", "document", "attach", "context"], - title: "Attach Notion page", - icon: , - run: async () => { - openNotionPagePicker(composerThreadRef); - }, - }); - } - if (composerSettings?.enableGoogleIntegration !== false) { - actionItems.push({ - kind: "action", - value: "action:attach-google-file", - searchTerms: [ - "google", - "gmail", - "email", - "calendar", - "page", - "document", - "attach", - "context", - ], - title: "Attach Google file, event or email", - icon: , - run: async () => { - openGoogleFilePicker(composerThreadRef); - }, - }); - } - if (composerSettings?.enableGranolaIntegration !== false) { - actionItems.push({ - kind: "action", - value: "action:attach-granola-note", - searchTerms: ["granola", "meeting", "notes", "transcript", "call", "attach", "context"], - title: "Attach Granola note", - icon: , - run: async () => { - openGranolaNotePicker(composerThreadRef); - }, - }); - } - if (composerSettings?.enableSlackIntegration !== false) { - actionItems.push({ - kind: "action", - value: "action:attach-slack-message", - searchTerms: ["slack", "message", "thread", "conversation", "attach", "context"], - title: "Attach Slack message", - icon: , - run: async () => { - openSlackMessagePicker(composerThreadRef); - }, - }); - } - if (composerSettings?.enableDiscordIntegration !== false) { - actionItems.push({ - kind: "action", - value: "action:attach-discord-message", - searchTerms: ["discord", "message", "thread", "conversation", "attach", "context"], - title: "Attach Discord message", - icon: , - run: async () => { - openDiscordMessagePicker(composerThreadRef); - }, - }); - } - // Issues are listed from the project's GitHub repository, so only offer them there. - const composerProject = projectByKey.get( - `${composerThreadRef.environmentId}:${currentProjectId ?? ""}`, - ); - if (isGitHubProject(composerProject)) { - actionItems.push({ - kind: "action", - value: "action:attach-github-issue", - searchTerms: ["github", "issue", "ticket", "attach", "context"], - title: "Attach GitHub issue", - icon: , - run: async () => { - openGitHubIssuePicker(composerThreadRef); - }, - }); - if (canLinkGitHubIssue) { - actionItems.push({ - kind: "action", - value: "action:link-github-issue", - searchTerms: ["github", "issue", "ticket", "link", "thread"], - title: activeGitHubIssueLink - ? `Change linked GitHub issue (#${activeGitHubIssueLink.number})` - : "Link GitHub issue", - icon: , - run: async () => { - openGitHubIssuePicker(composerThreadRef, "link"); - }, - }); - } - } - // Unlinking needs no listing, so it stays offered even if the project stops being on GitHub. - if (canLinkGitHubIssue && activeGitHubIssueLink) { - actionItems.push({ - kind: "action", - value: "action:unlink-github-issue", - searchTerms: ["github", "issue", "ticket", "unlink", "remove"], - title: `Unlink GitHub issue #${activeGitHubIssueLink.number}`, - icon: , - run: async () => { - await unlinkGitHubIssue(composerThreadRef); - }, - }); - } - if (canLinkLinearIssue) { - actionItems.push({ - kind: "action", - value: "action:link-linear-issue", - searchTerms: ["linear", "issue", "ticket", "link", "thread"], - title: activeLinearLink - ? `Change linked Linear issue (${activeLinearLink.identifier})` - : "Link Linear issue", - icon: , - run: async () => { - openLinearIssuePicker(composerThreadRef, "link"); - }, - }); - if (activeLinearLink) { - actionItems.push({ - kind: "action", - value: "action:unlink-linear-issue", - searchTerms: ["linear", "issue", "ticket", "unlink", "remove"], - title: `Unlink Linear issue ${activeLinearLink.identifier}`, - icon: , - run: async () => { - await unlinkLinearIssue(composerThreadRef); - }, - }); - } - } - if (canLinkSlack && composerSettings?.enableSlackIntegration === true) { - actionItems.push({ - kind: "action", - value: "action:link-slack-thread", - searchTerms: ["slack", "thread", "conversation", "link"], - title: activeSlackLink - ? `Change linked Slack thread (${activeSlackLink.channelLabel})` - : "Link Slack thread", - icon: , - run: async () => { - openSlackMessagePicker(composerThreadRef, "link"); - }, - }); - } - // Unlinking stays reachable with the integration off: it is the way out of a link. - if (canLinkSlack && activeSlackLink) { - actionItems.push({ - kind: "action", - value: "action:unlink-slack-thread", - searchTerms: ["slack", "thread", "conversation", "unlink", "remove"], - title: `Unlink Slack thread in ${activeSlackLink.channelLabel}`, - icon: , - run: async () => { - await unlinkSlackThread(composerThreadRef); - }, - }); - } - if (canLinkDiscord && composerSettings?.enableDiscordIntegration === true) { - actionItems.push({ - kind: "action", - value: "action:link-discord-thread", - searchTerms: ["discord", "thread", "conversation", "link"], - title: activeDiscordLink - ? `Change linked Discord thread (${activeDiscordLink.channelLabel})` - : "Link Discord thread", - icon: , - run: async () => { - openDiscordMessagePicker(composerThreadRef, "link"); - }, - }); - } - // Unlinking stays reachable with the integration off: it is the way out of a link. - if (canLinkDiscord && activeDiscordLink) { - actionItems.push({ - kind: "action", - value: "action:unlink-discord-thread", - searchTerms: ["discord", "thread", "conversation", "unlink", "remove"], - title: `Unlink Discord thread in ${activeDiscordLink.channelLabel}`, - icon: , - run: async () => { - await unlinkDiscordThread(composerThreadRef); - }, - }); - } - if (canLinkNotion && composerSettings?.enableNotionIntegration === true) { - actionItems.push({ - kind: "action", - value: "action:link-notion-page", - searchTerms: ["notion", "page", "document", "link"], - title: activeNotionLink - ? `Change linked Notion page (${activeNotionLink.title})` - : "Link Notion page", - icon: , - run: async () => { - openNotionPagePicker(composerThreadRef, "link"); - }, - }); - } - if (canLinkGoogle && composerSettings?.enableGoogleIntegration === true) { - actionItems.push({ - kind: "action", - value: "action:link-google-file", - searchTerms: ["google", "gmail", "email", "calendar", "page", "document", "link"], - title: activeGoogleLink - ? `Change linked Google source (${activeGoogleLink.title})` - : "Link Google file, event or email", - icon: , - run: async () => { - openGoogleFilePicker(composerThreadRef, "link"); - }, - }); - } - if (canLinkNotion && activeNotionLink) { - actionItems.push({ - kind: "action", - value: "action:unlink-notion-page", - searchTerms: ["notion", "page", "document", "unlink", "remove"], - title: `Unlink Notion page ${activeNotionLink.title}`, - icon: , - run: async () => { - await unlinkNotionPage(composerThreadRef); - }, - }); - } - if (canLinkGoogle && activeGoogleLink) { - actionItems.push({ - kind: "action", - value: "action:unlink-google-file", - searchTerms: [ - "google", - "gmail", - "email", - "calendar", - "page", - "document", - "unlink", - "remove", - ], - title: `Unlink Google source ${activeGoogleLink.title}`, - icon: , - run: async () => { - await unlinkGoogleFile(composerThreadRef); - }, - }); - } - if (canLinkGranola && composerSettings?.enableGranolaIntegration === true) { - actionItems.push({ - kind: "action", - value: "action:link-granola-note", - searchTerms: ["granola", "meeting", "notes", "link"], - title: activeGranolaLink - ? `Change linked Granola note (${activeGranolaLink.title})` - : "Link Granola note", - icon: , - run: async () => { - openGranolaNotePicker(composerThreadRef, "link"); - }, - }); - } - if (canLinkGranola && activeGranolaLink) { - actionItems.push({ - kind: "action", - value: "action:unlink-granola-note", - searchTerms: ["granola", "meeting", "notes", "unlink", "remove"], - title: `Unlink Granola note ${activeGranolaLink.title}`, - icon: , - run: async () => { - await unlinkGranolaNote(composerThreadRef); - }, - }); - } + actionItems.push(...integrationActionItems); if ( serverConfigs.get(composerThreadRef.environmentId)?.environment.capabilities.pullRequests === true diff --git a/apps/web/src/integrations/useIntegrationCommandPaletteActions.tsx b/apps/web/src/integrations/useIntegrationCommandPaletteActions.tsx new file mode 100644 index 0000000000000..bbbc256354b50 --- /dev/null +++ b/apps/web/src/integrations/useIntegrationCommandPaletteActions.tsx @@ -0,0 +1,370 @@ +import { useAtomValue } from "@effect/atom-react"; +import type { ScopedThreadRef, ServerSettings } from "@t3tools/contracts"; +import type { Project } from "../types"; +import { ITEM_ICON_CLASS, type CommandPaletteActionItem } from "../components/CommandPalette.logic"; +import { isGitHubProject } from "@t3tools/client-runtime/state/github-issues"; +import { openGitHubIssuePicker } from "../components/chat/GitHubIssuePicker"; +import { + useThreadGitHubIssueLink, + useUnlinkGitHubIssue, +} from "../components/chat/GitHubIssueThreadLink"; +import { openNotionPagePicker } from "../components/chat/NotionPagePicker"; +import { openGoogleFilePicker } from "../components/chat/GoogleFilePicker"; +import { openGranolaNotePicker } from "../components/chat/GranolaNotePicker"; +import { openSlackMessagePicker } from "../components/chat/SlackMessagePicker"; +import { openDiscordMessagePicker } from "../components/chat/DiscordMessagePicker"; +import { openLinearIssuePicker } from "../components/chat/LinearIssuePicker"; +import { useThreadLinearLink, useUnlinkLinearIssue } from "../components/chat/LinearThreadLink"; +import { useThreadNotionLink, useUnlinkNotionPage } from "../components/chat/NotionThreadLink"; +import { useThreadGoogleLink, useUnlinkGoogleFile } from "../components/chat/GoogleThreadLink"; +import { useThreadGranolaLink, useUnlinkGranolaNote } from "../components/chat/GranolaThreadLink"; +import { useThreadSlackLink, useUnlinkSlackThread } from "../components/chat/SlackThreadLink"; +import { useThreadDiscordLink, useUnlinkDiscordThread } from "../components/chat/DiscordThreadLink"; +import { githubIssueEnvironment } from "../state/githubIssues"; +import { linearEnvironment } from "../state/linear"; +import { notionEnvironment } from "../state/notion"; +import { googleEnvironment } from "../state/google"; +import { granolaEnvironment } from "../state/granola"; +import { slackEnvironment } from "../state/slack"; +import { discordEnvironment } from "../state/discord"; +import { + DiscordIcon, + GitHubIcon, + GranolaIcon, + LinearIcon, + NotionIcon, + GoogleIcon, + SlackIcon, +} from "../components/Icons"; + +export function useIntegrationCommandPaletteActions( + activeLinkThreadRef: ScopedThreadRef | null, + composerProject: Project | undefined, + composerSettings: ServerSettings | undefined, +): CommandPaletteActionItem[] { + const activeLinearLink = useThreadLinearLink(activeLinkThreadRef); + const unlinkLinearIssue = useUnlinkLinearIssue(); + const canLinkLinearIssue = useAtomValue( + linearEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), + ); + const activeSlackLink = useThreadSlackLink(activeLinkThreadRef); + const unlinkSlackThread = useUnlinkSlackThread(); + const canLinkSlack = useAtomValue( + slackEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), + ); + const activeDiscordLink = useThreadDiscordLink(activeLinkThreadRef); + const unlinkDiscordThread = useUnlinkDiscordThread(); + const canLinkDiscord = useAtomValue( + discordEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), + ); + const activeNotionLink = useThreadNotionLink(activeLinkThreadRef); + const activeGoogleLink = useThreadGoogleLink(activeLinkThreadRef); + const unlinkNotionPage = useUnlinkNotionPage(); + const unlinkGoogleFile = useUnlinkGoogleFile(); + const canLinkNotion = useAtomValue( + notionEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), + ); + const canLinkGoogle = useAtomValue( + googleEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), + ); + const activeGranolaLink = useThreadGranolaLink(activeLinkThreadRef); + const unlinkGranolaNote = useUnlinkGranolaNote(); + const canLinkGranola = useAtomValue( + granolaEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), + ); + const activeGitHubIssueLink = useThreadGitHubIssueLink(activeLinkThreadRef); + const unlinkGitHubIssue = useUnlinkGitHubIssue(); + const canLinkGitHubIssue = useAtomValue( + githubIssueEnvironment.linkThread.permissionAtom(activeLinkThreadRef?.environmentId ?? null), + ); + const actionItems: CommandPaletteActionItem[] = []; + if (activeLinkThreadRef !== null) { + const composerThreadRef = activeLinkThreadRef; + actionItems.push({ + kind: "action", + value: "action:attach-linear-issue", + searchTerms: ["linear", "issue", "ticket", "attach", "context"], + title: "Attach Linear issue", + icon: , + run: async () => { + openLinearIssuePicker(composerThreadRef); + }, + }); + if (composerSettings?.enableNotionIntegration !== false) { + actionItems.push({ + kind: "action", + value: "action:attach-notion-page", + searchTerms: ["notion", "page", "document", "attach", "context"], + title: "Attach Notion page", + icon: , + run: async () => { + openNotionPagePicker(composerThreadRef); + }, + }); + } + if (composerSettings?.enableGoogleIntegration !== false) { + actionItems.push({ + kind: "action", + value: "action:attach-google-file", + searchTerms: [ + "google", + "gmail", + "email", + "calendar", + "page", + "document", + "attach", + "context", + ], + title: "Attach Google file, event or email", + icon: , + run: async () => { + openGoogleFilePicker(composerThreadRef); + }, + }); + } + if (composerSettings?.enableGranolaIntegration !== false) { + actionItems.push({ + kind: "action", + value: "action:attach-granola-note", + searchTerms: ["granola", "meeting", "notes", "transcript", "call", "attach", "context"], + title: "Attach Granola note", + icon: , + run: async () => { + openGranolaNotePicker(composerThreadRef); + }, + }); + } + if (composerSettings?.enableSlackIntegration !== false) { + actionItems.push({ + kind: "action", + value: "action:attach-slack-message", + searchTerms: ["slack", "message", "thread", "conversation", "attach", "context"], + title: "Attach Slack message", + icon: , + run: async () => { + openSlackMessagePicker(composerThreadRef); + }, + }); + } + if (composerSettings?.enableDiscordIntegration !== false) { + actionItems.push({ + kind: "action", + value: "action:attach-discord-message", + searchTerms: ["discord", "message", "thread", "conversation", "attach", "context"], + title: "Attach Discord message", + icon: , + run: async () => { + openDiscordMessagePicker(composerThreadRef); + }, + }); + } + if (isGitHubProject(composerProject)) { + actionItems.push({ + kind: "action", + value: "action:attach-github-issue", + searchTerms: ["github", "issue", "ticket", "attach", "context"], + title: "Attach GitHub issue", + icon: , + run: async () => { + openGitHubIssuePicker(composerThreadRef); + }, + }); + if (canLinkGitHubIssue) { + actionItems.push({ + kind: "action", + value: "action:link-github-issue", + searchTerms: ["github", "issue", "ticket", "link", "thread"], + title: activeGitHubIssueLink + ? `Change linked GitHub issue (#${activeGitHubIssueLink.number})` + : "Link GitHub issue", + icon: , + run: async () => { + openGitHubIssuePicker(composerThreadRef, "link"); + }, + }); + } + } + // Unlinking needs no listing, so it stays offered even if the project stops being on GitHub. + if (canLinkGitHubIssue && activeGitHubIssueLink) { + actionItems.push({ + kind: "action", + value: "action:unlink-github-issue", + searchTerms: ["github", "issue", "ticket", "unlink", "remove"], + title: `Unlink GitHub issue #${activeGitHubIssueLink.number}`, + icon: , + run: async () => { + await unlinkGitHubIssue(composerThreadRef); + }, + }); + } + if (canLinkLinearIssue) { + actionItems.push({ + kind: "action", + value: "action:link-linear-issue", + searchTerms: ["linear", "issue", "ticket", "link", "thread"], + title: activeLinearLink + ? `Change linked Linear issue (${activeLinearLink.identifier})` + : "Link Linear issue", + icon: , + run: async () => { + openLinearIssuePicker(composerThreadRef, "link"); + }, + }); + if (activeLinearLink) { + actionItems.push({ + kind: "action", + value: "action:unlink-linear-issue", + searchTerms: ["linear", "issue", "ticket", "unlink", "remove"], + title: `Unlink Linear issue ${activeLinearLink.identifier}`, + icon: , + run: async () => { + await unlinkLinearIssue(composerThreadRef); + }, + }); + } + } + if (canLinkSlack && composerSettings?.enableSlackIntegration === true) { + actionItems.push({ + kind: "action", + value: "action:link-slack-thread", + searchTerms: ["slack", "thread", "conversation", "link"], + title: activeSlackLink + ? `Change linked Slack thread (${activeSlackLink.channelLabel})` + : "Link Slack thread", + icon: , + run: async () => { + openSlackMessagePicker(composerThreadRef, "link"); + }, + }); + } + // Unlinking stays reachable with the integration off: it is the way out of a link. + if (canLinkSlack && activeSlackLink) { + actionItems.push({ + kind: "action", + value: "action:unlink-slack-thread", + searchTerms: ["slack", "thread", "conversation", "unlink", "remove"], + title: `Unlink Slack thread in ${activeSlackLink.channelLabel}`, + icon: , + run: async () => { + await unlinkSlackThread(composerThreadRef); + }, + }); + } + if (canLinkDiscord && composerSettings?.enableDiscordIntegration === true) { + actionItems.push({ + kind: "action", + value: "action:link-discord-thread", + searchTerms: ["discord", "thread", "conversation", "link"], + title: activeDiscordLink + ? `Change linked Discord thread (${activeDiscordLink.channelLabel})` + : "Link Discord thread", + icon: , + run: async () => { + openDiscordMessagePicker(composerThreadRef, "link"); + }, + }); + } + // Unlinking stays reachable with the integration off: it is the way out of a link. + if (canLinkDiscord && activeDiscordLink) { + actionItems.push({ + kind: "action", + value: "action:unlink-discord-thread", + searchTerms: ["discord", "thread", "conversation", "unlink", "remove"], + title: `Unlink Discord thread in ${activeDiscordLink.channelLabel}`, + icon: , + run: async () => { + await unlinkDiscordThread(composerThreadRef); + }, + }); + } + if (canLinkNotion && composerSettings?.enableNotionIntegration === true) { + actionItems.push({ + kind: "action", + value: "action:link-notion-page", + searchTerms: ["notion", "page", "document", "link"], + title: activeNotionLink + ? `Change linked Notion page (${activeNotionLink.title})` + : "Link Notion page", + icon: , + run: async () => { + openNotionPagePicker(composerThreadRef, "link"); + }, + }); + } + if (canLinkGoogle && composerSettings?.enableGoogleIntegration === true) { + actionItems.push({ + kind: "action", + value: "action:link-google-file", + searchTerms: ["google", "gmail", "email", "calendar", "page", "document", "link"], + title: activeGoogleLink + ? `Change linked Google source (${activeGoogleLink.title})` + : "Link Google file, event or email", + icon: , + run: async () => { + openGoogleFilePicker(composerThreadRef, "link"); + }, + }); + } + if (canLinkNotion && activeNotionLink) { + actionItems.push({ + kind: "action", + value: "action:unlink-notion-page", + searchTerms: ["notion", "page", "document", "unlink", "remove"], + title: `Unlink Notion page ${activeNotionLink.title}`, + icon: , + run: async () => { + await unlinkNotionPage(composerThreadRef); + }, + }); + } + if (canLinkGoogle && activeGoogleLink) { + actionItems.push({ + kind: "action", + value: "action:unlink-google-file", + searchTerms: [ + "google", + "gmail", + "email", + "calendar", + "page", + "document", + "unlink", + "remove", + ], + title: `Unlink Google source ${activeGoogleLink.title}`, + icon: , + run: async () => { + await unlinkGoogleFile(composerThreadRef); + }, + }); + } + if (canLinkGranola && composerSettings?.enableGranolaIntegration === true) { + actionItems.push({ + kind: "action", + value: "action:link-granola-note", + searchTerms: ["granola", "meeting", "notes", "link"], + title: activeGranolaLink + ? `Change linked Granola note (${activeGranolaLink.title})` + : "Link Granola note", + icon: , + run: async () => { + openGranolaNotePicker(composerThreadRef, "link"); + }, + }); + } + if (canLinkGranola && activeGranolaLink) { + actionItems.push({ + kind: "action", + value: "action:unlink-granola-note", + searchTerms: ["granola", "meeting", "notes", "unlink", "remove"], + title: `Unlink Granola note ${activeGranolaLink.title}`, + icon: , + run: async () => { + await unlinkGranolaNote(composerThreadRef); + }, + }); + } + } + return actionItems; +} diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts index 5004d7b7fa676..4bfe7a255e918 100644 --- a/packages/contracts/src/index.ts +++ b/packages/contracts/src/index.ts @@ -84,3 +84,4 @@ export * from "./rpc.ts"; export * from "./worktreeSetup.ts"; export * from "./secretRequest.ts"; export * from "./clientRpcPermissions.ts"; +export * from "./integrationRpc.ts"; diff --git a/packages/contracts/src/integrationRpc.ts b/packages/contracts/src/integrationRpc.ts new file mode 100644 index 0000000000000..0bac4d648c300 --- /dev/null +++ b/packages/contracts/src/integrationRpc.ts @@ -0,0 +1,767 @@ +import * as Schema from "effect/Schema"; +import * as Rpc from "effect/rpc/Rpc"; +import * as RpcGroup from "effect/rpc/RpcGroup"; +import { EnvironmentAuthorizationError } from "./auth.ts"; +import { + LinearCancelLoginInput, + LinearCompleteLoginInput, + LinearConnectionState, + LinearError, + LinearFilterOptions, + LinearGetIssueInput, + LinearGetObjectInput, + LinearIssueContext, + LinearObjectContext, + LinearIssueSummary, + LinearLinkThreadInput, + LinearListIssuesInput, + LinearListIssuesResult, + LinearThreadLink, + LinearThreadLinks, + LinearUnlinkThreadInput, +} from "./linear.ts"; +import { + NotionError, + NotionConnectionState, + NotionCompleteLoginInput, + NotionStartLoginInput, + NotionCancelLoginInput, + NotionSearchPagesInput, + NotionSearchPagesResult, + NotionGetPageInput, + NotionPageContext, + NotionLinkThreadInput, + NotionThreadLink, + NotionThreadLinks, + NotionUnlinkThreadInput, +} from "./notion.ts"; +import { + GoogleError, + GoogleConnectionState, + GoogleCompleteLoginInput, + GoogleStartLoginInput, + GoogleCancelLoginInput, + GoogleSearchFilesInput, + GoogleSearchFilesResult, + GoogleListCalendarsResult, + GoogleSearchPeopleInput, + GoogleSearchPeopleResult, + GoogleGetFileInput, + GoogleFileContext, + GoogleLinkThreadInput, + GoogleThreadLink, + GoogleThreadLinks, + GoogleUnlinkThreadInput, +} from "./google.ts"; +import { + GranolaCancelLoginInput, + GranolaConnectionState, + GranolaError, + GranolaGetNoteInput, + GranolaLinkThreadInput, + GranolaNoteContext, + GranolaNoteSummary, + GranolaThreadLink, + GranolaThreadLinks, + GranolaUnlinkThreadInput, + GranolaSearchNotesInput, + GranolaSearchNotesResult, +} from "./granola.ts"; +import { + SlackCancelLoginInput, + SlackCompleteLoginInput, + SlackConnectionState, + SlackError, + SlackGetThreadInput, + SlackLinkPreview, + SlackLinkPreviewInput, + SlackSearchMessagesInput, + SlackSearchMessagesResult, + SlackStartLoginInput, + SlackSuggestionsInput, + SlackSuggestionsResult, + SlackThreadContext, + SlackLinkThreadInput, + SlackThreadLink, + SlackThreadLinks, + SlackUnlinkThreadInput, +} from "./slack.ts"; +import { + DiscordConnectInput, + DiscordConnectionState, + DiscordError, + DiscordGetThreadInput, + DiscordLeaveServerInput, + DiscordListServersResult, + DiscordLinkPreview, + DiscordLinkPreviewInput, + DiscordLinkThreadInput, + DiscordSearchMessagesInput, + DiscordSearchMessagesResult, + DiscordThreadContext, + DiscordThreadLink, + DiscordThreadLinks, + DiscordUnlinkThreadInput, +} from "./discord.ts"; +import { + GitHubGetIssueInput, + GitHubIssueContext, + GitHubIssueError, + GitHubIssueSummary, + GitHubIssueThreadLink, + GitHubIssueThreadLinks, + GitHubLinkThreadInput, + GitHubListIssuesInput, + GitHubListIssuesResult, + GitHubUnlinkThreadInput, +} from "./githubIssues.ts"; +import { GitHubGetRefInput, GitHubRefContext } from "./githubRefs.ts"; +import { + ThreadLinkWatch, + ThreadLinkWatchError, + ThreadLinkWatches, + ThreadLinkWatchSetInput, +} from "./threadLinkWatch.ts"; + +export const INTEGRATION_WS_METHODS = { + linearSubscribeState: "linear.subscribeState", + linearStartLogin: "linear.startLogin", + linearCompleteLogin: "linear.completeLogin", + linearCancelLogin: "linear.cancelLogin", + linearDisconnect: "linear.disconnect", + linearListIssues: "linear.listIssues", + linearGetIssue: "linear.getIssue", + linearGetFilterOptions: "linear.getFilterOptions", + linearGetIssueSummary: "linear.getIssueSummary", + linearGetObject: "linear.getObject", + linearSubscribeThreadLinks: "linear.subscribeThreadLinks", + linearLinkThread: "linear.linkThread", + linearUnlinkThread: "linear.unlinkThread", + + notionSubscribeState: "notion.subscribeState", + notionStartLogin: "notion.startLogin", + notionCompleteLogin: "notion.completeLogin", + notionCancelLogin: "notion.cancelLogin", + notionDisconnect: "notion.disconnect", + notionSearchPages: "notion.searchPages", + notionGetPage: "notion.getPage", + notionSubscribeThreadLinks: "notion.subscribeThreadLinks", + notionLinkThread: "notion.linkThread", + notionUnlinkThread: "notion.unlinkThread", + googleSubscribeState: "google.subscribeState", + googleStartLogin: "google.startLogin", + googleCompleteLogin: "google.completeLogin", + googleCancelLogin: "google.cancelLogin", + googleDisconnect: "google.disconnect", + googleSearchFiles: "google.searchFiles", + googleListCalendars: "google.listCalendars", + googleSearchPeople: "google.searchPeople", + googleGetFile: "google.getFile", + googleSubscribeThreadLinks: "google.subscribeThreadLinks", + googleLinkThread: "google.linkThread", + googleUnlinkThread: "google.unlinkThread", + granolaSubscribeState: "granola.subscribeState", + granolaStartLogin: "granola.startLogin", + granolaCancelLogin: "granola.cancelLogin", + granolaDisconnect: "granola.disconnect", + granolaSearchNotes: "granola.searchNotes", + granolaGetNote: "granola.getNote", + granolaGetNoteSummary: "granola.getNoteSummary", + granolaSubscribeThreadLinks: "granola.subscribeThreadLinks", + granolaLinkThread: "granola.linkThread", + granolaUnlinkThread: "granola.unlinkThread", + slackSubscribeState: "slack.subscribeState", + slackStartLogin: "slack.startLogin", + slackCompleteLogin: "slack.completeLogin", + slackCancelLogin: "slack.cancelLogin", + slackDisconnect: "slack.disconnect", + slackSearchMessages: "slack.searchMessages", + slackSuggestions: "slack.suggestions", + slackGetThread: "slack.getThread", + slackGetLinkPreview: "slack.getLinkPreview", + slackSubscribeThreadLinks: "slack.subscribeThreadLinks", + slackLinkThread: "slack.linkThread", + slackUnlinkThread: "slack.unlinkThread", + discordSubscribeState: "discord.subscribeState", + discordConnect: "discord.connect", + discordDisconnect: "discord.disconnect", + discordListServers: "discord.listServers", + discordLeaveServer: "discord.leaveServer", + discordSearchMessages: "discord.searchMessages", + discordGetThread: "discord.getThread", + discordGetLinkPreview: "discord.getLinkPreview", + discordSubscribeThreadLinks: "discord.subscribeThreadLinks", + discordLinkThread: "discord.linkThread", + discordUnlinkThread: "discord.unlinkThread", + threadLinkWatchesSubscribe: "threadLinkWatches.subscribe", + threadLinkWatchSet: "threadLinkWatches.set", + + githubIssuesList: "githubIssues.list", + githubIssuesGet: "githubIssues.get", + githubIssuesGetSummary: "githubIssues.getSummary", + githubIssuesSubscribeThreadLinks: "githubIssues.subscribeThreadLinks", + githubIssuesLinkThread: "githubIssues.linkThread", + githubIssuesUnlinkThread: "githubIssues.unlinkThread", + githubRefsGet: "githubRefs.get", +} as const; + +const LinearRpcError = Schema.Union([LinearError, EnvironmentAuthorizationError]); + +const WsLinearSubscribeStateRpc = Rpc.make(INTEGRATION_WS_METHODS.linearSubscribeState, { + payload: Schema.Struct({}), + success: LinearConnectionState, + error: EnvironmentAuthorizationError, + stream: true, +}); + +const WsLinearStartLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.linearStartLogin, { + payload: Schema.Struct({}), + success: LinearConnectionState, + error: LinearRpcError, +}); + +const WsLinearCompleteLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.linearCompleteLogin, { + payload: LinearCompleteLoginInput, + success: LinearConnectionState, + error: LinearRpcError, +}); + +const WsLinearCancelLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.linearCancelLogin, { + payload: LinearCancelLoginInput, + success: LinearConnectionState, + error: LinearRpcError, +}); + +const WsLinearDisconnectRpc = Rpc.make(INTEGRATION_WS_METHODS.linearDisconnect, { + payload: Schema.Struct({}), + success: LinearConnectionState, + error: LinearRpcError, +}); + +const WsLinearListIssuesRpc = Rpc.make(INTEGRATION_WS_METHODS.linearListIssues, { + payload: LinearListIssuesInput, + success: LinearListIssuesResult, + error: LinearRpcError, +}); + +const WsLinearGetIssueRpc = Rpc.make(INTEGRATION_WS_METHODS.linearGetIssue, { + payload: LinearGetIssueInput, + success: LinearIssueContext, + error: LinearRpcError, +}); + +const WsLinearGetFilterOptionsRpc = Rpc.make(INTEGRATION_WS_METHODS.linearGetFilterOptions, { + payload: Schema.Struct({}), + success: LinearFilterOptions, + error: LinearRpcError, +}); + +const WsLinearGetIssueSummaryRpc = Rpc.make(INTEGRATION_WS_METHODS.linearGetIssueSummary, { + payload: LinearGetIssueInput, + success: LinearIssueSummary, + error: LinearRpcError, +}); + +const WsLinearGetObjectRpc = Rpc.make(INTEGRATION_WS_METHODS.linearGetObject, { + payload: LinearGetObjectInput, + success: LinearObjectContext, + error: LinearRpcError, +}); + +const WsLinearSubscribeThreadLinksRpc = Rpc.make( + INTEGRATION_WS_METHODS.linearSubscribeThreadLinks, + { + payload: Schema.Struct({}), + success: LinearThreadLinks, + error: EnvironmentAuthorizationError, + stream: true, + }, +); + +const WsLinearLinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.linearLinkThread, { + payload: LinearLinkThreadInput, + success: LinearThreadLink, + error: LinearRpcError, +}); + +const WsLinearUnlinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.linearUnlinkThread, { + payload: LinearUnlinkThreadInput, + success: Schema.Struct({}), + error: LinearRpcError, +}); + +const NotionRpcError = Schema.Union([NotionError, EnvironmentAuthorizationError]); +const WsNotionSubscribeStateRpc = Rpc.make(INTEGRATION_WS_METHODS.notionSubscribeState, { + payload: Schema.Struct({}), + success: NotionConnectionState, + error: NotionRpcError, + stream: true, +}); +const WsNotionStartLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.notionStartLogin, { + payload: NotionStartLoginInput, + success: NotionConnectionState, + error: NotionRpcError, +}); +const WsNotionCompleteLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.notionCompleteLogin, { + payload: NotionCompleteLoginInput, + success: NotionConnectionState, + error: NotionRpcError, +}); +const WsNotionCancelLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.notionCancelLogin, { + payload: NotionCancelLoginInput, + success: NotionConnectionState, + error: NotionRpcError, +}); +const WsNotionDisconnectRpc = Rpc.make(INTEGRATION_WS_METHODS.notionDisconnect, { + payload: Schema.Struct({}), + success: NotionConnectionState, + error: NotionRpcError, +}); +const WsNotionSearchPagesRpc = Rpc.make(INTEGRATION_WS_METHODS.notionSearchPages, { + payload: NotionSearchPagesInput, + success: NotionSearchPagesResult, + error: NotionRpcError, +}); +const WsNotionGetPageRpc = Rpc.make(INTEGRATION_WS_METHODS.notionGetPage, { + payload: NotionGetPageInput, + success: NotionPageContext, + error: NotionRpcError, +}); +const WsNotionSubscribeThreadLinksRpc = Rpc.make( + INTEGRATION_WS_METHODS.notionSubscribeThreadLinks, + { + payload: Schema.Struct({}), + success: NotionThreadLinks, + error: EnvironmentAuthorizationError, + stream: true, + }, +); +const WsNotionLinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.notionLinkThread, { + payload: NotionLinkThreadInput, + success: NotionThreadLink, + error: NotionRpcError, +}); +const WsNotionUnlinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.notionUnlinkThread, { + payload: NotionUnlinkThreadInput, + success: Schema.Struct({}), + error: NotionRpcError, +}); + +const GoogleRpcError = Schema.Union([GoogleError, EnvironmentAuthorizationError]); +const WsGoogleSubscribeStateRpc = Rpc.make(INTEGRATION_WS_METHODS.googleSubscribeState, { + payload: Schema.Struct({}), + success: GoogleConnectionState, + error: GoogleRpcError, + stream: true, +}); +const WsGoogleStartLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.googleStartLogin, { + payload: GoogleStartLoginInput, + success: GoogleConnectionState, + error: GoogleRpcError, +}); +const WsGoogleCompleteLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.googleCompleteLogin, { + payload: GoogleCompleteLoginInput, + success: GoogleConnectionState, + error: GoogleRpcError, +}); +const WsGoogleCancelLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.googleCancelLogin, { + payload: GoogleCancelLoginInput, + success: GoogleConnectionState, + error: GoogleRpcError, +}); +const WsGoogleDisconnectRpc = Rpc.make(INTEGRATION_WS_METHODS.googleDisconnect, { + payload: Schema.Struct({}), + success: GoogleConnectionState, + error: GoogleRpcError, +}); +const WsGoogleSearchFilesRpc = Rpc.make(INTEGRATION_WS_METHODS.googleSearchFiles, { + payload: GoogleSearchFilesInput, + success: GoogleSearchFilesResult, + error: GoogleRpcError, +}); +const WsGoogleListCalendarsRpc = Rpc.make(INTEGRATION_WS_METHODS.googleListCalendars, { + payload: Schema.Struct({}), + success: GoogleListCalendarsResult, + error: GoogleRpcError, +}); +const WsGoogleSearchPeopleRpc = Rpc.make(INTEGRATION_WS_METHODS.googleSearchPeople, { + payload: GoogleSearchPeopleInput, + success: GoogleSearchPeopleResult, + error: GoogleRpcError, +}); +const WsGoogleGetFileRpc = Rpc.make(INTEGRATION_WS_METHODS.googleGetFile, { + payload: GoogleGetFileInput, + success: GoogleFileContext, + error: GoogleRpcError, +}); +const WsGoogleSubscribeThreadLinksRpc = Rpc.make( + INTEGRATION_WS_METHODS.googleSubscribeThreadLinks, + { + payload: Schema.Struct({}), + success: GoogleThreadLinks, + error: EnvironmentAuthorizationError, + stream: true, + }, +); +const WsGoogleLinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.googleLinkThread, { + payload: GoogleLinkThreadInput, + success: GoogleThreadLink, + error: GoogleRpcError, +}); +const WsGoogleUnlinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.googleUnlinkThread, { + payload: GoogleUnlinkThreadInput, + success: Schema.Struct({}), + error: GoogleRpcError, +}); + +const GranolaRpcError = Schema.Union([GranolaError, EnvironmentAuthorizationError]); +const WsGranolaSubscribeStateRpc = Rpc.make(INTEGRATION_WS_METHODS.granolaSubscribeState, { + payload: Schema.Struct({}), + success: GranolaConnectionState, + error: GranolaRpcError, + stream: true, +}); +const WsGranolaStartLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.granolaStartLogin, { + payload: Schema.Struct({}), + success: GranolaConnectionState, + error: GranolaRpcError, +}); +const WsGranolaCancelLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.granolaCancelLogin, { + payload: GranolaCancelLoginInput, + success: GranolaConnectionState, + error: GranolaRpcError, +}); +const WsGranolaDisconnectRpc = Rpc.make(INTEGRATION_WS_METHODS.granolaDisconnect, { + payload: Schema.Struct({}), + success: GranolaConnectionState, + error: GranolaRpcError, +}); +const WsGranolaSearchNotesRpc = Rpc.make(INTEGRATION_WS_METHODS.granolaSearchNotes, { + payload: GranolaSearchNotesInput, + success: GranolaSearchNotesResult, + error: GranolaRpcError, +}); +const WsGranolaGetNoteRpc = Rpc.make(INTEGRATION_WS_METHODS.granolaGetNote, { + payload: GranolaGetNoteInput, + success: GranolaNoteContext, + error: GranolaRpcError, +}); +/** A note's title and meeting, for naming a bare note link; the server keeps each one it reads. */ +const WsGranolaGetNoteSummaryRpc = Rpc.make(INTEGRATION_WS_METHODS.granolaGetNoteSummary, { + payload: GranolaGetNoteInput, + success: GranolaNoteSummary, + error: GranolaRpcError, +}); +const WsGranolaSubscribeThreadLinksRpc = Rpc.make( + INTEGRATION_WS_METHODS.granolaSubscribeThreadLinks, + { + payload: Schema.Struct({}), + success: GranolaThreadLinks, + error: EnvironmentAuthorizationError, + stream: true, + }, +); +const WsGranolaLinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.granolaLinkThread, { + payload: GranolaLinkThreadInput, + success: GranolaThreadLink, + error: GranolaRpcError, +}); +const WsGranolaUnlinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.granolaUnlinkThread, { + payload: GranolaUnlinkThreadInput, + success: Schema.Struct({}), + error: GranolaRpcError, +}); + +const SlackRpcError = Schema.Union([SlackError, EnvironmentAuthorizationError]); + +const WsSlackSubscribeStateRpc = Rpc.make(INTEGRATION_WS_METHODS.slackSubscribeState, { + payload: Schema.Struct({}), + success: SlackConnectionState, + error: EnvironmentAuthorizationError, + stream: true, +}); + +const WsSlackStartLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.slackStartLogin, { + payload: SlackStartLoginInput, + success: SlackConnectionState, + error: SlackRpcError, +}); + +const WsSlackCompleteLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.slackCompleteLogin, { + payload: SlackCompleteLoginInput, + success: SlackConnectionState, + error: SlackRpcError, +}); + +const WsSlackCancelLoginRpc = Rpc.make(INTEGRATION_WS_METHODS.slackCancelLogin, { + payload: SlackCancelLoginInput, + success: SlackConnectionState, + error: SlackRpcError, +}); + +const WsSlackDisconnectRpc = Rpc.make(INTEGRATION_WS_METHODS.slackDisconnect, { + payload: Schema.Struct({}), + success: SlackConnectionState, + error: SlackRpcError, +}); + +const WsSlackSearchMessagesRpc = Rpc.make(INTEGRATION_WS_METHODS.slackSearchMessages, { + payload: SlackSearchMessagesInput, + success: SlackSearchMessagesResult, + error: SlackRpcError, +}); + +const WsSlackSuggestionsRpc = Rpc.make(INTEGRATION_WS_METHODS.slackSuggestions, { + payload: SlackSuggestionsInput, + success: SlackSuggestionsResult, + error: SlackRpcError, +}); + +const WsSlackGetThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.slackGetThread, { + payload: SlackGetThreadInput, + success: SlackThreadContext, + error: SlackRpcError, +}); + +const WsSlackGetLinkPreviewRpc = Rpc.make(INTEGRATION_WS_METHODS.slackGetLinkPreview, { + payload: SlackLinkPreviewInput, + success: SlackLinkPreview, + error: SlackRpcError, +}); + +const WsSlackSubscribeThreadLinksRpc = Rpc.make(INTEGRATION_WS_METHODS.slackSubscribeThreadLinks, { + payload: Schema.Struct({}), + success: SlackThreadLinks, + error: EnvironmentAuthorizationError, + stream: true, +}); + +const WsSlackLinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.slackLinkThread, { + payload: SlackLinkThreadInput, + success: SlackThreadLink, + error: SlackRpcError, +}); + +const WsSlackUnlinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.slackUnlinkThread, { + payload: SlackUnlinkThreadInput, + success: Schema.Struct({}), + error: SlackRpcError, +}); + +const DiscordRpcError = Schema.Union([DiscordError, EnvironmentAuthorizationError]); + +const WsDiscordSubscribeStateRpc = Rpc.make(INTEGRATION_WS_METHODS.discordSubscribeState, { + payload: Schema.Struct({}), + success: DiscordConnectionState, + error: EnvironmentAuthorizationError, + stream: true, +}); + +const WsDiscordConnectRpc = Rpc.make(INTEGRATION_WS_METHODS.discordConnect, { + payload: DiscordConnectInput, + success: DiscordConnectionState, + error: DiscordRpcError, +}); + +const WsDiscordDisconnectRpc = Rpc.make(INTEGRATION_WS_METHODS.discordDisconnect, { + payload: Schema.Struct({}), + success: DiscordConnectionState, + error: DiscordRpcError, +}); + +const WsDiscordListServersRpc = Rpc.make(INTEGRATION_WS_METHODS.discordListServers, { + payload: Schema.Struct({}), + success: DiscordListServersResult, + error: DiscordRpcError, +}); + +const WsDiscordLeaveServerRpc = Rpc.make(INTEGRATION_WS_METHODS.discordLeaveServer, { + payload: DiscordLeaveServerInput, + success: Schema.Struct({}), + error: DiscordRpcError, +}); + +const WsDiscordSearchMessagesRpc = Rpc.make(INTEGRATION_WS_METHODS.discordSearchMessages, { + payload: DiscordSearchMessagesInput, + success: DiscordSearchMessagesResult, + error: DiscordRpcError, +}); + +const WsDiscordGetThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.discordGetThread, { + payload: DiscordGetThreadInput, + success: DiscordThreadContext, + error: DiscordRpcError, +}); + +const WsDiscordGetLinkPreviewRpc = Rpc.make(INTEGRATION_WS_METHODS.discordGetLinkPreview, { + payload: DiscordLinkPreviewInput, + success: DiscordLinkPreview, + error: DiscordRpcError, +}); + +const WsDiscordSubscribeThreadLinksRpc = Rpc.make( + INTEGRATION_WS_METHODS.discordSubscribeThreadLinks, + { + payload: Schema.Struct({}), + success: DiscordThreadLinks, + error: EnvironmentAuthorizationError, + stream: true, + }, +); + +const WsDiscordLinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.discordLinkThread, { + payload: DiscordLinkThreadInput, + success: DiscordThreadLink, + error: DiscordRpcError, +}); + +const WsDiscordUnlinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.discordUnlinkThread, { + payload: DiscordUnlinkThreadInput, + success: Schema.Struct({}), + error: DiscordRpcError, +}); + +const WsThreadLinkWatchesSubscribeRpc = Rpc.make( + INTEGRATION_WS_METHODS.threadLinkWatchesSubscribe, + { + payload: Schema.Struct({}), + success: ThreadLinkWatches, + error: EnvironmentAuthorizationError, + stream: true, + }, +); + +/** Starts or stops watching a thread's link; returns the watch, or null once stopped. */ +const WsThreadLinkWatchSetRpc = Rpc.make(INTEGRATION_WS_METHODS.threadLinkWatchSet, { + payload: ThreadLinkWatchSetInput, + success: Schema.NullOr(ThreadLinkWatch), + error: Schema.Union([ThreadLinkWatchError, EnvironmentAuthorizationError]), +}); + +const GitHubIssueRpcError = Schema.Union([GitHubIssueError, EnvironmentAuthorizationError]); + +const WsGitHubIssuesListRpc = Rpc.make(INTEGRATION_WS_METHODS.githubIssuesList, { + payload: GitHubListIssuesInput, + success: GitHubListIssuesResult, + error: GitHubIssueRpcError, +}); + +const WsGitHubIssuesGetRpc = Rpc.make(INTEGRATION_WS_METHODS.githubIssuesGet, { + payload: GitHubGetIssueInput, + success: GitHubIssueContext, + error: GitHubIssueRpcError, +}); + +const WsGitHubIssuesGetSummaryRpc = Rpc.make(INTEGRATION_WS_METHODS.githubIssuesGetSummary, { + payload: GitHubGetIssueInput, + success: GitHubIssueSummary, + error: GitHubIssueRpcError, +}); + +const WsGitHubRefsGetRpc = Rpc.make(INTEGRATION_WS_METHODS.githubRefsGet, { + payload: GitHubGetRefInput, + success: GitHubRefContext, + error: GitHubIssueRpcError, +}); + +const WsGitHubIssuesSubscribeThreadLinksRpc = Rpc.make( + INTEGRATION_WS_METHODS.githubIssuesSubscribeThreadLinks, + { + payload: Schema.Struct({}), + success: GitHubIssueThreadLinks, + error: EnvironmentAuthorizationError, + stream: true, + }, +); + +const WsGitHubIssuesLinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.githubIssuesLinkThread, { + payload: GitHubLinkThreadInput, + success: GitHubIssueThreadLink, + error: GitHubIssueRpcError, +}); + +const WsGitHubIssuesUnlinkThreadRpc = Rpc.make(INTEGRATION_WS_METHODS.githubIssuesUnlinkThread, { + payload: GitHubUnlinkThreadInput, + success: Schema.Struct({}), + error: GitHubIssueRpcError, +}); + +export const IntegrationRpcGroup = RpcGroup.make( + WsLinearSubscribeStateRpc, + WsLinearStartLoginRpc, + WsLinearCompleteLoginRpc, + WsLinearCancelLoginRpc, + WsLinearDisconnectRpc, + WsLinearListIssuesRpc, + WsLinearGetIssueRpc, + WsLinearGetFilterOptionsRpc, + WsLinearGetIssueSummaryRpc, + WsLinearGetObjectRpc, + WsLinearSubscribeThreadLinksRpc, + WsLinearLinkThreadRpc, + WsLinearUnlinkThreadRpc, + WsNotionSubscribeStateRpc, + WsNotionStartLoginRpc, + WsNotionCompleteLoginRpc, + WsNotionCancelLoginRpc, + WsNotionDisconnectRpc, + WsNotionSearchPagesRpc, + WsNotionGetPageRpc, + WsNotionSubscribeThreadLinksRpc, + WsNotionLinkThreadRpc, + WsNotionUnlinkThreadRpc, + WsGoogleSubscribeStateRpc, + WsGoogleStartLoginRpc, + WsGoogleCompleteLoginRpc, + WsGoogleCancelLoginRpc, + WsGoogleDisconnectRpc, + WsGoogleSearchFilesRpc, + WsGoogleListCalendarsRpc, + WsGoogleSearchPeopleRpc, + WsGoogleGetFileRpc, + WsGoogleSubscribeThreadLinksRpc, + WsGoogleLinkThreadRpc, + WsGoogleUnlinkThreadRpc, + WsGranolaSubscribeStateRpc, + WsGranolaStartLoginRpc, + WsGranolaCancelLoginRpc, + WsGranolaDisconnectRpc, + WsGranolaSearchNotesRpc, + WsGranolaGetNoteRpc, + WsGranolaGetNoteSummaryRpc, + WsGranolaSubscribeThreadLinksRpc, + WsGranolaLinkThreadRpc, + WsGranolaUnlinkThreadRpc, + WsSlackSubscribeStateRpc, + WsSlackStartLoginRpc, + WsSlackCompleteLoginRpc, + WsSlackCancelLoginRpc, + WsSlackDisconnectRpc, + WsSlackSearchMessagesRpc, + WsSlackSuggestionsRpc, + WsSlackGetThreadRpc, + WsSlackGetLinkPreviewRpc, + WsSlackSubscribeThreadLinksRpc, + WsSlackLinkThreadRpc, + WsSlackUnlinkThreadRpc, + WsDiscordSubscribeStateRpc, + WsDiscordConnectRpc, + WsDiscordDisconnectRpc, + WsDiscordListServersRpc, + WsDiscordLeaveServerRpc, + WsDiscordSearchMessagesRpc, + WsDiscordGetThreadRpc, + WsDiscordGetLinkPreviewRpc, + WsDiscordSubscribeThreadLinksRpc, + WsDiscordLinkThreadRpc, + WsDiscordUnlinkThreadRpc, + WsThreadLinkWatchesSubscribeRpc, + WsThreadLinkWatchSetRpc, + WsGitHubIssuesListRpc, + WsGitHubIssuesGetRpc, + WsGitHubIssuesGetSummaryRpc, + WsGitHubRefsGetRpc, + WsGitHubIssuesSubscribeThreadLinksRpc, + WsGitHubIssuesLinkThreadRpc, + WsGitHubIssuesUnlinkThreadRpc, +); diff --git a/packages/contracts/src/rpc.ts b/packages/contracts/src/rpc.ts index e32be0a4a002c..952ce7144349c 100644 --- a/packages/contracts/src/rpc.ts +++ b/packages/contracts/src/rpc.ts @@ -1,3 +1,4 @@ +import { INTEGRATION_WS_METHODS, IntegrationRpcGroup } from "./integrationRpc.ts"; import { OrchestrationV2SearchThreadError, OrchestrationV2SearchThreadInput, @@ -6,53 +7,6 @@ import { OrchestrationV2SearchThreadsResult, } from "./orchestrationV2.ts"; import { OrchestrationDispatchCommandError } from "./orchestrationDispatch.ts"; -import { - NotionError, - NotionConnectionState, - NotionCompleteLoginInput, - NotionStartLoginInput, - NotionCancelLoginInput, - NotionSearchPagesInput, - NotionSearchPagesResult, - NotionGetPageInput, - NotionPageContext, - NotionLinkThreadInput, - NotionThreadLink, - NotionThreadLinks, - NotionUnlinkThreadInput, -} from "./notion.ts"; -import { - GoogleError, - GoogleConnectionState, - GoogleCompleteLoginInput, - GoogleStartLoginInput, - GoogleCancelLoginInput, - GoogleSearchFilesInput, - GoogleSearchFilesResult, - GoogleListCalendarsResult, - GoogleSearchPeopleInput, - GoogleSearchPeopleResult, - GoogleGetFileInput, - GoogleFileContext, - GoogleLinkThreadInput, - GoogleThreadLink, - GoogleThreadLinks, - GoogleUnlinkThreadInput, -} from "./google.ts"; -import { - GranolaCancelLoginInput, - GranolaConnectionState, - GranolaError, - GranolaGetNoteInput, - GranolaLinkThreadInput, - GranolaNoteContext, - GranolaNoteSummary, - GranolaThreadLink, - GranolaThreadLinks, - GranolaUnlinkThreadInput, - GranolaSearchNotesInput, - GranolaSearchNotesResult, -} from "./granola.ts"; import { McpAppCallToolInput, McpAppCallToolResult, @@ -132,78 +86,6 @@ import { AcpRegistrySetProviderResult, } from "./acpRegistry.ts"; import { ExternalLauncherError, LaunchEditorInput } from "./editor.ts"; -import { - LinearCancelLoginInput, - LinearCompleteLoginInput, - LinearConnectionState, - LinearError, - LinearFilterOptions, - LinearGetIssueInput, - LinearGetObjectInput, - LinearIssueContext, - LinearObjectContext, - LinearIssueSummary, - LinearLinkThreadInput, - LinearListIssuesInput, - LinearListIssuesResult, - LinearThreadLink, - LinearThreadLinks, - LinearUnlinkThreadInput, -} from "./linear.ts"; -import { - SlackCancelLoginInput, - SlackCompleteLoginInput, - SlackConnectionState, - SlackError, - SlackGetThreadInput, - SlackLinkPreview, - SlackLinkPreviewInput, - SlackSearchMessagesInput, - SlackSearchMessagesResult, - SlackStartLoginInput, - SlackSuggestionsInput, - SlackSuggestionsResult, - SlackThreadContext, - SlackLinkThreadInput, - SlackThreadLink, - SlackThreadLinks, - SlackUnlinkThreadInput, -} from "./slack.ts"; -import { - ThreadLinkWatch, - ThreadLinkWatchError, - ThreadLinkWatches, - ThreadLinkWatchSetInput, -} from "./threadLinkWatch.ts"; -import { - DiscordConnectInput, - DiscordConnectionState, - DiscordError, - DiscordGetThreadInput, - DiscordLeaveServerInput, - DiscordListServersResult, - DiscordLinkPreview, - DiscordLinkPreviewInput, - DiscordLinkThreadInput, - DiscordSearchMessagesInput, - DiscordSearchMessagesResult, - DiscordThreadContext, - DiscordThreadLink, - DiscordThreadLinks, - DiscordUnlinkThreadInput, -} from "./discord.ts"; -import { - GitHubGetIssueInput, - GitHubIssueContext, - GitHubIssueError, - GitHubIssueSummary, - GitHubIssueThreadLink, - GitHubIssueThreadLinks, - GitHubLinkThreadInput, - GitHubListIssuesInput, - GitHubListIssuesResult, - GitHubUnlinkThreadInput, -} from "./githubIssues.ts"; import { MessageDraft, MessageDraftError, @@ -213,7 +95,6 @@ import { MessageDraftSendInput, MessageDraftSendResult, } from "./messageDrafts.ts"; -import { GitHubGetRefInput, GitHubRefContext } from "./githubRefs.ts"; import { TextGenerationError } from "./git.ts"; import { AuthAccessStreamError, @@ -742,93 +623,13 @@ export const WS_METHODS = { checkoutsInspect: "checkouts.inspect", - // Linear methods - linearSubscribeState: "linear.subscribeState", - linearStartLogin: "linear.startLogin", - linearCompleteLogin: "linear.completeLogin", - linearCancelLogin: "linear.cancelLogin", - linearDisconnect: "linear.disconnect", - linearListIssues: "linear.listIssues", - linearGetIssue: "linear.getIssue", - linearGetFilterOptions: "linear.getFilterOptions", - linearGetIssueSummary: "linear.getIssueSummary", - linearGetObject: "linear.getObject", - linearSubscribeThreadLinks: "linear.subscribeThreadLinks", - linearLinkThread: "linear.linkThread", - linearUnlinkThread: "linear.unlinkThread", - - // Slack methods - notionSubscribeState: "notion.subscribeState", - notionStartLogin: "notion.startLogin", - notionCompleteLogin: "notion.completeLogin", - notionCancelLogin: "notion.cancelLogin", - notionDisconnect: "notion.disconnect", - notionSearchPages: "notion.searchPages", - notionGetPage: "notion.getPage", - notionSubscribeThreadLinks: "notion.subscribeThreadLinks", - notionLinkThread: "notion.linkThread", - notionUnlinkThread: "notion.unlinkThread", - googleSubscribeState: "google.subscribeState", - googleStartLogin: "google.startLogin", - googleCompleteLogin: "google.completeLogin", - googleCancelLogin: "google.cancelLogin", - googleDisconnect: "google.disconnect", - googleSearchFiles: "google.searchFiles", - googleListCalendars: "google.listCalendars", - googleSearchPeople: "google.searchPeople", - googleGetFile: "google.getFile", - googleSubscribeThreadLinks: "google.subscribeThreadLinks", - googleLinkThread: "google.linkThread", - googleUnlinkThread: "google.unlinkThread", - granolaSubscribeState: "granola.subscribeState", - granolaStartLogin: "granola.startLogin", - granolaCancelLogin: "granola.cancelLogin", - granolaDisconnect: "granola.disconnect", - granolaSearchNotes: "granola.searchNotes", - granolaGetNote: "granola.getNote", - granolaGetNoteSummary: "granola.getNoteSummary", - granolaSubscribeThreadLinks: "granola.subscribeThreadLinks", - granolaLinkThread: "granola.linkThread", - granolaUnlinkThread: "granola.unlinkThread", - slackSubscribeState: "slack.subscribeState", - slackStartLogin: "slack.startLogin", - slackCompleteLogin: "slack.completeLogin", - slackCancelLogin: "slack.cancelLogin", - slackDisconnect: "slack.disconnect", - slackSearchMessages: "slack.searchMessages", - slackSuggestions: "slack.suggestions", - slackGetThread: "slack.getThread", - slackGetLinkPreview: "slack.getLinkPreview", - slackSubscribeThreadLinks: "slack.subscribeThreadLinks", - slackLinkThread: "slack.linkThread", - slackUnlinkThread: "slack.unlinkThread", - discordSubscribeState: "discord.subscribeState", - discordConnect: "discord.connect", - discordDisconnect: "discord.disconnect", - discordListServers: "discord.listServers", - discordLeaveServer: "discord.leaveServer", - discordSearchMessages: "discord.searchMessages", - discordGetThread: "discord.getThread", - discordGetLinkPreview: "discord.getLinkPreview", - discordSubscribeThreadLinks: "discord.subscribeThreadLinks", - discordLinkThread: "discord.linkThread", - discordUnlinkThread: "discord.unlinkThread", - threadLinkWatchesSubscribe: "threadLinkWatches.subscribe", - threadLinkWatchSet: "threadLinkWatches.set", - - // GitHub issue methods - githubIssuesList: "githubIssues.list", - githubIssuesGet: "githubIssues.get", - githubIssuesGetSummary: "githubIssues.getSummary", - githubIssuesSubscribeThreadLinks: "githubIssues.subscribeThreadLinks", - githubIssuesLinkThread: "githubIssues.linkThread", - githubIssuesUnlinkThread: "githubIssues.unlinkThread", + ...INTEGRATION_WS_METHODS, + messageDraftsSubscribe: "messageDrafts.subscribe", messageDraftsSave: "messageDrafts.save", messageDraftsTake: "messageDrafts.take", messageDraftsDelete: "messageDrafts.delete", messageDraftsSend: "messageDrafts.send", - githubRefsGet: "githubRefs.get", projectCloneStart: "projectClone.start", projectCloneCancel: "projectClone.cancel", projectCloneRetry: "projectClone.retry", @@ -1064,469 +865,6 @@ const WsServerDiscoverSourceControlRpc = Rpc.make(WS_METHODS.serverDiscoverSourc error: EnvironmentAuthorizationError, }); -const LinearRpcError = Schema.Union([LinearError, EnvironmentAuthorizationError]); - -const WsLinearSubscribeStateRpc = Rpc.make(WS_METHODS.linearSubscribeState, { - payload: Schema.Struct({}), - success: LinearConnectionState, - error: EnvironmentAuthorizationError, - stream: true, -}); - -const WsLinearStartLoginRpc = Rpc.make(WS_METHODS.linearStartLogin, { - payload: Schema.Struct({}), - success: LinearConnectionState, - error: LinearRpcError, -}); - -const WsLinearCompleteLoginRpc = Rpc.make(WS_METHODS.linearCompleteLogin, { - payload: LinearCompleteLoginInput, - success: LinearConnectionState, - error: LinearRpcError, -}); - -const WsLinearCancelLoginRpc = Rpc.make(WS_METHODS.linearCancelLogin, { - payload: LinearCancelLoginInput, - success: LinearConnectionState, - error: LinearRpcError, -}); - -const WsLinearDisconnectRpc = Rpc.make(WS_METHODS.linearDisconnect, { - payload: Schema.Struct({}), - success: LinearConnectionState, - error: LinearRpcError, -}); - -const WsLinearListIssuesRpc = Rpc.make(WS_METHODS.linearListIssues, { - payload: LinearListIssuesInput, - success: LinearListIssuesResult, - error: LinearRpcError, -}); - -const WsLinearGetIssueRpc = Rpc.make(WS_METHODS.linearGetIssue, { - payload: LinearGetIssueInput, - success: LinearIssueContext, - error: LinearRpcError, -}); - -const WsLinearGetFilterOptionsRpc = Rpc.make(WS_METHODS.linearGetFilterOptions, { - payload: Schema.Struct({}), - success: LinearFilterOptions, - error: LinearRpcError, -}); - -const WsLinearGetIssueSummaryRpc = Rpc.make(WS_METHODS.linearGetIssueSummary, { - payload: LinearGetIssueInput, - success: LinearIssueSummary, - error: LinearRpcError, -}); - -const WsLinearGetObjectRpc = Rpc.make(WS_METHODS.linearGetObject, { - payload: LinearGetObjectInput, - success: LinearObjectContext, - error: LinearRpcError, -}); - -const WsLinearSubscribeThreadLinksRpc = Rpc.make(WS_METHODS.linearSubscribeThreadLinks, { - payload: Schema.Struct({}), - success: LinearThreadLinks, - error: EnvironmentAuthorizationError, - stream: true, -}); - -const WsLinearLinkThreadRpc = Rpc.make(WS_METHODS.linearLinkThread, { - payload: LinearLinkThreadInput, - success: LinearThreadLink, - error: LinearRpcError, -}); - -const WsLinearUnlinkThreadRpc = Rpc.make(WS_METHODS.linearUnlinkThread, { - payload: LinearUnlinkThreadInput, - success: Schema.Struct({}), - error: LinearRpcError, -}); - -const NotionRpcError = Schema.Union([NotionError, EnvironmentAuthorizationError]); -const WsNotionSubscribeStateRpc = Rpc.make(WS_METHODS.notionSubscribeState, { - payload: Schema.Struct({}), - success: NotionConnectionState, - error: NotionRpcError, - stream: true, -}); -const WsNotionStartLoginRpc = Rpc.make(WS_METHODS.notionStartLogin, { - payload: NotionStartLoginInput, - success: NotionConnectionState, - error: NotionRpcError, -}); -const WsNotionCompleteLoginRpc = Rpc.make(WS_METHODS.notionCompleteLogin, { - payload: NotionCompleteLoginInput, - success: NotionConnectionState, - error: NotionRpcError, -}); -const WsNotionCancelLoginRpc = Rpc.make(WS_METHODS.notionCancelLogin, { - payload: NotionCancelLoginInput, - success: NotionConnectionState, - error: NotionRpcError, -}); -const WsNotionDisconnectRpc = Rpc.make(WS_METHODS.notionDisconnect, { - payload: Schema.Struct({}), - success: NotionConnectionState, - error: NotionRpcError, -}); -const WsNotionSearchPagesRpc = Rpc.make(WS_METHODS.notionSearchPages, { - payload: NotionSearchPagesInput, - success: NotionSearchPagesResult, - error: NotionRpcError, -}); -const WsNotionGetPageRpc = Rpc.make(WS_METHODS.notionGetPage, { - payload: NotionGetPageInput, - success: NotionPageContext, - error: NotionRpcError, -}); -const WsNotionSubscribeThreadLinksRpc = Rpc.make(WS_METHODS.notionSubscribeThreadLinks, { - payload: Schema.Struct({}), - success: NotionThreadLinks, - error: EnvironmentAuthorizationError, - stream: true, -}); -const WsNotionLinkThreadRpc = Rpc.make(WS_METHODS.notionLinkThread, { - payload: NotionLinkThreadInput, - success: NotionThreadLink, - error: NotionRpcError, -}); -const WsNotionUnlinkThreadRpc = Rpc.make(WS_METHODS.notionUnlinkThread, { - payload: NotionUnlinkThreadInput, - success: Schema.Struct({}), - error: NotionRpcError, -}); - -const GoogleRpcError = Schema.Union([GoogleError, EnvironmentAuthorizationError]); -const WsGoogleSubscribeStateRpc = Rpc.make(WS_METHODS.googleSubscribeState, { - payload: Schema.Struct({}), - success: GoogleConnectionState, - error: GoogleRpcError, - stream: true, -}); -const WsGoogleStartLoginRpc = Rpc.make(WS_METHODS.googleStartLogin, { - payload: GoogleStartLoginInput, - success: GoogleConnectionState, - error: GoogleRpcError, -}); -const WsGoogleCompleteLoginRpc = Rpc.make(WS_METHODS.googleCompleteLogin, { - payload: GoogleCompleteLoginInput, - success: GoogleConnectionState, - error: GoogleRpcError, -}); -const WsGoogleCancelLoginRpc = Rpc.make(WS_METHODS.googleCancelLogin, { - payload: GoogleCancelLoginInput, - success: GoogleConnectionState, - error: GoogleRpcError, -}); -const WsGoogleDisconnectRpc = Rpc.make(WS_METHODS.googleDisconnect, { - payload: Schema.Struct({}), - success: GoogleConnectionState, - error: GoogleRpcError, -}); -const WsGoogleSearchFilesRpc = Rpc.make(WS_METHODS.googleSearchFiles, { - payload: GoogleSearchFilesInput, - success: GoogleSearchFilesResult, - error: GoogleRpcError, -}); -const WsGoogleListCalendarsRpc = Rpc.make(WS_METHODS.googleListCalendars, { - payload: Schema.Struct({}), - success: GoogleListCalendarsResult, - error: GoogleRpcError, -}); -const WsGoogleSearchPeopleRpc = Rpc.make(WS_METHODS.googleSearchPeople, { - payload: GoogleSearchPeopleInput, - success: GoogleSearchPeopleResult, - error: GoogleRpcError, -}); -const WsGoogleGetFileRpc = Rpc.make(WS_METHODS.googleGetFile, { - payload: GoogleGetFileInput, - success: GoogleFileContext, - error: GoogleRpcError, -}); -const WsGoogleSubscribeThreadLinksRpc = Rpc.make(WS_METHODS.googleSubscribeThreadLinks, { - payload: Schema.Struct({}), - success: GoogleThreadLinks, - error: EnvironmentAuthorizationError, - stream: true, -}); -const WsGoogleLinkThreadRpc = Rpc.make(WS_METHODS.googleLinkThread, { - payload: GoogleLinkThreadInput, - success: GoogleThreadLink, - error: GoogleRpcError, -}); -const WsGoogleUnlinkThreadRpc = Rpc.make(WS_METHODS.googleUnlinkThread, { - payload: GoogleUnlinkThreadInput, - success: Schema.Struct({}), - error: GoogleRpcError, -}); - -const GranolaRpcError = Schema.Union([GranolaError, EnvironmentAuthorizationError]); -const WsGranolaSubscribeStateRpc = Rpc.make(WS_METHODS.granolaSubscribeState, { - payload: Schema.Struct({}), - success: GranolaConnectionState, - error: GranolaRpcError, - stream: true, -}); -const WsGranolaStartLoginRpc = Rpc.make(WS_METHODS.granolaStartLogin, { - payload: Schema.Struct({}), - success: GranolaConnectionState, - error: GranolaRpcError, -}); -const WsGranolaCancelLoginRpc = Rpc.make(WS_METHODS.granolaCancelLogin, { - payload: GranolaCancelLoginInput, - success: GranolaConnectionState, - error: GranolaRpcError, -}); -const WsGranolaDisconnectRpc = Rpc.make(WS_METHODS.granolaDisconnect, { - payload: Schema.Struct({}), - success: GranolaConnectionState, - error: GranolaRpcError, -}); -const WsGranolaSearchNotesRpc = Rpc.make(WS_METHODS.granolaSearchNotes, { - payload: GranolaSearchNotesInput, - success: GranolaSearchNotesResult, - error: GranolaRpcError, -}); -const WsGranolaGetNoteRpc = Rpc.make(WS_METHODS.granolaGetNote, { - payload: GranolaGetNoteInput, - success: GranolaNoteContext, - error: GranolaRpcError, -}); -/** A note's title and meeting, for naming a bare note link; the server keeps each one it reads. */ -const WsGranolaGetNoteSummaryRpc = Rpc.make(WS_METHODS.granolaGetNoteSummary, { - payload: GranolaGetNoteInput, - success: GranolaNoteSummary, - error: GranolaRpcError, -}); -const WsGranolaSubscribeThreadLinksRpc = Rpc.make(WS_METHODS.granolaSubscribeThreadLinks, { - payload: Schema.Struct({}), - success: GranolaThreadLinks, - error: EnvironmentAuthorizationError, - stream: true, -}); -const WsGranolaLinkThreadRpc = Rpc.make(WS_METHODS.granolaLinkThread, { - payload: GranolaLinkThreadInput, - success: GranolaThreadLink, - error: GranolaRpcError, -}); -const WsGranolaUnlinkThreadRpc = Rpc.make(WS_METHODS.granolaUnlinkThread, { - payload: GranolaUnlinkThreadInput, - success: Schema.Struct({}), - error: GranolaRpcError, -}); - -const SlackRpcError = Schema.Union([SlackError, EnvironmentAuthorizationError]); - -const WsSlackSubscribeStateRpc = Rpc.make(WS_METHODS.slackSubscribeState, { - payload: Schema.Struct({}), - success: SlackConnectionState, - error: EnvironmentAuthorizationError, - stream: true, -}); - -const WsSlackStartLoginRpc = Rpc.make(WS_METHODS.slackStartLogin, { - payload: SlackStartLoginInput, - success: SlackConnectionState, - error: SlackRpcError, -}); - -const WsSlackCompleteLoginRpc = Rpc.make(WS_METHODS.slackCompleteLogin, { - payload: SlackCompleteLoginInput, - success: SlackConnectionState, - error: SlackRpcError, -}); - -const WsSlackCancelLoginRpc = Rpc.make(WS_METHODS.slackCancelLogin, { - payload: SlackCancelLoginInput, - success: SlackConnectionState, - error: SlackRpcError, -}); - -const WsSlackDisconnectRpc = Rpc.make(WS_METHODS.slackDisconnect, { - payload: Schema.Struct({}), - success: SlackConnectionState, - error: SlackRpcError, -}); - -const WsSlackSearchMessagesRpc = Rpc.make(WS_METHODS.slackSearchMessages, { - payload: SlackSearchMessagesInput, - success: SlackSearchMessagesResult, - error: SlackRpcError, -}); - -const WsSlackSuggestionsRpc = Rpc.make(WS_METHODS.slackSuggestions, { - payload: SlackSuggestionsInput, - success: SlackSuggestionsResult, - error: SlackRpcError, -}); - -const WsSlackGetThreadRpc = Rpc.make(WS_METHODS.slackGetThread, { - payload: SlackGetThreadInput, - success: SlackThreadContext, - error: SlackRpcError, -}); - -const WsSlackGetLinkPreviewRpc = Rpc.make(WS_METHODS.slackGetLinkPreview, { - payload: SlackLinkPreviewInput, - success: SlackLinkPreview, - error: SlackRpcError, -}); - -const WsSlackSubscribeThreadLinksRpc = Rpc.make(WS_METHODS.slackSubscribeThreadLinks, { - payload: Schema.Struct({}), - success: SlackThreadLinks, - error: EnvironmentAuthorizationError, - stream: true, -}); - -const WsSlackLinkThreadRpc = Rpc.make(WS_METHODS.slackLinkThread, { - payload: SlackLinkThreadInput, - success: SlackThreadLink, - error: SlackRpcError, -}); - -const WsSlackUnlinkThreadRpc = Rpc.make(WS_METHODS.slackUnlinkThread, { - payload: SlackUnlinkThreadInput, - success: Schema.Struct({}), - error: SlackRpcError, -}); - -const DiscordRpcError = Schema.Union([DiscordError, EnvironmentAuthorizationError]); - -const WsDiscordSubscribeStateRpc = Rpc.make(WS_METHODS.discordSubscribeState, { - payload: Schema.Struct({}), - success: DiscordConnectionState, - error: EnvironmentAuthorizationError, - stream: true, -}); - -const WsDiscordConnectRpc = Rpc.make(WS_METHODS.discordConnect, { - payload: DiscordConnectInput, - success: DiscordConnectionState, - error: DiscordRpcError, -}); - -const WsDiscordDisconnectRpc = Rpc.make(WS_METHODS.discordDisconnect, { - payload: Schema.Struct({}), - success: DiscordConnectionState, - error: DiscordRpcError, -}); - -const WsDiscordListServersRpc = Rpc.make(WS_METHODS.discordListServers, { - payload: Schema.Struct({}), - success: DiscordListServersResult, - error: DiscordRpcError, -}); - -const WsDiscordLeaveServerRpc = Rpc.make(WS_METHODS.discordLeaveServer, { - payload: DiscordLeaveServerInput, - success: Schema.Struct({}), - error: DiscordRpcError, -}); - -const WsDiscordSearchMessagesRpc = Rpc.make(WS_METHODS.discordSearchMessages, { - payload: DiscordSearchMessagesInput, - success: DiscordSearchMessagesResult, - error: DiscordRpcError, -}); - -const WsDiscordGetThreadRpc = Rpc.make(WS_METHODS.discordGetThread, { - payload: DiscordGetThreadInput, - success: DiscordThreadContext, - error: DiscordRpcError, -}); - -const WsDiscordGetLinkPreviewRpc = Rpc.make(WS_METHODS.discordGetLinkPreview, { - payload: DiscordLinkPreviewInput, - success: DiscordLinkPreview, - error: DiscordRpcError, -}); - -const WsDiscordSubscribeThreadLinksRpc = Rpc.make(WS_METHODS.discordSubscribeThreadLinks, { - payload: Schema.Struct({}), - success: DiscordThreadLinks, - error: EnvironmentAuthorizationError, - stream: true, -}); - -const WsDiscordLinkThreadRpc = Rpc.make(WS_METHODS.discordLinkThread, { - payload: DiscordLinkThreadInput, - success: DiscordThreadLink, - error: DiscordRpcError, -}); - -const WsDiscordUnlinkThreadRpc = Rpc.make(WS_METHODS.discordUnlinkThread, { - payload: DiscordUnlinkThreadInput, - success: Schema.Struct({}), - error: DiscordRpcError, -}); - -const WsThreadLinkWatchesSubscribeRpc = Rpc.make(WS_METHODS.threadLinkWatchesSubscribe, { - payload: Schema.Struct({}), - success: ThreadLinkWatches, - error: EnvironmentAuthorizationError, - stream: true, -}); - -/** Starts or stops watching a thread's link; returns the watch, or null once stopped. */ -const WsThreadLinkWatchSetRpc = Rpc.make(WS_METHODS.threadLinkWatchSet, { - payload: ThreadLinkWatchSetInput, - success: Schema.NullOr(ThreadLinkWatch), - error: Schema.Union([ThreadLinkWatchError, EnvironmentAuthorizationError]), -}); - -const GitHubIssueRpcError = Schema.Union([GitHubIssueError, EnvironmentAuthorizationError]); - -const WsGitHubIssuesListRpc = Rpc.make(WS_METHODS.githubIssuesList, { - payload: GitHubListIssuesInput, - success: GitHubListIssuesResult, - error: GitHubIssueRpcError, -}); - -const WsGitHubIssuesGetRpc = Rpc.make(WS_METHODS.githubIssuesGet, { - payload: GitHubGetIssueInput, - success: GitHubIssueContext, - error: GitHubIssueRpcError, -}); - -const WsGitHubIssuesGetSummaryRpc = Rpc.make(WS_METHODS.githubIssuesGetSummary, { - payload: GitHubGetIssueInput, - success: GitHubIssueSummary, - error: GitHubIssueRpcError, -}); - -const WsGitHubRefsGetRpc = Rpc.make(WS_METHODS.githubRefsGet, { - payload: GitHubGetRefInput, - success: GitHubRefContext, - error: GitHubIssueRpcError, -}); - -const WsGitHubIssuesSubscribeThreadLinksRpc = Rpc.make( - WS_METHODS.githubIssuesSubscribeThreadLinks, - { - payload: Schema.Struct({}), - success: GitHubIssueThreadLinks, - error: EnvironmentAuthorizationError, - stream: true, - }, -); - -const WsGitHubIssuesLinkThreadRpc = Rpc.make(WS_METHODS.githubIssuesLinkThread, { - payload: GitHubLinkThreadInput, - success: GitHubIssueThreadLink, - error: GitHubIssueRpcError, -}); - -const WsGitHubIssuesUnlinkThreadRpc = Rpc.make(WS_METHODS.githubIssuesUnlinkThread, { - payload: GitHubUnlinkThreadInput, - success: Schema.Struct({}), - error: GitHubIssueRpcError, -}); - const MessageDraftRpcError = Schema.Union([MessageDraftError, EnvironmentAuthorizationError]); const WsMessageDraftsSubscribeRpc = Rpc.make(WS_METHODS.messageDraftsSubscribe, { @@ -2872,83 +2210,6 @@ export const WsRpcGroup = RpcGroup.make( WsContextRepositoriesInspectRpc, WsContextRepositoriesRemoveRpc, WsCheckoutsInspectRpc, - WsLinearSubscribeStateRpc, - WsLinearStartLoginRpc, - WsLinearCompleteLoginRpc, - WsLinearCancelLoginRpc, - WsLinearDisconnectRpc, - WsLinearListIssuesRpc, - WsLinearGetIssueRpc, - WsLinearGetFilterOptionsRpc, - WsLinearGetIssueSummaryRpc, - WsLinearGetObjectRpc, - WsLinearSubscribeThreadLinksRpc, - WsLinearLinkThreadRpc, - WsLinearUnlinkThreadRpc, - WsNotionSubscribeStateRpc, - WsGoogleSubscribeStateRpc, - WsNotionStartLoginRpc, - WsGoogleStartLoginRpc, - WsNotionCompleteLoginRpc, - WsGoogleCompleteLoginRpc, - WsNotionCancelLoginRpc, - WsGoogleCancelLoginRpc, - WsNotionDisconnectRpc, - WsGoogleDisconnectRpc, - WsNotionSearchPagesRpc, - WsGoogleSearchFilesRpc, - WsGoogleListCalendarsRpc, - WsGoogleSearchPeopleRpc, - WsNotionGetPageRpc, - WsGoogleGetFileRpc, - WsNotionSubscribeThreadLinksRpc, - WsGoogleSubscribeThreadLinksRpc, - WsNotionLinkThreadRpc, - WsGoogleLinkThreadRpc, - WsNotionUnlinkThreadRpc, - WsGoogleUnlinkThreadRpc, - WsGranolaSubscribeStateRpc, - WsGranolaStartLoginRpc, - WsGranolaCancelLoginRpc, - WsGranolaDisconnectRpc, - WsGranolaSearchNotesRpc, - WsGranolaGetNoteRpc, - WsGranolaGetNoteSummaryRpc, - WsGranolaSubscribeThreadLinksRpc, - WsGranolaLinkThreadRpc, - WsGranolaUnlinkThreadRpc, - WsSlackSubscribeStateRpc, - WsSlackStartLoginRpc, - WsSlackCompleteLoginRpc, - WsSlackCancelLoginRpc, - WsSlackDisconnectRpc, - WsSlackSearchMessagesRpc, - WsSlackSuggestionsRpc, - WsSlackGetThreadRpc, - WsSlackGetLinkPreviewRpc, - WsSlackSubscribeThreadLinksRpc, - WsSlackLinkThreadRpc, - WsSlackUnlinkThreadRpc, - WsDiscordSubscribeStateRpc, - WsDiscordConnectRpc, - WsDiscordDisconnectRpc, - WsDiscordListServersRpc, - WsDiscordLeaveServerRpc, - WsDiscordSearchMessagesRpc, - WsDiscordGetThreadRpc, - WsDiscordGetLinkPreviewRpc, - WsDiscordSubscribeThreadLinksRpc, - WsDiscordLinkThreadRpc, - WsDiscordUnlinkThreadRpc, - WsThreadLinkWatchesSubscribeRpc, - WsThreadLinkWatchSetRpc, - WsGitHubIssuesListRpc, - WsGitHubIssuesGetRpc, - WsGitHubIssuesGetSummaryRpc, - WsGitHubRefsGetRpc, - WsGitHubIssuesSubscribeThreadLinksRpc, - WsGitHubIssuesLinkThreadRpc, - WsGitHubIssuesUnlinkThreadRpc, WsMessageDraftsSubscribeRpc, WsMessageDraftsSaveRpc, WsMessageDraftsTakeRpc, @@ -3059,4 +2320,6 @@ export const WsRpcGroup = RpcGroup.make( WsOrchestrationV2SubscribeShellRpc, WsOrchestrationV2SubscribeThreadRpc, WsOrchestrationV2SubscribeCommandOutputRpc, -).middleware(RpcScopeAuthorization); +) + .merge(IntegrationRpcGroup) + .middleware(RpcScopeAuthorization);