@actagent/googlechat 2026.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +11 -0
  2. package/actagent.plugin.json +17 -0
  3. package/api.ts +4 -0
  4. package/channel-config-api.ts +2 -0
  5. package/channel-plugin-api.ts +2 -0
  6. package/config-api.ts +3 -0
  7. package/contract-api.ts +6 -0
  8. package/directory-contract-api.ts +7 -0
  9. package/doctor-contract-api.ts +2 -0
  10. package/index.ts +21 -0
  11. package/npm-shrinkwrap.json +314 -0
  12. package/package.json +88 -0
  13. package/runtime-api.ts +61 -0
  14. package/secret-contract-api.ts +6 -0
  15. package/setup-entry.ts +14 -0
  16. package/setup-plugin-api.ts +3 -0
  17. package/src/accounts.ts +185 -0
  18. package/src/actions.test.ts +312 -0
  19. package/src/actions.ts +228 -0
  20. package/src/api.ts +346 -0
  21. package/src/approval-auth.test.ts +25 -0
  22. package/src/approval-auth.ts +38 -0
  23. package/src/approval-card-actions.test.ts +113 -0
  24. package/src/approval-card-actions.ts +307 -0
  25. package/src/approval-card-click.test.ts +279 -0
  26. package/src/approval-card-click.ts +94 -0
  27. package/src/approval-handler.runtime.test.ts +388 -0
  28. package/src/approval-handler.runtime.ts +413 -0
  29. package/src/approval-native.test.ts +399 -0
  30. package/src/approval-native.ts +246 -0
  31. package/src/auth.ts +219 -0
  32. package/src/channel-base.ts +123 -0
  33. package/src/channel-config.test.ts +174 -0
  34. package/src/channel.adapters.ts +363 -0
  35. package/src/channel.deps.runtime.ts +30 -0
  36. package/src/channel.runtime.ts +18 -0
  37. package/src/channel.setup.ts +7 -0
  38. package/src/channel.test.ts +845 -0
  39. package/src/channel.ts +214 -0
  40. package/src/config-schema.test.ts +32 -0
  41. package/src/config-schema.ts +4 -0
  42. package/src/doctor-contract.test.ts +76 -0
  43. package/src/doctor-contract.ts +181 -0
  44. package/src/doctor.ts +58 -0
  45. package/src/gateway.ts +84 -0
  46. package/src/google-auth.runtime.test.ts +571 -0
  47. package/src/google-auth.runtime.ts +570 -0
  48. package/src/group-policy.ts +18 -0
  49. package/src/monitor-access.test.ts +492 -0
  50. package/src/monitor-access.ts +466 -0
  51. package/src/monitor-durable.test.ts +40 -0
  52. package/src/monitor-durable.ts +24 -0
  53. package/src/monitor-reply-delivery.ts +162 -0
  54. package/src/monitor-routing.ts +66 -0
  55. package/src/monitor-types.ts +34 -0
  56. package/src/monitor-webhook.test.ts +670 -0
  57. package/src/monitor-webhook.ts +361 -0
  58. package/src/monitor.reply-delivery.test.ts +145 -0
  59. package/src/monitor.test.ts +389 -0
  60. package/src/monitor.ts +530 -0
  61. package/src/monitor.webhook-routing.test.ts +258 -0
  62. package/src/runtime.ts +10 -0
  63. package/src/secret-contract.test.ts +61 -0
  64. package/src/secret-contract.ts +162 -0
  65. package/src/setup-core.ts +41 -0
  66. package/src/setup-surface.ts +244 -0
  67. package/src/setup.test.ts +620 -0
  68. package/src/targets.test.ts +562 -0
  69. package/src/targets.ts +67 -0
  70. package/src/types.config.ts +4 -0
  71. package/src/types.ts +139 -0
  72. package/test-api.ts +3 -0
  73. package/tsconfig.json +16 -0
@@ -0,0 +1,399 @@
1
+ import type { ChannelOutboundPayloadHint } from "actagent/plugin-sdk/channel-contract";
2
+ import type { ACTAgentConfig } from "actagent/plugin-sdk/config-contracts";
3
+ import type { ReplyPayload } from "actagent/plugin-sdk/reply-runtime";
4
+ import { describe, expect, it } from "vitest";
5
+ import {
6
+ googleChatApprovalCapability,
7
+ shouldHandleGoogleChatNativeApprovalRequest,
8
+ shouldSuppressLocalGoogleChatExecApprovalPrompt,
9
+ } from "./approval-native.js";
10
+
11
+ const GOOGLE_CHAT_APPROVAL_ACCOUNT = {
12
+ serviceAccount: {
13
+ type: "service_account" as const,
14
+ client_email: "bot@example.com",
15
+ private_key: "test-key",
16
+ token_uri: "https://oauth2.googleapis.com/token",
17
+ },
18
+ audienceType: "app-url" as const,
19
+ audience: "https://chat-app.example.test/googlechat",
20
+ appPrincipal: "123456789012345678901",
21
+ dm: { allowFrom: ["users/123"] },
22
+ };
23
+
24
+ const execApprovalPayload: ReplyPayload = {
25
+ text: "I need approval to run this command.",
26
+ channelData: {
27
+ execApproval: {
28
+ approvalId: "12345678-1234-1234-1234-123456789012",
29
+ approvalSlug: "12345678",
30
+ approvalKind: "exec",
31
+ agentId: "dev",
32
+ sessionKey: "agent:dev:main",
33
+ },
34
+ },
35
+ };
36
+
37
+ const activeExecApprovalHint: ChannelOutboundPayloadHint = {
38
+ kind: "approval-pending",
39
+ approvalKind: "exec",
40
+ nativeRouteActive: true,
41
+ };
42
+
43
+ describe("googleChatApprovalCapability", () => {
44
+ it("declares native exec and plugin approval runtime support", async () => {
45
+ const runtime = googleChatApprovalCapability.nativeRuntime;
46
+ expect(runtime?.eventKinds).toEqual(["exec", "plugin"]);
47
+ expect(
48
+ runtime?.availability.isConfigured({
49
+ cfg: {
50
+ approvals: { exec: { enabled: true } },
51
+ channels: {
52
+ googlechat: {
53
+ serviceAccount: {
54
+ type: "service_account",
55
+ client_email: "bot@example.com",
56
+ private_key: "test-key",
57
+ token_uri: "https://oauth2.googleapis.com/token",
58
+ },
59
+ audienceType: "app-url",
60
+ audience: "https://chat-app.example.test/googlechat",
61
+ appPrincipal: "123456789012345678901",
62
+ dm: { allowFrom: ["users/123"] },
63
+ },
64
+ },
65
+ },
66
+ }),
67
+ ).toBe(true);
68
+ });
69
+
70
+ it("does not enable native cards when webhook callback audience auth is incomplete", async () => {
71
+ const runtime = googleChatApprovalCapability.nativeRuntime;
72
+ expect(
73
+ runtime?.availability.isConfigured({
74
+ cfg: {
75
+ approvals: { exec: { enabled: true } },
76
+ channels: {
77
+ googlechat: {
78
+ serviceAccount: {
79
+ type: "service_account",
80
+ client_email: "bot@example.com",
81
+ private_key: "test-key",
82
+ token_uri: "https://oauth2.googleapis.com/token",
83
+ },
84
+ dm: { allowFrom: ["users/123"] },
85
+ },
86
+ },
87
+ },
88
+ }),
89
+ ).toBe(false);
90
+ expect(
91
+ runtime?.availability.isConfigured({
92
+ cfg: {
93
+ approvals: { exec: { enabled: true } },
94
+ channels: {
95
+ googlechat: {
96
+ serviceAccount: {
97
+ type: "service_account",
98
+ client_email: "bot@example.com",
99
+ private_key: "test-key",
100
+ token_uri: "https://oauth2.googleapis.com/token",
101
+ },
102
+ audienceType: "project-number",
103
+ dm: { allowFrom: ["users/123"] },
104
+ },
105
+ },
106
+ },
107
+ }),
108
+ ).toBe(false);
109
+ });
110
+
111
+ it("requires a top-level approval forwarding route before enabling native cards", async () => {
112
+ const runtime = googleChatApprovalCapability.nativeRuntime;
113
+ const googlechat = {
114
+ serviceAccount: {
115
+ type: "service_account" as const,
116
+ client_email: "bot@example.com",
117
+ private_key: "test-key",
118
+ token_uri: "https://oauth2.googleapis.com/token",
119
+ },
120
+ audienceType: "app-url" as const,
121
+ audience: "https://chat-app.example.test/googlechat",
122
+ dm: { allowFrom: ["users/123"] },
123
+ };
124
+
125
+ expect(
126
+ runtime?.availability.isConfigured({
127
+ cfg: { channels: { googlechat } },
128
+ }),
129
+ ).toBe(false);
130
+ expect(
131
+ runtime?.availability.isConfigured({
132
+ cfg: {
133
+ approvals: { exec: { enabled: false } },
134
+ channels: { googlechat },
135
+ },
136
+ }),
137
+ ).toBe(false);
138
+ expect(
139
+ runtime?.availability.isConfigured({
140
+ cfg: {
141
+ approvals: { exec: { enabled: true, mode: "targets" } },
142
+ channels: { googlechat },
143
+ },
144
+ }),
145
+ ).toBe(false);
146
+ expect(
147
+ runtime?.availability.isConfigured({
148
+ cfg: {
149
+ approvals: { plugin: { enabled: true } },
150
+ channels: { googlechat },
151
+ },
152
+ }),
153
+ ).toBe(true);
154
+ });
155
+
156
+ it("enables native cards for supported webhook audience modes", async () => {
157
+ const runtime = googleChatApprovalCapability.nativeRuntime;
158
+ expect(
159
+ runtime?.availability.isConfigured({
160
+ cfg: {
161
+ approvals: { exec: { enabled: true } },
162
+ channels: {
163
+ googlechat: {
164
+ serviceAccount: {
165
+ type: "service_account",
166
+ client_email: "bot@example.com",
167
+ private_key: "test-key",
168
+ token_uri: "https://oauth2.googleapis.com/token",
169
+ },
170
+ audienceType: "app-url",
171
+ audience: "https://chat-app.example.test/googlechat",
172
+ dm: { allowFrom: ["users/123"] },
173
+ },
174
+ },
175
+ },
176
+ }),
177
+ ).toBe(true);
178
+ expect(
179
+ runtime?.availability.isConfigured({
180
+ cfg: {
181
+ approvals: { exec: { enabled: true } },
182
+ channels: {
183
+ googlechat: {
184
+ serviceAccount: {
185
+ type: "service_account",
186
+ client_email: "bot@example.com",
187
+ private_key: "test-key",
188
+ token_uri: "https://oauth2.googleapis.com/token",
189
+ },
190
+ audienceType: "project-number",
191
+ audience: "1234567890",
192
+ dm: { allowFrom: ["users/123"] },
193
+ },
194
+ },
195
+ },
196
+ }),
197
+ ).toBe(true);
198
+ });
199
+
200
+ it("preserves Google Chat approval actor authorization", () => {
201
+ expect(
202
+ googleChatApprovalCapability.authorizeActorAction?.({
203
+ cfg: { channels: { googlechat: { dm: { allowFrom: ["users/123"] } } } },
204
+ senderId: "users/123",
205
+ action: "approve",
206
+ approvalKind: "plugin",
207
+ }),
208
+ ).toEqual({ authorized: true });
209
+
210
+ expect(
211
+ googleChatApprovalCapability.authorizeActorAction?.({
212
+ cfg: { channels: { googlechat: { dm: { allowFrom: ["users/123"] } } } },
213
+ senderId: "users/999",
214
+ action: "approve",
215
+ approvalKind: "plugin",
216
+ }),
217
+ ).toEqual({
218
+ authorized: false,
219
+ reason: "❌ You are not authorized to approve plugin requests on Google Chat.",
220
+ });
221
+ });
222
+
223
+ it("only handles approvals for the originating Google Chat account", () => {
224
+ const cfg: ACTAgentConfig = {
225
+ approvals: { exec: { enabled: true } },
226
+ channels: {
227
+ googlechat: {
228
+ accounts: {
229
+ alpha: {
230
+ enabled: true,
231
+ serviceAccount: {
232
+ type: "service_account",
233
+ client_email: "alpha@example.com",
234
+ private_key: "test-key",
235
+ token_uri: "https://oauth2.googleapis.com/token",
236
+ },
237
+ audienceType: "app-url",
238
+ audience: "https://alpha.example.com/googlechat",
239
+ appPrincipal: "123456789012345678901",
240
+ dm: { allowFrom: ["users/123"] },
241
+ },
242
+ beta: {
243
+ enabled: true,
244
+ serviceAccount: {
245
+ type: "service_account",
246
+ client_email: "beta@example.com",
247
+ private_key: "test-key",
248
+ token_uri: "https://oauth2.googleapis.com/token",
249
+ },
250
+ audienceType: "app-url",
251
+ audience: "https://beta.example.com/googlechat",
252
+ appPrincipal: "987654321098765432109",
253
+ dm: { allowFrom: ["users/456"] },
254
+ },
255
+ },
256
+ },
257
+ },
258
+ };
259
+ const request = {
260
+ id: "approval-1",
261
+ request: {
262
+ command: "echo hi",
263
+ turnSourceChannel: "googlechat",
264
+ turnSourceAccountId: "alpha",
265
+ turnSourceTo: "spaces/AAA",
266
+ },
267
+ } as never;
268
+
269
+ expect(
270
+ shouldHandleGoogleChatNativeApprovalRequest({
271
+ cfg,
272
+ accountId: "alpha",
273
+ request,
274
+ }),
275
+ ).toBe(true);
276
+ expect(
277
+ shouldHandleGoogleChatNativeApprovalRequest({
278
+ cfg,
279
+ accountId: "beta",
280
+ request,
281
+ }),
282
+ ).toBe(false);
283
+ });
284
+
285
+ it("does not handle exec approvals when only plugin approval forwarding is enabled", () => {
286
+ const cfg: ACTAgentConfig = {
287
+ approvals: { plugin: { enabled: true } },
288
+ channels: {
289
+ googlechat: {
290
+ serviceAccount: {
291
+ type: "service_account",
292
+ client_email: "bot@example.com",
293
+ private_key: "test-key",
294
+ token_uri: "https://oauth2.googleapis.com/token",
295
+ },
296
+ audienceType: "app-url",
297
+ audience: "https://chat-app.example.test/googlechat",
298
+ appPrincipal: "123456789012345678901",
299
+ dm: { allowFrom: ["users/123"] },
300
+ },
301
+ },
302
+ };
303
+ const request = {
304
+ id: "approval-1",
305
+ request: {
306
+ command: "echo hi",
307
+ turnSourceChannel: "googlechat",
308
+ turnSourceTo: "spaces/AAA",
309
+ },
310
+ } as never;
311
+
312
+ expect(
313
+ shouldHandleGoogleChatNativeApprovalRequest({
314
+ cfg,
315
+ request,
316
+ }),
317
+ ).toBe(false);
318
+ });
319
+
320
+ it("suppresses the local exec prompt when a Google Chat native route is active", () => {
321
+ expect(
322
+ shouldSuppressLocalGoogleChatExecApprovalPrompt({
323
+ cfg: {
324
+ approvals: { exec: { enabled: true } },
325
+ channels: { googlechat: GOOGLE_CHAT_APPROVAL_ACCOUNT },
326
+ },
327
+ payload: execApprovalPayload,
328
+ hint: activeExecApprovalHint,
329
+ }),
330
+ ).toBe(true);
331
+ });
332
+
333
+ it("keeps the local exec prompt when native Google Chat delivery cannot own it", () => {
334
+ expect(
335
+ shouldSuppressLocalGoogleChatExecApprovalPrompt({
336
+ cfg: {
337
+ approvals: { exec: { enabled: true } },
338
+ channels: { googlechat: GOOGLE_CHAT_APPROVAL_ACCOUNT },
339
+ },
340
+ payload: execApprovalPayload,
341
+ hint: {
342
+ kind: "approval-pending",
343
+ approvalKind: "exec",
344
+ nativeRouteActive: false,
345
+ },
346
+ }),
347
+ ).toBe(false);
348
+
349
+ expect(
350
+ shouldSuppressLocalGoogleChatExecApprovalPrompt({
351
+ cfg: {
352
+ approvals: { exec: { enabled: false } },
353
+ channels: { googlechat: GOOGLE_CHAT_APPROVAL_ACCOUNT },
354
+ },
355
+ payload: execApprovalPayload,
356
+ hint: activeExecApprovalHint,
357
+ }),
358
+ ).toBe(false);
359
+
360
+ expect(
361
+ shouldSuppressLocalGoogleChatExecApprovalPrompt({
362
+ cfg: {
363
+ approvals: { exec: { enabled: true } },
364
+ channels: {
365
+ googlechat: {
366
+ ...GOOGLE_CHAT_APPROVAL_ACCOUNT,
367
+ audience: undefined,
368
+ },
369
+ },
370
+ },
371
+ payload: execApprovalPayload,
372
+ hint: activeExecApprovalHint,
373
+ }),
374
+ ).toBe(false);
375
+
376
+ expect(
377
+ shouldSuppressLocalGoogleChatExecApprovalPrompt({
378
+ cfg: {
379
+ approvals: { exec: { enabled: true } },
380
+ channels: { googlechat: GOOGLE_CHAT_APPROVAL_ACCOUNT },
381
+ },
382
+ payload: {
383
+ channelData: {
384
+ execApproval: {
385
+ approvalId: "12345678-1234-1234-1234-123456789012",
386
+ approvalSlug: "12345678",
387
+ approvalKind: "plugin",
388
+ },
389
+ },
390
+ },
391
+ hint: {
392
+ kind: "approval-pending",
393
+ approvalKind: "plugin",
394
+ nativeRouteActive: true,
395
+ },
396
+ }),
397
+ ).toBe(false);
398
+ });
399
+ });
@@ -0,0 +1,246 @@
1
+ import {
2
+ createApproverRestrictedNativeApprovalCapability,
3
+ splitChannelApprovalCapability,
4
+ } from "actagent/plugin-sdk/approval-delivery-runtime";
5
+ import { createLazyChannelApprovalNativeRuntimeAdapter } from "actagent/plugin-sdk/approval-handler-adapter-runtime";
6
+ import type { ChannelApprovalNativeRuntimeAdapter } from "actagent/plugin-sdk/approval-handler-runtime";
7
+ import {
8
+ createChannelApproverDmTargetResolver,
9
+ createChannelNativeOriginTargetResolver,
10
+ createNativeApprovalChannelRouteGates,
11
+ shouldSuppressLocalNativeExecApprovalPrompt,
12
+ } from "actagent/plugin-sdk/approval-native-runtime";
13
+ import type {
14
+ ExecApprovalRequest,
15
+ PluginApprovalRequest,
16
+ } from "actagent/plugin-sdk/approval-runtime";
17
+ import type {
18
+ ChannelApprovalCapability,
19
+ ChannelOutboundPayloadHint,
20
+ } from "actagent/plugin-sdk/channel-contract";
21
+ import type { ACTAgentConfig } from "actagent/plugin-sdk/config-contracts";
22
+ import type { ReplyPayload } from "actagent/plugin-sdk/reply-runtime";
23
+ import {
24
+ normalizeLowercaseStringOrEmpty,
25
+ normalizeOptionalString,
26
+ } from "actagent/plugin-sdk/string-coerce-runtime";
27
+ import {
28
+ listGoogleChatAccountIds,
29
+ resolveDefaultGoogleChatAccountId,
30
+ resolveGoogleChatAccount,
31
+ } from "./accounts.js";
32
+ import {
33
+ getGoogleChatApprovalApprovers,
34
+ googleChatApprovalAuth,
35
+ normalizeGoogleChatApproverId,
36
+ } from "./approval-auth.js";
37
+ import { isGoogleChatSpaceTarget, normalizeGoogleChatTarget } from "./targets.js";
38
+
39
+ type ApprovalRequest = ExecApprovalRequest | PluginApprovalRequest;
40
+ type GoogleChatApprovalTarget = {
41
+ to: string;
42
+ accountId?: string | null;
43
+ threadId?: string | number | null;
44
+ };
45
+ type ChannelApprovalForwardTarget = Parameters<
46
+ NonNullable<
47
+ NonNullable<ChannelApprovalCapability["delivery"]>["shouldSuppressForwardingFallback"]
48
+ >
49
+ >[0]["target"];
50
+
51
+ const DEFAULT_APPROVAL_FORWARDING_MODE = "session";
52
+
53
+ function isGoogleChatAccountConfigured(params: {
54
+ cfg: Parameters<typeof resolveGoogleChatAccount>[0]["cfg"];
55
+ accountId?: string | null;
56
+ }): boolean {
57
+ const account = resolveGoogleChatAccount(params);
58
+ return account.enabled && account.credentialSource !== "none";
59
+ }
60
+
61
+ function hasGoogleChatWebhookApprovalAuthConfig(params: {
62
+ cfg: Parameters<typeof resolveGoogleChatAccount>[0]["cfg"];
63
+ accountId?: string | null;
64
+ }): boolean {
65
+ const account = resolveGoogleChatAccount(params).config;
66
+ const audience = normalizeOptionalString(account.audience);
67
+ if (!audience) {
68
+ return false;
69
+ }
70
+ if (account.audienceType === "project-number") {
71
+ return true;
72
+ }
73
+ return account.audienceType === "app-url";
74
+ }
75
+
76
+ function isGoogleChatApprovalTransportEnabled(params: {
77
+ cfg: Parameters<typeof resolveGoogleChatAccount>[0]["cfg"];
78
+ accountId?: string | null;
79
+ }): boolean {
80
+ return isGoogleChatAccountConfigured(params) && hasGoogleChatWebhookApprovalAuthConfig(params);
81
+ }
82
+
83
+ function normalizeGoogleChatForwardTarget(
84
+ target: Pick<ChannelApprovalForwardTarget, "channel" | "to" | "accountId" | "threadId">,
85
+ ): GoogleChatApprovalTarget | null {
86
+ if (normalizeLowercaseStringOrEmpty(target.channel) !== "googlechat") {
87
+ return null;
88
+ }
89
+ const to = normalizeGoogleChatTarget(target.to);
90
+ return to
91
+ ? {
92
+ to,
93
+ accountId: normalizeOptionalString(target.accountId),
94
+ threadId: target.threadId ?? null,
95
+ }
96
+ : null;
97
+ }
98
+
99
+ function resolveTurnSourceGoogleChatOriginTarget(
100
+ request: ApprovalRequest,
101
+ ): GoogleChatApprovalTarget | null {
102
+ const turnSourceChannel = normalizeLowercaseStringOrEmpty(request.request.turnSourceChannel);
103
+ if (turnSourceChannel !== "googlechat") {
104
+ return null;
105
+ }
106
+ const target = normalizeGoogleChatTarget(request.request.turnSourceTo ?? "");
107
+ if (!target || !isGoogleChatSpaceTarget(target)) {
108
+ return null;
109
+ }
110
+ return {
111
+ to: target,
112
+ accountId: normalizeOptionalString(request.request.turnSourceAccountId),
113
+ threadId: request.request.turnSourceThreadId ?? null,
114
+ };
115
+ }
116
+
117
+ const googleChatApprovalRouteGates = createNativeApprovalChannelRouteGates({
118
+ channel: "googlechat",
119
+ defaultForwardingMode: DEFAULT_APPROVAL_FORWARDING_MODE,
120
+ isTransportEnabled: isGoogleChatApprovalTransportEnabled,
121
+ listAccountIds: listGoogleChatAccountIds,
122
+ resolveDefaultAccountId: resolveDefaultGoogleChatAccountId,
123
+ normalizeForwardTarget: normalizeGoogleChatForwardTarget,
124
+ resolveTurnSourceTarget: resolveTurnSourceGoogleChatOriginTarget,
125
+ });
126
+
127
+ export function isGoogleChatNativeApprovalClientEnabled(params: {
128
+ cfg: Parameters<typeof resolveGoogleChatAccount>[0]["cfg"];
129
+ accountId?: string | null;
130
+ }): boolean {
131
+ return (
132
+ googleChatApprovalRouteGates.canAnyApprovalPotentiallyRouteToChannel({
133
+ ...params,
134
+ nativeSessionOnly: true,
135
+ }) && getGoogleChatApprovalApprovers(params).length > 0
136
+ );
137
+ }
138
+
139
+ function resolveSessionGoogleChatOriginTarget(sessionTarget: {
140
+ to: string;
141
+ threadId?: string | number | null;
142
+ }): GoogleChatApprovalTarget | null {
143
+ const target = normalizeGoogleChatTarget(sessionTarget.to);
144
+ return target && isGoogleChatSpaceTarget(target)
145
+ ? { to: target, threadId: sessionTarget.threadId ?? null }
146
+ : null;
147
+ }
148
+
149
+ export function shouldHandleGoogleChatNativeApprovalRequest(params: {
150
+ cfg: Parameters<typeof resolveGoogleChatAccount>[0]["cfg"];
151
+ accountId?: string | null;
152
+ request: ApprovalRequest;
153
+ }): boolean {
154
+ return (
155
+ googleChatApprovalRouteGates.shouldHandleApprovalRequest(params) &&
156
+ getGoogleChatApprovalApprovers(params).length > 0 &&
157
+ Boolean(resolveTurnSourceGoogleChatOriginTarget(params.request))
158
+ );
159
+ }
160
+
161
+ export function shouldSuppressLocalGoogleChatExecApprovalPrompt(params: {
162
+ cfg: ACTAgentConfig;
163
+ accountId?: string | null;
164
+ payload: ReplyPayload;
165
+ hint?: ChannelOutboundPayloadHint;
166
+ }): boolean {
167
+ return shouldSuppressLocalNativeExecApprovalPrompt({
168
+ ...params,
169
+ isNativeDeliveryEnabled: isGoogleChatNativeApprovalClientEnabled,
170
+ });
171
+ }
172
+
173
+ const resolveGoogleChatOriginTarget = createChannelNativeOriginTargetResolver({
174
+ channel: "googlechat",
175
+ shouldHandleRequest: shouldHandleGoogleChatNativeApprovalRequest,
176
+ resolveTurnSourceTarget: resolveTurnSourceGoogleChatOriginTarget,
177
+ resolveSessionTarget: resolveSessionGoogleChatOriginTarget,
178
+ });
179
+
180
+ const resolveGoogleChatApproverDmTargets = createChannelApproverDmTargetResolver({
181
+ shouldHandleRequest: shouldHandleGoogleChatNativeApprovalRequest,
182
+ resolveApprovers: getGoogleChatApprovalApprovers,
183
+ mapApprover: (approver, params) => {
184
+ const to = normalizeGoogleChatApproverId(approver);
185
+ return to
186
+ ? {
187
+ to,
188
+ accountId: normalizeOptionalString(params.accountId),
189
+ }
190
+ : null;
191
+ },
192
+ });
193
+
194
+ export const googleChatApprovalCapability: ChannelApprovalCapability =
195
+ createApproverRestrictedNativeApprovalCapability({
196
+ channel: "googlechat",
197
+ channelLabel: "Google Chat",
198
+ describeExecApprovalSetup: ({ accountId }) => {
199
+ const prefix =
200
+ accountId && accountId !== "default"
201
+ ? `channels.googlechat.accounts.${accountId}`
202
+ : "channels.googlechat";
203
+ return `Approve it from the Web UI or terminal UI for now. Google Chat supports native approvals for this account when the webhook and service account are configured. Configure \`${prefix}.dm.allowFrom\` or \`${prefix}.defaultTo\` with numeric \`users/{id}\` approvers.`;
204
+ },
205
+ listAccountIds: listGoogleChatAccountIds,
206
+ hasApprovers: ({ cfg, accountId }) =>
207
+ getGoogleChatApprovalApprovers({ cfg, accountId }).length > 0,
208
+ isExecAuthorizedSender: ({ cfg, accountId, senderId }) =>
209
+ googleChatApprovalAuth.authorizeActorAction?.({
210
+ cfg,
211
+ accountId,
212
+ senderId,
213
+ action: "approve",
214
+ approvalKind: "exec",
215
+ })?.authorized ?? false,
216
+ isPluginAuthorizedSender: ({ cfg, accountId, senderId }) =>
217
+ googleChatApprovalAuth.authorizeActorAction?.({
218
+ cfg,
219
+ accountId,
220
+ senderId,
221
+ action: "approve",
222
+ approvalKind: "plugin",
223
+ })?.authorized ?? false,
224
+ isNativeDeliveryEnabled: isGoogleChatNativeApprovalClientEnabled,
225
+ resolveNativeDeliveryMode: () => "channel",
226
+ requireMatchingTurnSourceChannel: true,
227
+ resolveSuppressionAccountId: ({ target, request }) =>
228
+ normalizeOptionalString(target.accountId) ??
229
+ normalizeOptionalString(request.request.turnSourceAccountId),
230
+ resolveOriginTarget: resolveGoogleChatOriginTarget,
231
+ resolveApproverDmTargets: resolveGoogleChatApproverDmTargets,
232
+ nativeRuntime: createLazyChannelApprovalNativeRuntimeAdapter({
233
+ eventKinds: ["exec", "plugin"],
234
+ isConfigured: ({ cfg, accountId }) =>
235
+ isGoogleChatNativeApprovalClientEnabled({ cfg, accountId }),
236
+ shouldHandle: ({ cfg, accountId, request }) =>
237
+ shouldHandleGoogleChatNativeApprovalRequest({ cfg, accountId, request }),
238
+ load: async () =>
239
+ (await import("./approval-handler.runtime.js"))
240
+ .googleChatApprovalNativeRuntime as unknown as ChannelApprovalNativeRuntimeAdapter,
241
+ }),
242
+ });
243
+
244
+ export const googleChatNativeApprovalAdapter = splitChannelApprovalCapability(
245
+ googleChatApprovalCapability,
246
+ );