@mercury-fw/channel-google-chat 0.6.0 → 0.7.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 +8 -0
- package/README.md +2 -0
- package/dist/google-chat-app-client.d.ts +7 -1
- package/google-chat-app-client.ts +11 -6
- package/google-chat-provider.ts +30 -3
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @mercury-fw/channel-google-chat
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
- 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.
|
|
9
|
+
- `@mercury-fw/channel-google-chat` needs `@mercury-fw/channel-types` 0.46.0 or later.
|
|
10
|
+
|
|
3
11
|
## 0.6.0
|
|
4
12
|
|
|
5
13
|
### 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
|
}>;
|
|
@@ -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,12 @@ 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,
|
|
37
39
|
NO_REPLY,
|
|
40
|
+
type LoginRequest,
|
|
38
41
|
type PendingConfirmation,
|
|
39
42
|
type ToolOutcome,
|
|
40
43
|
type Provider,
|
|
@@ -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.",
|
|
@@ -338,8 +348,12 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
338
348
|
});
|
|
339
349
|
let activeSubscription: PubSubSubscription | undefined;
|
|
340
350
|
|
|
341
|
-
/**
|
|
342
|
-
|
|
351
|
+
/**
|
|
352
|
+
* Per-turn output sink — same responsibilities as the retired `ChatStreamer`,
|
|
353
|
+
* rebuilt against the new client. `viewer` is the person the turn is for;
|
|
354
|
+
* outside a direct message, what only they may see is sent to them alone.
|
|
355
|
+
*/
|
|
356
|
+
function createSink(space: string, viewer: { user: string; isDirectMessage: boolean }): TurnSink {
|
|
343
357
|
let chain: Promise<void> = Promise.resolve();
|
|
344
358
|
|
|
345
359
|
function enqueue(fn: () => Promise<void>): void {
|
|
@@ -376,6 +390,9 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
376
390
|
// the same turn (e.g. after a tool call) always gets its own new card,
|
|
377
391
|
// never reuses this one.
|
|
378
392
|
let receivingCardName: string | undefined;
|
|
393
|
+
|
|
394
|
+
// A tool asking again in the same turn hands back the same pending login.
|
|
395
|
+
const shownLogins = new Set<string>();
|
|
379
396
|
enqueue(async () => {
|
|
380
397
|
log(`[chat:${space}] [out] receiving card`);
|
|
381
398
|
const sent = await sendCardFn(space, buildReceivingCard(), clientDeps);
|
|
@@ -470,6 +487,16 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
470
487
|
sentMessageNames.add(sent.name);
|
|
471
488
|
});
|
|
472
489
|
}
|
|
490
|
+
const login = detectLoginRequired(step);
|
|
491
|
+
if (login && !shownLogins.has(login.url)) {
|
|
492
|
+
shownLogins.add(login.url);
|
|
493
|
+
enqueue(async () => {
|
|
494
|
+
log(`[chat:${space}] [out] login card: ${login.service}`);
|
|
495
|
+
const options = viewer.isDirectMessage ? undefined : { privateViewer: viewer.user };
|
|
496
|
+
const sent = await sendCardFn(space, buildLoginCard(login), clientDeps, options);
|
|
497
|
+
sentMessageNames.add(sent.name);
|
|
498
|
+
});
|
|
499
|
+
}
|
|
473
500
|
},
|
|
474
501
|
finalize: async (finalText: string) => {
|
|
475
502
|
const trimmed = finalText.trim();
|
|
@@ -520,7 +547,7 @@ export function createGoogleChatProvider(deps: GoogleChatProviderDeps): GoogleCh
|
|
|
520
547
|
|
|
521
548
|
busySessions.add(sessionKey);
|
|
522
549
|
try {
|
|
523
|
-
const sink = createSink(event.space);
|
|
550
|
+
const sink = createSink(event.space, { user: event.sender, isDirectMessage: event.isDirectMessage });
|
|
524
551
|
await handleTurn(
|
|
525
552
|
{
|
|
526
553
|
channel: "google-chat",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mercury-fw/channel-google-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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.46.0 <1.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@mercury-fw/channel-types": "0.
|
|
37
|
+
"@mercury-fw/channel-types": "0.46.0",
|
|
38
38
|
"@mercury-fw/typescript-config": "*",
|
|
39
39
|
"@types/bun": "^1.4.2",
|
|
40
40
|
"typescript": "^6.0.3"
|