1import * as Schema from "effect/Schema";
2import { GitPreparePullRequestThreadInput, GitRunStackedActionInput } from "./git.ts";
3import {
4 AuthAccessWriteScope,
5 AuthEnvironmentMaintainScope,
6 AuthOrchestrationOperateScope,
7 AuthOrchestrationReadScope,
8 AuthPreviewOperateScope,
9 AuthProvidersManageScope,
10 AuthSettingsWriteScope,
11 AuthSourceControlWriteScope,
12 AuthTerminalOperateScope,
13 type AuthEnvironmentScope,
14} from "./auth.ts";
15import { ORCHESTRATION_V2_WS_METHODS } from "./orchestrationV2.ts";
16import { WS_METHODS } from "./rpc.ts";
17
18/** Incremental client enforcement; the server still authorizes every request. */
19export const CLIENT_GUARDED_RPC_SCOPES = {
20 [WS_METHODS.providerAuthRemoveMethod]: AuthProvidersManageScope,
21 [WS_METHODS.previewClose]: AuthPreviewOperateScope,
22 // Stopping a server signals its processes, as closing a terminal does.
23 [WS_METHODS.previewStopLocalServer]: AuthTerminalOperateScope,
24 [WS_METHODS.serverRunStorageCleanup]: AuthSettingsWriteScope,
25 // Restarts the server, as an update does.
26 [WS_METHODS.serverReclaimDatabaseSpace]: AuthEnvironmentMaintainScope,
27 [WS_METHODS.serverCheckOtlpEndpoint]: AuthSettingsWriteScope,
28 [WS_METHODS.serverCheckSecretSource]: AuthSettingsWriteScope,
29 // Listing models spends the stored API key, so only those who can choose a model may.
30 [WS_METHODS.serverListTextGeneratorModels]: AuthSettingsWriteScope,
31 [WS_METHODS.serverRefreshProviders]: AuthOrchestrationReadScope,
32 [WS_METHODS.pullRequestsRunAction]: AuthSourceControlWriteScope,
33 [WS_METHODS.pullRequestsUpdate]: AuthSourceControlWriteScope,
34 [WS_METHODS.pullRequestsComment]: AuthSourceControlWriteScope,
35 [WS_METHODS.pullRequestsUpdateComment]: AuthSourceControlWriteScope,
36 [WS_METHODS.pullRequestsSubmitReview]: AuthSourceControlWriteScope,
37 [WS_METHODS.pullRequestsReplyToThread]: AuthSourceControlWriteScope,
38 [WS_METHODS.pullRequestsSetThreadResolution]: AuthSourceControlWriteScope,
39 [WS_METHODS.pullRequestsSetReaction]: AuthSourceControlWriteScope,
40 [WS_METHODS.pullRequestsSetFilesViewed]: AuthSourceControlWriteScope,
41 [WS_METHODS.pullRequestsRequestReviewers]: AuthSourceControlWriteScope,
42 [WS_METHODS.pullRequestsSetLabels]: AuthSourceControlWriteScope,
43 // Writing a guide runs a model on this environment's providers, which reading one does not.
44 [WS_METHODS.pullRequestsGenerateReviewGuide]: AuthOrchestrationOperateScope,
45 [WS_METHODS.projectsCreateFromThread]: AuthOrchestrationOperateScope,
46 [WS_METHODS.sourceControlCloneRepository]: AuthSourceControlWriteScope,
47 [WS_METHODS.sourceControlPublishRepository]: AuthSourceControlWriteScope,
48 [WS_METHODS.sourceControlStar]: AuthSourceControlWriteScope,
49 // Signing in replaces the server's GitHub credential, as saving a token in Settings does.
50 [WS_METHODS.sourceControlSignInStart]: AuthSettingsWriteScope,
51 [WS_METHODS.sourceControlSignInWait]: AuthSettingsWriteScope,
52 [WS_METHODS.sourceControlSignInCancel]: AuthSettingsWriteScope,
53 [WS_METHODS.projectCloneStart]: AuthSourceControlWriteScope,
54 [WS_METHODS.projectCloneCancel]: AuthSourceControlWriteScope,
55 [WS_METHODS.projectCloneRetry]: AuthSourceControlWriteScope,
56 [WS_METHODS.vcsPull]: AuthSourceControlWriteScope,
57 [WS_METHODS.gitRunStackedAction]: AuthSourceControlWriteScope,
58 [WS_METHODS.gitPreparePullRequestThread]: AuthSourceControlWriteScope,
59 [WS_METHODS.vcsCreateWorktree]: AuthSourceControlWriteScope,
60 [WS_METHODS.vcsRemoveWorktree]: AuthSourceControlWriteScope,
61 [WS_METHODS.vcsCreateRef]: AuthSourceControlWriteScope,
62 [WS_METHODS.vcsSwitchRef]: AuthSourceControlWriteScope,
63 [WS_METHODS.vcsInit]: AuthSourceControlWriteScope,
64
65 // Starts an agent session on this environment that can read its logs and database.
66 [WS_METHODS.serverPrepareTriage]: AuthOrchestrationOperateScope,
67 [WS_METHODS.scheduledTasksUpsert]: AuthOrchestrationOperateScope,
68 [WS_METHODS.scheduledTasksSetEnabled]: AuthOrchestrationOperateScope,
69 [WS_METHODS.scheduledTasksDelete]: AuthOrchestrationOperateScope,
70 [WS_METHODS.scheduledTasksRunNow]: AuthOrchestrationOperateScope,
71 [WS_METHODS.scheduledTasksRotateWebhookToken]: AuthOrchestrationOperateScope,
72 [ORCHESTRATION_V2_WS_METHODS.stopWorkflow]: AuthOrchestrationOperateScope,
73 // Force stop kills agent processes, as Stop on a terminal does.
74 [ORCHESTRATION_V2_WS_METHODS.forceStopThread]: AuthOrchestrationOperateScope,
75 // A guest link hands thread access to someone outside, like a pairing link.
76 [WS_METHODS.guestShareCreate]: AuthAccessWriteScope,
77 [WS_METHODS.peerLinksLink]: AuthAccessWriteScope,
78 [WS_METHODS.peerLinksUnlink]: AuthAccessWriteScope,
79 // A link lets every agent here reach the other environment: Settings' bar.
80 [WS_METHODS.peerLinksAnswerRequest]: AuthAccessWriteScope,
81} as const;
82export type ClientGuardedRpcTag = keyof typeof CLIENT_GUARDED_RPC_SCOPES;
83
84const decodePrepareThread = Schema.decodeUnknownSync(GitPreparePullRequestThreadInput);
85const decodeStackedAction = Schema.decodeUnknownSync(GitRunStackedActionInput);
86
87export function clientRpcRequiredScopes(
88 method: string,
89 input: unknown,
90): readonly AuthEnvironmentScope[] {
91 if (method === WS_METHODS.gitPreparePullRequestThread && input !== undefined) {
92 const payload = decodePrepareThread(input);
93 if (payload.mode === "worktree" && payload.threadId !== undefined)
94 return [AuthSourceControlWriteScope, AuthOrchestrationOperateScope];
95 }
96 if (method === WS_METHODS.gitRunStackedAction && input !== undefined) {
97 const payload = decodeStackedAction(input);
98 if (
99 payload.threadId !== undefined &&
100 (payload.action === "create_pr" || payload.action === "commit_push_pr")
101 )
102 return [AuthSourceControlWriteScope, AuthOrchestrationReadScope];
103 }
104 return Object.hasOwn(CLIENT_GUARDED_RPC_SCOPES, method)
105 ? [CLIENT_GUARDED_RPC_SCOPES[method as ClientGuardedRpcTag]]
106 : [];
107}