@mercury-fw/channel-google-chat 0.6.0 → 0.8.0
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/CHANGELOG.md +22 -0
- package/README.md +2 -0
- package/dist/google-chat-app-client.d.ts +7 -1
- package/dist/google-chat-provider.d.ts +3 -1
- package/google-chat-app-client.ts +11 -6
- package/google-chat-provider.ts +117 -39
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @mercury-fw/channel-google-chat
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b2f8cfb: - A confirmation can offer several choices (for example "only this time" / "always for this host"): a plugin tool stages them with the new `stageChoices` of `SessionToolContext`, and gets one token per choice. The person takes one; the others die with it, and they all expire together.
|
|
8
|
+
- The paper-trail note of a group of choices is one, under its first token, and says which choice was taken.
|
|
9
|
+
- `detectPendingConfirmation` returns the `choices` a staging offers; a plain staging is one choice labelled `Conferma`.
|
|
10
|
+
- A confirmed action can hand its result back to the model (`resume` on a choice): instead of showing the person the raw result, the channel resumes the turn and the model answers on it, the result reaching it as the tool result of the call that staged it, never stored in the history.
|
|
11
|
+
- A resumed turn answers the person who confirmed even in a shared space, and the line standing for the confirmation isn't archived as something they said. A result nobody resumes within five minutes (the life of a confirmation token) is forgotten, and a restart loses it.
|
|
12
|
+
- `InboundTurn.resumeAfterConfirm` and `RESUME_AFTER_CONFIRM` (channel contract) carry the resumed turn; `ConfirmOutcome` has a `resume` status.
|
|
13
|
+
- Terminal: every choice is printed on its own line with its token; a confirmation that resumes runs the turn again.
|
|
14
|
+
- Google Chat: the confirmation card has one button per choice; a typed token or a clicked choice that resumes runs the turn again, in the session's queue. The channel now needs channel-types 0.51.0 or later.
|
|
15
|
+
- HTTP: the `pending` event carries `choices`; a token sent to `POST /turn` that resumes streams the model's answer on the same request; `POST /confirm` answers `resume: true`, and `POST /turn { resume: true, conversationId }` runs that turn. The channel now needs channel-types 0.51.0 or later.
|
|
16
|
+
|
|
17
|
+
## 0.7.0
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- 4184ea6: - `loginLinkText` words a login link the same way on every channel, and the contract states that a login link is shown only to the person it's for.
|
|
22
|
+
- Google Chat shows the login to a service a plugin acts on as the person (Jira, Bitbucket) as a button that opens it, visible only to them: in a shared space it's a private message to whoever asked.
|
|
23
|
+
- `@mercury-fw/channel-google-chat` needs `@mercury-fw/channel-types` 0.46.0 or later.
|
|
24
|
+
|
|
3
25
|
## 0.6.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -26,6 +26,8 @@ channels: [googleChatChannel],
|
|
|
26
26
|
|
|
27
27
|
Each message reaches the core as `google-chat:users/<id>`, the sender's Chat user, with their email when Google vouches for it (a person's, never a bot's), which a user directory can join on: the ZITADEL directory of `@mercury-fw/plugin-zitadel` does.
|
|
28
28
|
|
|
29
|
+
When a plugin that acts as the person (Jira, Bitbucket) needs them to log in, the channel sends a card with a button that opens the service's login, visible only to that person: in a direct message the space is already theirs, in a shared space the card is a private message to whoever asked, since anyone else opening it would log their own account in as them. The service sends the browser back to the HTTP channel's `/login/callback`, so the app needs [`@mercury-fw/channel-http`](https://www.npmjs.com/package/@mercury-fw/channel-http) with `HTTP_SURFACE_PUBLIC_URL` too; without it Mercury answers that nobody can log in on this instance.
|
|
30
|
+
|
|
29
31
|
## Table of contents
|
|
30
32
|
|
|
31
33
|
- [Setting up the Chat app](#setting-up-the-chat-app)
|
|
@@ -55,10 +55,16 @@ export type ChatCard = {
|
|
|
55
55
|
};
|
|
56
56
|
sections: ChatCardSection[];
|
|
57
57
|
};
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Posts `card` as a Cards v2 message to `space`. Returns the created message's
|
|
60
|
+
* `name`, same shape as `sendMessage`. With `privateViewer` (`users/<id>`) only
|
|
61
|
+
* that user and the app see it, in a space anyone else is in too.
|
|
62
|
+
*/
|
|
59
63
|
export declare function sendCard(space: string, card: ChatCard, deps: {
|
|
60
64
|
tokenSource: TokenSource;
|
|
61
65
|
fetchFn?: FetchFn;
|
|
66
|
+
}, options?: {
|
|
67
|
+
privateViewer?: string;
|
|
62
68
|
}): Promise<{
|
|
63
69
|
name: string;
|
|
64
70
|
}>;
|
|
@@ -58,10 +58,12 @@ export type ParsedCardClickEvent = {
|
|
|
58
58
|
sender: string;
|
|
59
59
|
senderEmail: string | undefined;
|
|
60
60
|
parameters: Record<string, string>;
|
|
61
|
+
/** As on a message (see `ParsedMessageEvent.isDirectMessage`): for the turn a click resumes. */
|
|
62
|
+
isDirectMessage: boolean;
|
|
61
63
|
};
|
|
62
64
|
/** Parses one decoded Pub/Sub event, or `null` if it isn't a kind this provider acts on. */
|
|
63
65
|
export declare function parseChatEvent(raw: unknown): ParsedMessageEvent | ParsedCardClickEvent | null;
|
|
64
|
-
export type CardClickHandler = (params: Record<string, string>, space: string, sender: string, senderEmail?: string) => Promise<void>;
|
|
66
|
+
export type CardClickHandler = (params: Record<string, string>, space: string, sender: string, senderEmail?: string, isDirectMessage?: boolean) => Promise<void>;
|
|
65
67
|
export type GoogleChatProviderDeps = {
|
|
66
68
|
credentials: ServiceAccountCredentials;
|
|
67
69
|
subscription: string;
|
|
@@ -171,17 +171,22 @@ export type ChatCardSection = {
|
|
|
171
171
|
};
|
|
172
172
|
export type ChatCard = { header?: { title: string }; sections: ChatCardSection[] };
|
|
173
173
|
|
|
174
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* Posts `card` as a Cards v2 message to `space`. Returns the created message's
|
|
176
|
+
* `name`, same shape as `sendMessage`. With `privateViewer` (`users/<id>`) only
|
|
177
|
+
* that user and the app see it, in a space anyone else is in too.
|
|
178
|
+
*/
|
|
175
179
|
export async function sendCard(
|
|
176
180
|
space: string,
|
|
177
181
|
card: ChatCard,
|
|
178
182
|
deps: { tokenSource: TokenSource; fetchFn?: FetchFn },
|
|
183
|
+
options: { privateViewer?: string } = {},
|
|
179
184
|
): Promise<{ name: string }> {
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
)) as { name: string };
|
|
185
|
+
const body = {
|
|
186
|
+
cardsV2: [{ cardId: "card", card }],
|
|
187
|
+
...(options.privateViewer === undefined ? {} : { privateMessageViewer: { name: options.privateViewer } }),
|
|
188
|
+
};
|
|
189
|
+
const data = (await callChatApi(`${space}/messages`, { method: "POST", body }, deps)) as { name: string };
|
|
185
190
|
return { name: data.name };
|
|
186
191
|
}
|
|
187
192
|
|
package/google-chat-provider.ts
CHANGED
|
@@ -32,9 +32,13 @@ import {
|
|
|
32
32
|
import { openSubscription, type PubSubSubscription, type StreamMessage } from "./google-chat-pubsub-stream.ts";
|
|
33
33
|
import { splitForSendLimit } from "./google-chat-message-buffer.ts";
|
|
34
34
|
import {
|
|
35
|
+
detectLoginRequired,
|
|
35
36
|
detectPendingConfirmation,
|
|
37
|
+
loginLinkText,
|
|
36
38
|
PENDING_CONFIRMATION_NOTE,
|
|
39
|
+
RESUME_AFTER_CONFIRM,
|
|
37
40
|
NO_REPLY,
|
|
41
|
+
type LoginRequest,
|
|
38
42
|
type PendingConfirmation,
|
|
39
43
|
type ToolOutcome,
|
|
40
44
|
type Provider,
|
|
@@ -45,7 +49,8 @@ import {
|
|
|
45
49
|
|
|
46
50
|
/**
|
|
47
51
|
* Builds the confirmation card sent when a step stages an irreversible
|
|
48
|
-
* command (see `pending-confirmation.ts`)
|
|
52
|
+
* command (see `pending-confirmation.ts`), one button per choice it offers
|
|
53
|
+
* (a single `Conferma` for a plain staging). Each button's parameters carry
|
|
49
54
|
* the token, so a click routes straight into the same `tryConfirm` path
|
|
50
55
|
* a bare token typed on the terminal uses (see `onCardClick`'s `confirm`
|
|
51
56
|
* case, below) — the user never has to see or type the token themselves.
|
|
@@ -59,12 +64,10 @@ function buildConfirmCard(pending: PendingConfirmation): ChatCard {
|
|
|
59
64
|
{ textParagraph: { text: `\`${pending.summary}\`` } },
|
|
60
65
|
{
|
|
61
66
|
buttonList: {
|
|
62
|
-
buttons:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
},
|
|
67
|
-
],
|
|
67
|
+
buttons: pending.choices.map((choice) => ({
|
|
68
|
+
text: choice.label,
|
|
69
|
+
onClick: { action: { function: "confirm", parameters: [{ key: "token", value: choice.token }] } },
|
|
70
|
+
})),
|
|
68
71
|
},
|
|
69
72
|
},
|
|
70
73
|
],
|
|
@@ -73,6 +76,13 @@ function buildConfirmCard(pending: PendingConfirmation): ChatCard {
|
|
|
73
76
|
};
|
|
74
77
|
}
|
|
75
78
|
|
|
79
|
+
/** The card that shows a person the link to log in to a service a plugin acts on as them: one button that opens it. */
|
|
80
|
+
function buildLoginCard(login: LoginRequest): ChatCard {
|
|
81
|
+
return {
|
|
82
|
+
sections: [{ widgets: [{ buttonList: { buttons: [{ text: loginLinkText(login), onClick: { openLink: { url: login.url } } }] } }] }],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
76
86
|
const TOOL_STATUS_LINES: Record<"loading" | ToolOutcome, string> = {
|
|
77
87
|
loading: "In corso…",
|
|
78
88
|
success: "Fatto.",
|
|
@@ -186,7 +196,7 @@ type RawChatEvent = {
|
|
|
186
196
|
sender?: RawChatSender;
|
|
187
197
|
};
|
|
188
198
|
action?: { actionMethodName?: string; parameters?: Array<{ key?: string; value?: string }> };
|
|
189
|
-
space?: { name?: string };
|
|
199
|
+
space?: { name?: string; type?: string };
|
|
190
200
|
user?: RawChatSender;
|
|
191
201
|
};
|
|
192
202
|
|
|
@@ -215,6 +225,8 @@ export type ParsedCardClickEvent = {
|
|
|
215
225
|
sender: string;
|
|
216
226
|
senderEmail: string | undefined;
|
|
217
227
|
parameters: Record<string, string>;
|
|
228
|
+
/** As on a message (see `ParsedMessageEvent.isDirectMessage`): for the turn a click resumes. */
|
|
229
|
+
isDirectMessage: boolean;
|
|
218
230
|
};
|
|
219
231
|
|
|
220
232
|
/** The email Google vouches for on `sender`: a person's only (`HUMAN`), never a bot's or an unknown kind's. */
|
|
@@ -250,12 +262,25 @@ export function parseChatEvent(raw: unknown): ParsedMessageEvent | ParsedCardCli
|
|
|
250
262
|
parameters[p.key] = p.value;
|
|
251
263
|
}
|
|
252
264
|
}
|
|
253
|
-
return {
|
|
265
|
+
return {
|
|
266
|
+
kind: "card-click",
|
|
267
|
+
space: event.space.name,
|
|
268
|
+
sender: event.user.name,
|
|
269
|
+
senderEmail: emailOf(event.user),
|
|
270
|
+
parameters,
|
|
271
|
+
isDirectMessage: event.space.type === "DM",
|
|
272
|
+
};
|
|
254
273
|
}
|
|
255
274
|
return null;
|
|
256
275
|
}
|
|
257
276
|
|
|
258
|
-
export type CardClickHandler = (
|
|
277
|
+
export type CardClickHandler = (
|
|
278
|
+
params: Record<string, string>,
|
|
279
|
+
space: string,
|
|
280
|
+
sender: string,
|
|
281
|
+
senderEmail?: string,
|
|
282
|
+
isDirectMessage?: boolean,
|
|
283
|
+
) => Promise<void>;
|
|
259
284
|
|
|
260
285
|
export type GoogleChatProviderDeps = {
|
|
261
286
|
credentials: ServiceAccountCredentials;
|
|
@@ -309,7 +334,50 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
309
334
|
// concurrently. Scoped by sessionKey only (not global), so a slow turn
|
|
310
335
|
// for one user/space never blocks a different one's.
|
|
311
336
|
const busySessions = new Set<string>();
|
|
312
|
-
const
|
|
337
|
+
const queuedTurns = new Map<string, Array<() => Promise<void>>>();
|
|
338
|
+
// The turn handler `start` was given, for a turn a card click resumes.
|
|
339
|
+
let activeHandleTurn: HandleTurn | undefined;
|
|
340
|
+
|
|
341
|
+
/** Runs `turn` for `sessionKey` now, or after the turns already queued for it. */
|
|
342
|
+
async function runInSession(sessionKey: string, turn: () => Promise<void>): Promise<void> {
|
|
343
|
+
if (busySessions.has(sessionKey)) {
|
|
344
|
+
const queue = queuedTurns.get(sessionKey) ?? [];
|
|
345
|
+
queue.push(turn);
|
|
346
|
+
queuedTurns.set(sessionKey, queue);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
busySessions.add(sessionKey);
|
|
350
|
+
try {
|
|
351
|
+
await turn();
|
|
352
|
+
} finally {
|
|
353
|
+
busySessions.delete(sessionKey);
|
|
354
|
+
const queue = queuedTurns.get(sessionKey);
|
|
355
|
+
const next = queue?.shift();
|
|
356
|
+
if (queue && queue.length === 0) queuedTurns.delete(sessionKey);
|
|
357
|
+
if (next) {
|
|
358
|
+
runInSession(sessionKey, next).catch((err) => log(`[chat] queued turn failed: ${String(err)}`));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Resumes the session's turn after a confirmation whose result went back to the model (`RESUME_AFTER_CONFIRM`). */
|
|
364
|
+
function resumeTurn(handleTurn: HandleTurn, space: string, principal: Principal, isDirectMessage: boolean): Promise<void> {
|
|
365
|
+
const sessionKey = deriveSessionKey(space, principal.id);
|
|
366
|
+
return runInSession(sessionKey, () =>
|
|
367
|
+
handleTurn(
|
|
368
|
+
{
|
|
369
|
+
channel: "google-chat",
|
|
370
|
+
multiUser: !isDirectMessage,
|
|
371
|
+
text: "",
|
|
372
|
+
sessionKey,
|
|
373
|
+
principal,
|
|
374
|
+
logPrefix: `[chat:${space}:${principal.id}] `,
|
|
375
|
+
resumeAfterConfirm: true,
|
|
376
|
+
},
|
|
377
|
+
createSink(space, { user: principal.id, isDirectMessage }),
|
|
378
|
+
),
|
|
379
|
+
);
|
|
380
|
+
}
|
|
313
381
|
|
|
314
382
|
/**
|
|
315
383
|
* Default `onCardClick`: the confirm button's token routes through the exact
|
|
@@ -319,17 +387,22 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
319
387
|
*/
|
|
320
388
|
const onCardClick: CardClickHandler =
|
|
321
389
|
deps.onCardClick ??
|
|
322
|
-
(async (params, space, sender, senderEmail) => {
|
|
390
|
+
(async (params, space, sender, senderEmail, isDirectMessage = false) => {
|
|
323
391
|
const token = params.token;
|
|
324
392
|
if (!token) {
|
|
325
393
|
log(`[chat] card click with no token parameter`);
|
|
326
394
|
return;
|
|
327
395
|
}
|
|
328
|
-
const
|
|
396
|
+
const principal: Principal = {
|
|
329
397
|
id: sender,
|
|
330
398
|
provider: "google-chat",
|
|
331
399
|
...(senderEmail === undefined ? {} : { claims: { email: senderEmail } }),
|
|
332
|
-
}
|
|
400
|
+
};
|
|
401
|
+
const reply = await deps.confirm(token, deriveSessionKey(space, sender), principal);
|
|
402
|
+
if (reply === RESUME_AFTER_CONFIRM && activeHandleTurn) {
|
|
403
|
+
await resumeTurn(activeHandleTurn, space, principal, isDirectMessage);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
333
406
|
if (reply !== null) {
|
|
334
407
|
log(`[chat:${space}] [out] ${reply}`);
|
|
335
408
|
const sent = await sendMessageFn(space, reply, clientDeps);
|
|
@@ -338,8 +411,12 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
338
411
|
});
|
|
339
412
|
let activeSubscription: PubSubSubscription | undefined;
|
|
340
413
|
|
|
341
|
-
/**
|
|
342
|
-
|
|
414
|
+
/**
|
|
415
|
+
* Per-turn output sink — same responsibilities as the retired `ChatStreamer`,
|
|
416
|
+
* rebuilt against the new client. `viewer` is the person the turn is for;
|
|
417
|
+
* outside a direct message, what only they may see is sent to them alone.
|
|
418
|
+
*/
|
|
419
|
+
function createSink(space: string, viewer: { user: string; isDirectMessage: boolean }): TurnSink {
|
|
343
420
|
let chain: Promise<void> = Promise.resolve();
|
|
344
421
|
|
|
345
422
|
function enqueue(fn: () => Promise<void>): void {
|
|
@@ -376,6 +453,9 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
376
453
|
// the same turn (e.g. after a tool call) always gets its own new card,
|
|
377
454
|
// never reuses this one.
|
|
378
455
|
let receivingCardName: string | undefined;
|
|
456
|
+
|
|
457
|
+
// A tool asking again in the same turn hands back the same pending login.
|
|
458
|
+
const shownLogins = new Set<string>();
|
|
379
459
|
enqueue(async () => {
|
|
380
460
|
log(`[chat:${space}] [out] receiving card`);
|
|
381
461
|
const sent = await sendCardFn(space, buildReceivingCard(), clientDeps);
|
|
@@ -470,6 +550,16 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
470
550
|
sentMessageNames.add(sent.name);
|
|
471
551
|
});
|
|
472
552
|
}
|
|
553
|
+
const login = detectLoginRequired(step);
|
|
554
|
+
if (login && !shownLogins.has(login.url)) {
|
|
555
|
+
shownLogins.add(login.url);
|
|
556
|
+
enqueue(async () => {
|
|
557
|
+
log(`[chat:${space}] [out] login card: ${login.service}`);
|
|
558
|
+
const options = viewer.isDirectMessage ? undefined : { privateViewer: viewer.user };
|
|
559
|
+
const sent = await sendCardFn(space, buildLoginCard(login), clientDeps, options);
|
|
560
|
+
sentMessageNames.add(sent.name);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
473
563
|
},
|
|
474
564
|
finalize: async (finalText: string) => {
|
|
475
565
|
const trimmed = finalText.trim();
|
|
@@ -501,6 +591,10 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
501
591
|
};
|
|
502
592
|
|
|
503
593
|
const confirmReply = await deps.confirm(event.text, sessionKey, principal);
|
|
594
|
+
if (confirmReply === RESUME_AFTER_CONFIRM) {
|
|
595
|
+
await resumeTurn(handleTurn, event.space, principal, event.isDirectMessage);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
504
598
|
if (confirmReply !== null) {
|
|
505
599
|
log(`[chat:${event.space}] [out] ${confirmReply}`);
|
|
506
600
|
const sent = await sendMessageFn(event.space, confirmReply, clientDeps);
|
|
@@ -511,17 +605,8 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
511
605
|
// Only the actual model turn needs serializing — tryConfirm above
|
|
512
606
|
// never touches SessionHistory, so it's always safe to run right
|
|
513
607
|
// away regardless of whether this session is mid-turn.
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
queue.push(event);
|
|
517
|
-
queuedEvents.set(sessionKey, queue);
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
busySessions.add(sessionKey);
|
|
522
|
-
try {
|
|
523
|
-
const sink = createSink(event.space);
|
|
524
|
-
await handleTurn(
|
|
608
|
+
await runInSession(sessionKey, () =>
|
|
609
|
+
handleTurn(
|
|
525
610
|
{
|
|
526
611
|
channel: "google-chat",
|
|
527
612
|
multiUser: !event.isDirectMessage,
|
|
@@ -530,17 +615,9 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
530
615
|
principal,
|
|
531
616
|
logPrefix: `[chat:${event.space}:${event.sender}] `,
|
|
532
617
|
},
|
|
533
|
-
|
|
534
|
-
)
|
|
535
|
-
|
|
536
|
-
busySessions.delete(sessionKey);
|
|
537
|
-
const queue = queuedEvents.get(sessionKey);
|
|
538
|
-
const next = queue?.shift();
|
|
539
|
-
if (queue && queue.length === 0) queuedEvents.delete(sessionKey);
|
|
540
|
-
if (next) {
|
|
541
|
-
processMessageEvent(next, handleTurn).catch((err) => log(`[chat] queued event handling failed: ${String(err)}`));
|
|
542
|
-
}
|
|
543
|
-
}
|
|
618
|
+
createSink(event.space, { user: event.sender, isDirectMessage: event.isDirectMessage }),
|
|
619
|
+
),
|
|
620
|
+
);
|
|
544
621
|
}
|
|
545
622
|
|
|
546
623
|
/**
|
|
@@ -571,7 +648,7 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
571
648
|
if (parsed.kind === "message") {
|
|
572
649
|
await processMessageEvent(parsed, handleTurn);
|
|
573
650
|
} else {
|
|
574
|
-
await onCardClick(parsed.parameters, parsed.space, parsed.sender, parsed.senderEmail);
|
|
651
|
+
await onCardClick(parsed.parameters, parsed.space, parsed.sender, parsed.senderEmail, parsed.isDirectMessage);
|
|
575
652
|
}
|
|
576
653
|
} catch (err) {
|
|
577
654
|
log(`[chat] event handling failed: ${String(err)}`);
|
|
@@ -580,6 +657,7 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
580
657
|
|
|
581
658
|
return {
|
|
582
659
|
async start(handleTurn: HandleTurn): Promise<void> {
|
|
660
|
+
activeHandleTurn = handleTurn;
|
|
583
661
|
const sub = subscriptionFn(deps.credentials, deps.subscription);
|
|
584
662
|
activeSubscription = sub;
|
|
585
663
|
sub.on("message", (message) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mercury-fw/channel-google-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"@google-cloud/pubsub": "^5.3.1"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@mercury-fw/channel-types": ">=0.
|
|
34
|
+
"@mercury-fw/channel-types": ">=0.51.0 <1.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@mercury-fw/channel-types": "0.
|
|
37
|
+
"@mercury-fw/channel-types": "0.51.0",
|
|
38
38
|
"@mercury-fw/typescript-config": "*",
|
|
39
39
|
"@types/bun": "^1.4.2",
|
|
40
40
|
"typescript": "^6.0.3"
|