@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.
- package/README.md +11 -0
- package/actagent.plugin.json +17 -0
- package/api.ts +4 -0
- package/channel-config-api.ts +2 -0
- package/channel-plugin-api.ts +2 -0
- package/config-api.ts +3 -0
- package/contract-api.ts +6 -0
- package/directory-contract-api.ts +7 -0
- package/doctor-contract-api.ts +2 -0
- package/index.ts +21 -0
- package/npm-shrinkwrap.json +314 -0
- package/package.json +88 -0
- package/runtime-api.ts +61 -0
- package/secret-contract-api.ts +6 -0
- package/setup-entry.ts +14 -0
- package/setup-plugin-api.ts +3 -0
- package/src/accounts.ts +185 -0
- package/src/actions.test.ts +312 -0
- package/src/actions.ts +228 -0
- package/src/api.ts +346 -0
- package/src/approval-auth.test.ts +25 -0
- package/src/approval-auth.ts +38 -0
- package/src/approval-card-actions.test.ts +113 -0
- package/src/approval-card-actions.ts +307 -0
- package/src/approval-card-click.test.ts +279 -0
- package/src/approval-card-click.ts +94 -0
- package/src/approval-handler.runtime.test.ts +388 -0
- package/src/approval-handler.runtime.ts +413 -0
- package/src/approval-native.test.ts +399 -0
- package/src/approval-native.ts +246 -0
- package/src/auth.ts +219 -0
- package/src/channel-base.ts +123 -0
- package/src/channel-config.test.ts +174 -0
- package/src/channel.adapters.ts +363 -0
- package/src/channel.deps.runtime.ts +30 -0
- package/src/channel.runtime.ts +18 -0
- package/src/channel.setup.ts +7 -0
- package/src/channel.test.ts +845 -0
- package/src/channel.ts +214 -0
- package/src/config-schema.test.ts +32 -0
- package/src/config-schema.ts +4 -0
- package/src/doctor-contract.test.ts +76 -0
- package/src/doctor-contract.ts +181 -0
- package/src/doctor.ts +58 -0
- package/src/gateway.ts +84 -0
- package/src/google-auth.runtime.test.ts +571 -0
- package/src/google-auth.runtime.ts +570 -0
- package/src/group-policy.ts +18 -0
- package/src/monitor-access.test.ts +492 -0
- package/src/monitor-access.ts +466 -0
- package/src/monitor-durable.test.ts +40 -0
- package/src/monitor-durable.ts +24 -0
- package/src/monitor-reply-delivery.ts +162 -0
- package/src/monitor-routing.ts +66 -0
- package/src/monitor-types.ts +34 -0
- package/src/monitor-webhook.test.ts +670 -0
- package/src/monitor-webhook.ts +361 -0
- package/src/monitor.reply-delivery.test.ts +145 -0
- package/src/monitor.test.ts +389 -0
- package/src/monitor.ts +530 -0
- package/src/monitor.webhook-routing.test.ts +258 -0
- package/src/runtime.ts +10 -0
- package/src/secret-contract.test.ts +61 -0
- package/src/secret-contract.ts +162 -0
- package/src/setup-core.ts +41 -0
- package/src/setup-surface.ts +244 -0
- package/src/setup.test.ts +620 -0
- package/src/targets.test.ts +562 -0
- package/src/targets.ts +67 -0
- package/src/types.config.ts +4 -0
- package/src/types.ts +139 -0
- package/test-api.ts +3 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { resolveApprovalOverGateway } from "actagent/plugin-sdk/approval-gateway-runtime";
|
|
2
|
+
import { googleChatApprovalAuth } from "./approval-auth.js";
|
|
3
|
+
import {
|
|
4
|
+
claimGoogleChatApprovalCardBinding,
|
|
5
|
+
completeGoogleChatApprovalCardBinding,
|
|
6
|
+
getGoogleChatApprovalCardBinding,
|
|
7
|
+
releaseGoogleChatApprovalCardBinding,
|
|
8
|
+
readGoogleChatApprovalActionToken,
|
|
9
|
+
} from "./approval-card-actions.js";
|
|
10
|
+
import type { WebhookTarget } from "./monitor-types.js";
|
|
11
|
+
import type { GoogleChatEvent } from "./types.js";
|
|
12
|
+
|
|
13
|
+
function logIgnored(target: WebhookTarget, message: string): void {
|
|
14
|
+
target.runtime.log?.(`[${target.account.accountId}] googlechat approval ignored: ${message}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function maybeHandleGoogleChatApprovalCardClick(params: {
|
|
18
|
+
event: GoogleChatEvent;
|
|
19
|
+
target: WebhookTarget;
|
|
20
|
+
}): Promise<boolean> {
|
|
21
|
+
const eventType = params.event.type ?? params.event.eventType;
|
|
22
|
+
if (eventType !== "CARD_CLICKED") {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const token = readGoogleChatApprovalActionToken(params.event);
|
|
26
|
+
if (!token) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const binding = getGoogleChatApprovalCardBinding(token);
|
|
31
|
+
if (!binding) {
|
|
32
|
+
logIgnored(params.target, "unknown or expired card token");
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (binding.accountId !== params.target.account.accountId) {
|
|
36
|
+
logIgnored(params.target, "card token account mismatch");
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (params.event.space?.name !== binding.spaceName) {
|
|
40
|
+
logIgnored(params.target, "card token space mismatch");
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (params.event.message?.name && params.event.message.name !== binding.messageName) {
|
|
44
|
+
logIgnored(params.target, "card token message mismatch");
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (!binding.allowedDecisions.includes(binding.decision)) {
|
|
48
|
+
logIgnored(params.target, "card token decision is no longer allowed");
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const actor = params.event.user?.name;
|
|
53
|
+
const auth = googleChatApprovalAuth.authorizeActorAction?.({
|
|
54
|
+
cfg: params.target.config,
|
|
55
|
+
accountId: params.target.account.accountId,
|
|
56
|
+
senderId: actor,
|
|
57
|
+
action: "approve",
|
|
58
|
+
approvalKind: binding.approvalKind,
|
|
59
|
+
});
|
|
60
|
+
if (!auth?.authorized) {
|
|
61
|
+
logIgnored(params.target, `unauthorized actor ${actor || "unknown"}`);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const claim = claimGoogleChatApprovalCardBinding(token);
|
|
66
|
+
if (claim.kind === "missing") {
|
|
67
|
+
logIgnored(params.target, "card token already consumed");
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
if (claim.kind === "in-flight") {
|
|
71
|
+
logIgnored(params.target, "card token resolve already in flight");
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
const consumed = claim.binding;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await resolveApprovalOverGateway({
|
|
78
|
+
cfg: params.target.config,
|
|
79
|
+
approvalId: consumed.approvalId,
|
|
80
|
+
decision: consumed.decision,
|
|
81
|
+
senderId: actor,
|
|
82
|
+
allowPluginFallback: consumed.approvalKind === "exec",
|
|
83
|
+
clientDisplayName: `Google Chat approval (${actor?.trim() || "unknown"})`,
|
|
84
|
+
});
|
|
85
|
+
} catch (error) {
|
|
86
|
+
releaseGoogleChatApprovalCardBinding(token);
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
completeGoogleChatApprovalCardBinding(token);
|
|
90
|
+
params.target.runtime.log?.(
|
|
91
|
+
`[${params.target.account.accountId}] googlechat approval resolved id=${consumed.approvalId} decision=${consumed.decision} sender=${actor || "unknown"}`,
|
|
92
|
+
);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExecApprovalPendingView,
|
|
3
|
+
ResolvedApprovalView,
|
|
4
|
+
} from "actagent/plugin-sdk/approval-handler-runtime";
|
|
5
|
+
import type { ACTAgentConfig } from "actagent/plugin-sdk/config-contracts";
|
|
6
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
import type { ResolvedGoogleChatAccount } from "./accounts.js";
|
|
8
|
+
import {
|
|
9
|
+
clearGoogleChatApprovalCardBindingsForTest,
|
|
10
|
+
shouldSuppressGoogleChatManualExecApprovalFollowupText,
|
|
11
|
+
} from "./approval-card-actions.js";
|
|
12
|
+
|
|
13
|
+
const sendGoogleChatMessage = vi.hoisted(() => vi.fn());
|
|
14
|
+
const updateGoogleChatMessage = vi.hoisted(() => vi.fn());
|
|
15
|
+
|
|
16
|
+
vi.mock("./api.js", async () => {
|
|
17
|
+
const actual = await vi.importActual<typeof import("./api.js")>("./api.js");
|
|
18
|
+
return {
|
|
19
|
+
...actual,
|
|
20
|
+
sendGoogleChatMessage,
|
|
21
|
+
updateGoogleChatMessage,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const { googleChatApprovalNativeRuntime } = await import("./approval-handler.runtime.js");
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
vi.clearAllMocks();
|
|
29
|
+
clearGoogleChatApprovalCardBindingsForTest();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const account = {
|
|
33
|
+
accountId: "default",
|
|
34
|
+
enabled: true,
|
|
35
|
+
credentialSource: "inline",
|
|
36
|
+
config: {
|
|
37
|
+
audienceType: "app-url",
|
|
38
|
+
audience: "https://chat-app.example.test/googlechat",
|
|
39
|
+
appPrincipal: "123456789012345678901",
|
|
40
|
+
},
|
|
41
|
+
} as ResolvedGoogleChatAccount;
|
|
42
|
+
|
|
43
|
+
const cfg: ACTAgentConfig = {
|
|
44
|
+
channels: {
|
|
45
|
+
googlechat: {
|
|
46
|
+
serviceAccount: {
|
|
47
|
+
type: "service_account",
|
|
48
|
+
client_email: "bot@example.com",
|
|
49
|
+
private_key: "test-key",
|
|
50
|
+
token_uri: "https://oauth2.googleapis.com/token",
|
|
51
|
+
},
|
|
52
|
+
audienceType: "app-url",
|
|
53
|
+
audience: "https://chat-app.example.test/googlechat",
|
|
54
|
+
appPrincipal: "123456789012345678901",
|
|
55
|
+
dm: { allowFrom: ["users/123"] },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function createPendingView(): ExecApprovalPendingView {
|
|
61
|
+
return {
|
|
62
|
+
approvalId: "approval-1",
|
|
63
|
+
approvalKind: "exec",
|
|
64
|
+
phase: "pending",
|
|
65
|
+
title: "Exec Approval Required",
|
|
66
|
+
description: "A command needs your approval.",
|
|
67
|
+
metadata: [{ label: "Agent", value: "main" }],
|
|
68
|
+
ask: "on-miss",
|
|
69
|
+
agentId: "main",
|
|
70
|
+
warningText: null,
|
|
71
|
+
commandAnalysis: null,
|
|
72
|
+
commandText: "echo hi",
|
|
73
|
+
commandPreview: null,
|
|
74
|
+
cwd: "/tmp",
|
|
75
|
+
envKeys: [],
|
|
76
|
+
host: "gateway",
|
|
77
|
+
nodeId: null,
|
|
78
|
+
sessionKey: "agent:main:googlechat:spaces/AAA",
|
|
79
|
+
actions: [
|
|
80
|
+
{
|
|
81
|
+
kind: "decision",
|
|
82
|
+
decision: "allow-once",
|
|
83
|
+
label: "Allow Once",
|
|
84
|
+
style: "success",
|
|
85
|
+
command: "/approve approval-1 allow-once",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
kind: "decision",
|
|
89
|
+
decision: "deny",
|
|
90
|
+
label: "Deny",
|
|
91
|
+
style: "danger",
|
|
92
|
+
command: "/approve approval-1 deny",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
expiresAtMs: Date.now() + 60_000,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createDeferred<T>(): {
|
|
100
|
+
promise: Promise<T>;
|
|
101
|
+
reject: (reason?: unknown) => void;
|
|
102
|
+
resolve: (value: T) => void;
|
|
103
|
+
} {
|
|
104
|
+
let resolve: (value: T) => void = () => {};
|
|
105
|
+
let reject: (reason?: unknown) => void = () => {};
|
|
106
|
+
const promise = new Promise<T>((innerResolve, innerReject) => {
|
|
107
|
+
resolve = innerResolve;
|
|
108
|
+
reject = innerReject;
|
|
109
|
+
});
|
|
110
|
+
return { promise, reject, resolve };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
describe("googleChatApprovalNativeRuntime", () => {
|
|
114
|
+
async function preparePendingDelivery(view = createPendingView()) {
|
|
115
|
+
const nowMs = Date.now();
|
|
116
|
+
const request = {
|
|
117
|
+
id: view.approvalId,
|
|
118
|
+
request: { command: view.commandText },
|
|
119
|
+
createdAtMs: nowMs,
|
|
120
|
+
expiresAtMs: view.expiresAtMs,
|
|
121
|
+
};
|
|
122
|
+
const pendingPayload = await googleChatApprovalNativeRuntime.presentation.buildPendingPayload({
|
|
123
|
+
cfg,
|
|
124
|
+
accountId: "default",
|
|
125
|
+
context: { account },
|
|
126
|
+
request,
|
|
127
|
+
approvalKind: "exec",
|
|
128
|
+
nowMs,
|
|
129
|
+
view,
|
|
130
|
+
});
|
|
131
|
+
const plannedTarget = {
|
|
132
|
+
surface: "origin" as const,
|
|
133
|
+
target: { to: "spaces/AAA", threadId: "threads/T1" },
|
|
134
|
+
reason: "preferred" as const,
|
|
135
|
+
};
|
|
136
|
+
const prepared = await googleChatApprovalNativeRuntime.transport.prepareTarget({
|
|
137
|
+
cfg,
|
|
138
|
+
accountId: "default",
|
|
139
|
+
context: { account },
|
|
140
|
+
plannedTarget,
|
|
141
|
+
request,
|
|
142
|
+
approvalKind: "exec",
|
|
143
|
+
view,
|
|
144
|
+
pendingPayload,
|
|
145
|
+
});
|
|
146
|
+
if (!prepared) {
|
|
147
|
+
throw new Error("Expected prepared target");
|
|
148
|
+
}
|
|
149
|
+
return { pendingPayload, plannedTarget, prepared, request, view };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
it("sends pending cards and updates the delivered message without buttons", async () => {
|
|
153
|
+
sendGoogleChatMessage.mockResolvedValue({ messageName: "spaces/AAA/messages/msg-1" });
|
|
154
|
+
updateGoogleChatMessage.mockResolvedValue({ messageName: "spaces/AAA/messages/msg-1" });
|
|
155
|
+
|
|
156
|
+
const view = createPendingView();
|
|
157
|
+
const pendingPayload = await googleChatApprovalNativeRuntime.presentation.buildPendingPayload({
|
|
158
|
+
cfg,
|
|
159
|
+
accountId: "default",
|
|
160
|
+
context: { account },
|
|
161
|
+
request: {
|
|
162
|
+
id: "approval-1",
|
|
163
|
+
request: { command: "echo hi" },
|
|
164
|
+
createdAtMs: Date.now(),
|
|
165
|
+
expiresAtMs: view.expiresAtMs,
|
|
166
|
+
},
|
|
167
|
+
approvalKind: "exec",
|
|
168
|
+
nowMs: Date.now(),
|
|
169
|
+
view,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(JSON.stringify(pendingPayload)).toContain("cardsV2");
|
|
173
|
+
expect(JSON.stringify(pendingPayload.cardsV2)).toContain(
|
|
174
|
+
"https://chat-app.example.test/googlechat",
|
|
175
|
+
);
|
|
176
|
+
expect(JSON.stringify(pendingPayload.cardsV2)).not.toContain("/approve approval-1 allow-once");
|
|
177
|
+
|
|
178
|
+
const prepared = await googleChatApprovalNativeRuntime.transport.prepareTarget({
|
|
179
|
+
cfg,
|
|
180
|
+
accountId: "default",
|
|
181
|
+
context: { account },
|
|
182
|
+
plannedTarget: {
|
|
183
|
+
surface: "origin",
|
|
184
|
+
target: { to: "spaces/AAA", threadId: "threads/T1" },
|
|
185
|
+
reason: "preferred",
|
|
186
|
+
},
|
|
187
|
+
request: {
|
|
188
|
+
id: "approval-1",
|
|
189
|
+
request: { command: "echo hi" },
|
|
190
|
+
createdAtMs: Date.now(),
|
|
191
|
+
expiresAtMs: view.expiresAtMs,
|
|
192
|
+
},
|
|
193
|
+
approvalKind: "exec",
|
|
194
|
+
view,
|
|
195
|
+
pendingPayload,
|
|
196
|
+
});
|
|
197
|
+
if (!prepared) {
|
|
198
|
+
throw new Error("Expected prepared target");
|
|
199
|
+
}
|
|
200
|
+
const entry = await googleChatApprovalNativeRuntime.transport.deliverPending({
|
|
201
|
+
cfg,
|
|
202
|
+
accountId: "default",
|
|
203
|
+
context: { account },
|
|
204
|
+
plannedTarget: {
|
|
205
|
+
surface: "origin",
|
|
206
|
+
target: { to: "spaces/AAA", threadId: "threads/T1" },
|
|
207
|
+
reason: "preferred",
|
|
208
|
+
},
|
|
209
|
+
preparedTarget: prepared.target,
|
|
210
|
+
request: {
|
|
211
|
+
id: "approval-1",
|
|
212
|
+
request: { command: "echo hi" },
|
|
213
|
+
createdAtMs: Date.now(),
|
|
214
|
+
expiresAtMs: view.expiresAtMs,
|
|
215
|
+
},
|
|
216
|
+
approvalKind: "exec",
|
|
217
|
+
view,
|
|
218
|
+
pendingPayload,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(sendGoogleChatMessage).toHaveBeenCalledWith({
|
|
222
|
+
account,
|
|
223
|
+
space: "spaces/AAA",
|
|
224
|
+
cardsV2: expect.any(Array),
|
|
225
|
+
thread: "threads/T1",
|
|
226
|
+
});
|
|
227
|
+
expect(sendGoogleChatMessage.mock.calls[0]?.[0]).not.toHaveProperty("text");
|
|
228
|
+
expect(entry).toEqual({
|
|
229
|
+
accountId: "default",
|
|
230
|
+
spaceName: "spaces/AAA",
|
|
231
|
+
messageName: "spaces/AAA/messages/msg-1",
|
|
232
|
+
threadName: "threads/T1",
|
|
233
|
+
actionTokens: expect.any(Array),
|
|
234
|
+
});
|
|
235
|
+
expect(
|
|
236
|
+
shouldSuppressGoogleChatManualExecApprovalFollowupText(
|
|
237
|
+
"Please reply with:\n/approve approval-1 allow-once",
|
|
238
|
+
),
|
|
239
|
+
).toBe(true);
|
|
240
|
+
|
|
241
|
+
const resolvedView: ResolvedApprovalView = {
|
|
242
|
+
...view,
|
|
243
|
+
phase: "resolved",
|
|
244
|
+
decision: "allow-once",
|
|
245
|
+
resolvedBy: "users/123",
|
|
246
|
+
};
|
|
247
|
+
const final = await googleChatApprovalNativeRuntime.presentation.buildResolvedResult({
|
|
248
|
+
cfg,
|
|
249
|
+
accountId: "default",
|
|
250
|
+
context: { account },
|
|
251
|
+
request: {
|
|
252
|
+
id: "approval-1",
|
|
253
|
+
request: { command: "echo hi" },
|
|
254
|
+
createdAtMs: Date.now(),
|
|
255
|
+
expiresAtMs: view.expiresAtMs,
|
|
256
|
+
},
|
|
257
|
+
resolved: {
|
|
258
|
+
id: "approval-1",
|
|
259
|
+
decision: "allow-once",
|
|
260
|
+
resolvedBy: "users/123",
|
|
261
|
+
ts: Date.now(),
|
|
262
|
+
},
|
|
263
|
+
view: resolvedView,
|
|
264
|
+
entry,
|
|
265
|
+
});
|
|
266
|
+
expect(final.kind).toBe("update");
|
|
267
|
+
if (final.kind !== "update" || !entry) {
|
|
268
|
+
throw new Error("Expected update result and entry");
|
|
269
|
+
}
|
|
270
|
+
await googleChatApprovalNativeRuntime.transport.updateEntry?.({
|
|
271
|
+
cfg,
|
|
272
|
+
accountId: "default",
|
|
273
|
+
context: { account },
|
|
274
|
+
entry,
|
|
275
|
+
payload: final.payload,
|
|
276
|
+
phase: "resolved",
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
expect(updateGoogleChatMessage).toHaveBeenCalledWith({
|
|
280
|
+
account,
|
|
281
|
+
messageName: "spaces/AAA/messages/msg-1",
|
|
282
|
+
cardsV2: expect.any(Array),
|
|
283
|
+
});
|
|
284
|
+
expect(updateGoogleChatMessage.mock.calls[0]?.[0]).not.toHaveProperty("text");
|
|
285
|
+
expect(JSON.stringify(final.payload)).not.toContain("buttonList");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("suppresses manual approval follow-ups while the native card send is in flight", async () => {
|
|
289
|
+
const deferred = createDeferred<{ messageName: string }>();
|
|
290
|
+
sendGoogleChatMessage.mockReturnValue(deferred.promise);
|
|
291
|
+
const { pendingPayload, plannedTarget, prepared, request, view } =
|
|
292
|
+
await preparePendingDelivery();
|
|
293
|
+
|
|
294
|
+
const deliveryPromise = googleChatApprovalNativeRuntime.transport.deliverPending({
|
|
295
|
+
cfg,
|
|
296
|
+
accountId: "default",
|
|
297
|
+
context: { account },
|
|
298
|
+
plannedTarget,
|
|
299
|
+
preparedTarget: prepared.target,
|
|
300
|
+
request,
|
|
301
|
+
approvalKind: "exec",
|
|
302
|
+
view,
|
|
303
|
+
pendingPayload,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
await vi.waitFor(() => expect(sendGoogleChatMessage).toHaveBeenCalled());
|
|
307
|
+
expect(
|
|
308
|
+
shouldSuppressGoogleChatManualExecApprovalFollowupText(
|
|
309
|
+
"Please reply with:\n`/approve approval-1 allow-once`",
|
|
310
|
+
),
|
|
311
|
+
).toBe(true);
|
|
312
|
+
|
|
313
|
+
deferred.resolve({ messageName: "spaces/AAA/messages/msg-1" });
|
|
314
|
+
await expect(deliveryPromise).resolves.toEqual(
|
|
315
|
+
expect.objectContaining({ messageName: "spaces/AAA/messages/msg-1" }),
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("restores manual approval follow-ups when the native card send fails", async () => {
|
|
320
|
+
sendGoogleChatMessage.mockRejectedValue(new Error("send failed"));
|
|
321
|
+
const { pendingPayload, plannedTarget, prepared, request, view } =
|
|
322
|
+
await preparePendingDelivery();
|
|
323
|
+
|
|
324
|
+
await expect(
|
|
325
|
+
googleChatApprovalNativeRuntime.transport.deliverPending({
|
|
326
|
+
cfg,
|
|
327
|
+
accountId: "default",
|
|
328
|
+
context: { account },
|
|
329
|
+
plannedTarget,
|
|
330
|
+
preparedTarget: prepared.target,
|
|
331
|
+
request,
|
|
332
|
+
approvalKind: "exec",
|
|
333
|
+
view,
|
|
334
|
+
pendingPayload,
|
|
335
|
+
}),
|
|
336
|
+
).rejects.toThrow("send failed");
|
|
337
|
+
expect(
|
|
338
|
+
shouldSuppressGoogleChatManualExecApprovalFollowupText(
|
|
339
|
+
"Please reply with:\n`/approve approval-1 allow-once`",
|
|
340
|
+
),
|
|
341
|
+
).toBe(false);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("uses the named Chat action when app-url add-on principal binding is absent", async () => {
|
|
345
|
+
const view = createPendingView();
|
|
346
|
+
const pendingPayload = await googleChatApprovalNativeRuntime.presentation.buildPendingPayload({
|
|
347
|
+
cfg: {
|
|
348
|
+
channels: {
|
|
349
|
+
googlechat: {
|
|
350
|
+
serviceAccount: {
|
|
351
|
+
type: "service_account",
|
|
352
|
+
client_email: "bot@example.com",
|
|
353
|
+
private_key: "test-key",
|
|
354
|
+
token_uri: "https://oauth2.googleapis.com/token",
|
|
355
|
+
},
|
|
356
|
+
audienceType: "app-url",
|
|
357
|
+
audience: "https://chat-app.example.test/googlechat",
|
|
358
|
+
dm: { allowFrom: ["users/123"] },
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
accountId: "default",
|
|
363
|
+
context: {
|
|
364
|
+
account: {
|
|
365
|
+
...account,
|
|
366
|
+
config: {
|
|
367
|
+
audienceType: "app-url",
|
|
368
|
+
audience: "https://chat-app.example.test/googlechat",
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
request: {
|
|
373
|
+
id: "approval-1",
|
|
374
|
+
request: { command: "echo hi" },
|
|
375
|
+
createdAtMs: Date.now(),
|
|
376
|
+
expiresAtMs: view.expiresAtMs,
|
|
377
|
+
},
|
|
378
|
+
approvalKind: "exec",
|
|
379
|
+
nowMs: Date.now(),
|
|
380
|
+
view,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
expect(JSON.stringify(pendingPayload.cardsV2)).toContain("actagent.approval");
|
|
384
|
+
expect(JSON.stringify(pendingPayload.cardsV2)).not.toContain(
|
|
385
|
+
"https://chat-app.example.test/googlechat",
|
|
386
|
+
);
|
|
387
|
+
});
|
|
388
|
+
});
|