crewly 1.20.264 → 1.20.266
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/config/skills/agent/core/docs-comment/SKILL.md +6 -3
- package/config/skills/agent/core/docs-comment/execute.sh +1 -1
- package/config/skills/agent/core/gmail-send/SKILL.md +4 -0
- package/config/skills/agent/core/gmail-send/execute.sh +1 -1
- package/config/skills/agent/core/gmail-send/execute.test.sh +11 -0
- package/config/skills/agent/core/slack-file/SKILL.md +53 -0
- package/config/skills/agent/core/slack-file/execute.sh +72 -0
- package/config/skills/agent/core/slack-file/execute.test.sh +34 -0
- package/dist/backend/backend/src/constants.d.ts +77 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +77 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/connector/connector.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/connector/connector.routes.js +6 -1
- package/dist/backend/backend/src/controllers/connector/connector.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/connector/remote-mcp-proxy.controller.d.ts +69 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp-proxy.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp-proxy.controller.js +203 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp-proxy.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.d.ts +16 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.js +126 -5
- package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google-connect-card.d.ts +12 -1
- package/dist/backend/backend/src/controllers/google/google-connect-card.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/google/google-connect-card.js +28 -10
- package/dist/backend/backend/src/controllers/google/google-connect-card.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts +26 -2
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.js +123 -33
- package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +37 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +15 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +2 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +4 -3
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
- package/dist/backend/backend/src/services/connector/remote-mcp-auth-card.d.ts +43 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth-card.d.ts.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth-card.js +98 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth-card.js.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth.service.d.ts +277 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth.service.js +719 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth.service.js.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.d.ts +29 -5
- package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.js +40 -12
- package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.js.map +1 -1
- package/dist/backend/backend/src/services/connector/remote-mcp-oauth.d.ts +196 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-oauth.d.ts.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-oauth.js +351 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-oauth.js.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.d.ts +3 -1
- package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.js +8 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.js.map +1 -1
- package/dist/backend/backend/src/services/core/credential-files.d.ts.map +1 -1
- package/dist/backend/backend/src/services/core/credential-files.js +2 -0
- package/dist/backend/backend/src/services/core/credential-files.js.map +1 -1
- package/dist/backend/backend/src/services/google/docs-comments.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/docs-comments.service.js +4 -1
- package/dist/backend/backend/src/services/google/docs-comments.service.js.map +1 -1
- package/dist/backend/backend/src/services/google/google-api.client.d.ts +16 -0
- package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/google-api.client.js +44 -0
- package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -1
- package/dist/backend/backend/src/services/google/google-reauth-notifier.service.d.ts +159 -0
- package/dist/backend/backend/src/services/google/google-reauth-notifier.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/google-reauth-notifier.service.js +283 -0
- package/dist/backend/backend/src/services/google/google-reauth-notifier.service.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-reauth.wiring.d.ts +71 -0
- package/dist/backend/backend/src/services/google/google-reauth.wiring.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/google-reauth.wiring.js +132 -0
- package/dist/backend/backend/src/services/google/google-reauth.wiring.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts +28 -10
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js +24 -11
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-file-fetch.service.d.ts +119 -0
- package/dist/backend/backend/src/services/slack/slack-file-fetch.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-file-fetch.service.js +415 -0
- package/dist/backend/backend/src/services/slack/slack-file-fetch.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-file-ref.d.ts +69 -0
- package/dist/backend/backend/src/services/slack/slack-file-ref.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-file-ref.js +177 -0
- package/dist/backend/backend/src/services/slack/slack-file-ref.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +16 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +15 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +84 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +2 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +3 -1
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/types/slack.types.d.ts +11 -0
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +77 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +77 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +2 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.d.ts +29 -5
- package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.js +40 -12
- package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.js.map +1 -1
- package/dist/cli/backend/src/services/core/credential-files.d.ts.map +1 -1
- package/dist/cli/backend/src/services/core/credential-files.js +2 -0
- package/dist/cli/backend/src/services/core/credential-files.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-file-fetch.service.d.ts +119 -0
- package/dist/cli/backend/src/services/slack/slack-file-fetch.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-file-fetch.service.js +415 -0
- package/dist/cli/backend/src/services/slack/slack-file-fetch.service.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-file-ref.d.ts +69 -0
- package/dist/cli/backend/src/services/slack/slack-file-ref.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-file-ref.js +177 -0
- package/dist/cli/backend/src/services/slack/slack-file-ref.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts +15 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +84 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +2 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +3 -1
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/cli/backend/src/types/slack.types.d.ts +11 -0
- package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/slack.types.js.map +1 -1
- package/frontend/dist/assets/{index-83188f0b.js → index-7e5ebf78.js} +111 -111
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -9,6 +9,39 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { GOOGLE_WORKSPACE_CONSTANTS } from '../../constants.js';
|
|
11
11
|
import { GoogleWorkspaceError } from './google-workspace-token.service.js';
|
|
12
|
+
/**
|
|
13
|
+
* Whether a Google 403 means "this token lacks a scope" rather than "this
|
|
14
|
+
* user may not touch this resource".
|
|
15
|
+
*
|
|
16
|
+
* Google says it two ways, often both at once: the legacy
|
|
17
|
+
* `errors[].reason: insufficientPermissions` and the newer
|
|
18
|
+
* `details[].reason: ACCESS_TOKEN_SCOPE_INSUFFICIENT` (an ErrorInfo), with
|
|
19
|
+
* the message "Request had insufficient authentication scopes."
|
|
20
|
+
* `insufficientFilePermissions` (Drive: no access to this file) is a
|
|
21
|
+
* different thing and deliberately does not match.
|
|
22
|
+
*
|
|
23
|
+
* @param status - HTTP status
|
|
24
|
+
* @param text - Raw response body
|
|
25
|
+
* @returns True for a scope refusal
|
|
26
|
+
*/
|
|
27
|
+
export function isInsufficientScopeError(status, text) {
|
|
28
|
+
if (status !== 403)
|
|
29
|
+
return false;
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(text);
|
|
32
|
+
const err = typeof parsed.error === 'object' ? parsed.error : undefined;
|
|
33
|
+
if (!err)
|
|
34
|
+
return false;
|
|
35
|
+
if (err.errors?.some((e) => e.reason === 'insufficientPermissions'))
|
|
36
|
+
return true;
|
|
37
|
+
if (err.details?.some((d) => d.reason === 'ACCESS_TOKEN_SCOPE_INSUFFICIENT'))
|
|
38
|
+
return true;
|
|
39
|
+
return /insufficient authentication scopes/i.test(err.message ?? '');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return /insufficient authentication scopes/i.test(text);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
12
45
|
/**
|
|
13
46
|
* Extract the human message from a Google error body, tolerating both the
|
|
14
47
|
* object and the plain-string shapes Google uses.
|
|
@@ -100,6 +133,17 @@ export async function googleRequest(deps, url, init = {}) {
|
|
|
100
133
|
deps.tokens.clearCache(deps.account);
|
|
101
134
|
throw new GoogleWorkspaceError(401, CODES.GOOGLE_ERROR, `Google rejected the access token: ${message}`);
|
|
102
135
|
}
|
|
136
|
+
if (isInsufficientScopeError(res.status, text)) {
|
|
137
|
+
// The grant is narrower than this call (2026-10-08: Gmail drafts need
|
|
138
|
+
// gmail.compose). Only the owner can widen it; the cached token must
|
|
139
|
+
// go so the call after the reconnect carries the new scope.
|
|
140
|
+
deps.tokens.clearCache(deps.account);
|
|
141
|
+
throw new GoogleWorkspaceError(403, CODES.REAUTH_REQUIRED, message, {
|
|
142
|
+
reason: 'insufficient_scope',
|
|
143
|
+
...(deps.product ? { product: deps.product } : {}),
|
|
144
|
+
...(deps.account ? { account: deps.account } : {}),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
103
147
|
const passthrough = res.status === 403 || res.status === 404 || res.status === 429;
|
|
104
148
|
throw new GoogleWorkspaceError(passthrough ? res.status : 502, CODES.GOOGLE_ERROR, message);
|
|
105
149
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-api.client.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/google-api.client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,0BAA0B,EAAsB,MAAM,oBAAoB,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"google-api.client.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/google-api.client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google reconnect cards — the harness asks the owner, not the agent.
|
|
3
|
+
*
|
|
4
|
+
* 2026-10-08: the Personal Assistant tried to save a reply as a Gmail draft,
|
|
5
|
+
* Google answered 403 "insufficient authentication scopes" (no grant carried
|
|
6
|
+
* `gmail.compose`), and the agent told the owner to "re-authorize Gmail in
|
|
7
|
+
* Crewly Connections" — a page the owner cannot reach from a phone. The
|
|
8
|
+
* same dead end follows an expired or revoked grant.
|
|
9
|
+
*
|
|
10
|
+
* So when a Google call fails because the grant is too narrow or no longer
|
|
11
|
+
* honoured, this posts the existing one-tap connect card (single-use ticket,
|
|
12
|
+
* `google-connect-card.ts`) to the owner in Slack — where the agent is
|
|
13
|
+
* working if that is known, else the owner's DM with that agent — at most
|
|
14
|
+
* once per product and Google account per
|
|
15
|
+
* {@link GOOGLE_WORKSPACE_CONSTANTS.REAUTH.CARD_THROTTLE_MS}. It then watches
|
|
16
|
+
* Cloud until the reconnect lands and tells the waiting agents to retry.
|
|
17
|
+
*
|
|
18
|
+
* Everything that touches Slack, Cloud or an agent is injected, so the
|
|
19
|
+
* machine the failure happened on is the one that posts — any machine with
|
|
20
|
+
* Slack and a Cloud session works the same.
|
|
21
|
+
*
|
|
22
|
+
* @module services/google/google-reauth-notifier.service
|
|
23
|
+
*/
|
|
24
|
+
import { type GoogleProduct } from '../../constants.js';
|
|
25
|
+
import type { GoogleWorkspaceStatus } from './google-workspace-token.service.js';
|
|
26
|
+
/** Why the owner has to reconnect. */
|
|
27
|
+
export type ReauthKind = 'missing_scope' | 'expired';
|
|
28
|
+
/** A Google failure that only the owner can fix. */
|
|
29
|
+
export interface ReauthTrigger {
|
|
30
|
+
product: GoogleProduct;
|
|
31
|
+
kind: ReauthKind;
|
|
32
|
+
/** Google account the call acted as; undefined = the default connection */
|
|
33
|
+
account?: string;
|
|
34
|
+
/** The agent whose call failed; it is told when to retry */
|
|
35
|
+
agentSession?: string;
|
|
36
|
+
}
|
|
37
|
+
/** Where a card goes. */
|
|
38
|
+
export interface ReauthPlace {
|
|
39
|
+
slackChannelId: string;
|
|
40
|
+
threadTs?: string;
|
|
41
|
+
/** Post as this bot (the agent's own); the shared bot when absent */
|
|
42
|
+
botToken?: string;
|
|
43
|
+
}
|
|
44
|
+
/** What {@link GoogleReauthNotifier.notify} did. */
|
|
45
|
+
export type ReauthNotifyResult = {
|
|
46
|
+
status: 'posted';
|
|
47
|
+
expiresAt: string;
|
|
48
|
+
} | {
|
|
49
|
+
status: 'already_sent';
|
|
50
|
+
sentAt: string;
|
|
51
|
+
nextCardAfter: string;
|
|
52
|
+
} | {
|
|
53
|
+
status: 'unavailable';
|
|
54
|
+
why: string;
|
|
55
|
+
};
|
|
56
|
+
/** Collaborators. */
|
|
57
|
+
export interface ReauthNotifierDeps {
|
|
58
|
+
/** The owner's Slack user id */
|
|
59
|
+
ownerUserId: () => string | null;
|
|
60
|
+
/** The thread the agent is working in, else the owner's DM with it */
|
|
61
|
+
placeFor: (agentSession: string | undefined, ownerUserId: string) => Promise<ReauthPlace | null>;
|
|
62
|
+
/** A single-use connect link from Cloud */
|
|
63
|
+
connectUrl: (args: {
|
|
64
|
+
products: GoogleProduct[];
|
|
65
|
+
email?: string;
|
|
66
|
+
slackUserId: string;
|
|
67
|
+
slackChannelId: string;
|
|
68
|
+
slackThreadTs?: string;
|
|
69
|
+
}) => Promise<{
|
|
70
|
+
url: string;
|
|
71
|
+
expiresAt: string;
|
|
72
|
+
}>;
|
|
73
|
+
/** Post so only the owner sees it; true when Slack accepted */
|
|
74
|
+
postCard: (place: ReauthPlace, ownerUserId: string, text: string, blocks: unknown[]) => Promise<boolean>;
|
|
75
|
+
/** Cloud's view of the connections now (fresh, not cached) */
|
|
76
|
+
status: () => Promise<GoogleWorkspaceStatus>;
|
|
77
|
+
/** Drop cached Google tokens so the retry carries the new grant */
|
|
78
|
+
clearTokenCache: (account?: string) => void;
|
|
79
|
+
/** Tell an agent something (wakes or queues it) */
|
|
80
|
+
tellAgent: (session: string, text: string) => Promise<boolean>;
|
|
81
|
+
now?: () => number;
|
|
82
|
+
/** Timer seam for tests */
|
|
83
|
+
setTimer?: (fn: () => void, ms: number) => {
|
|
84
|
+
cancel: () => void;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The card's first line. Owner-facing, so it says what broke and that it is
|
|
89
|
+
* quick — not which scope is missing.
|
|
90
|
+
*
|
|
91
|
+
* @param trigger - The failure
|
|
92
|
+
* @returns Card message
|
|
93
|
+
*/
|
|
94
|
+
export declare function reauthCardMessage(trigger: Pick<ReauthTrigger, 'product' | 'kind'>): string;
|
|
95
|
+
/**
|
|
96
|
+
* What the agent is told once the owner has reconnected.
|
|
97
|
+
*
|
|
98
|
+
* @param trigger - The failure
|
|
99
|
+
* @returns Notice text
|
|
100
|
+
*/
|
|
101
|
+
export declare function reauthRetryNotice(trigger: Pick<ReauthTrigger, 'product' | 'kind'>): string;
|
|
102
|
+
/**
|
|
103
|
+
* Whether a status from Cloud shows the failure fixed.
|
|
104
|
+
*
|
|
105
|
+
* - `missing_scope`: the account is connected for the product and now
|
|
106
|
+
* carries the scope the call lacked (or, for a product with no known
|
|
107
|
+
* extra, reports nothing missing).
|
|
108
|
+
* - `expired`: the account is connected for the product again (Cloud
|
|
109
|
+
* deleted the dead grant, so being there at all is new).
|
|
110
|
+
*
|
|
111
|
+
* @param status - Cloud's connections
|
|
112
|
+
* @param trigger - The failure
|
|
113
|
+
* @returns True when the agent can retry
|
|
114
|
+
*/
|
|
115
|
+
export declare function isReconnected(status: GoogleWorkspaceStatus, trigger: ReauthTrigger): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Posts reconnect cards and tells agents when to retry.
|
|
118
|
+
*/
|
|
119
|
+
export declare class GoogleReauthNotifier {
|
|
120
|
+
private readonly deps;
|
|
121
|
+
private static instance;
|
|
122
|
+
private readonly pending;
|
|
123
|
+
/** Posts on the wire, so two failures at once post one card */
|
|
124
|
+
private readonly posting;
|
|
125
|
+
private readonly now;
|
|
126
|
+
private readonly setTimer;
|
|
127
|
+
/**
|
|
128
|
+
* @param deps - Collaborators
|
|
129
|
+
*/
|
|
130
|
+
constructor(deps: ReauthNotifierDeps);
|
|
131
|
+
/** The wired instance, or null when Slack is not set up on this machine. */
|
|
132
|
+
static getInstance(): GoogleReauthNotifier | null;
|
|
133
|
+
/**
|
|
134
|
+
* Install (or, with null, remove) the process-wide instance.
|
|
135
|
+
*
|
|
136
|
+
* @param next - Notifier
|
|
137
|
+
*/
|
|
138
|
+
static setInstance(next: GoogleReauthNotifier | null): void;
|
|
139
|
+
/**
|
|
140
|
+
* Ask the owner to reconnect, unless a card for this product and account
|
|
141
|
+
* went out within the throttle window — then only remember that this
|
|
142
|
+
* agent is waiting too.
|
|
143
|
+
*
|
|
144
|
+
* @param trigger - The failure
|
|
145
|
+
* @returns What happened, for the agent's hint
|
|
146
|
+
*/
|
|
147
|
+
notify(trigger: ReauthTrigger): Promise<ReauthNotifyResult>;
|
|
148
|
+
/** Stop every watch (shutdown, tests). */
|
|
149
|
+
stop(): void;
|
|
150
|
+
private post;
|
|
151
|
+
/** Check again after the poll interval, until the watch window closes. */
|
|
152
|
+
private schedule;
|
|
153
|
+
/**
|
|
154
|
+
* One look at Cloud. On success the waiting agents are told to retry and
|
|
155
|
+
* the throttle is lifted, so a later, different failure gets its own card.
|
|
156
|
+
*/
|
|
157
|
+
private check;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=google-reauth-notifier.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-reauth-notifier.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/google-reauth-notifier.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google reconnect cards — the harness asks the owner, not the agent.
|
|
3
|
+
*
|
|
4
|
+
* 2026-10-08: the Personal Assistant tried to save a reply as a Gmail draft,
|
|
5
|
+
* Google answered 403 "insufficient authentication scopes" (no grant carried
|
|
6
|
+
* `gmail.compose`), and the agent told the owner to "re-authorize Gmail in
|
|
7
|
+
* Crewly Connections" — a page the owner cannot reach from a phone. The
|
|
8
|
+
* same dead end follows an expired or revoked grant.
|
|
9
|
+
*
|
|
10
|
+
* So when a Google call fails because the grant is too narrow or no longer
|
|
11
|
+
* honoured, this posts the existing one-tap connect card (single-use ticket,
|
|
12
|
+
* `google-connect-card.ts`) to the owner in Slack — where the agent is
|
|
13
|
+
* working if that is known, else the owner's DM with that agent — at most
|
|
14
|
+
* once per product and Google account per
|
|
15
|
+
* {@link GOOGLE_WORKSPACE_CONSTANTS.REAUTH.CARD_THROTTLE_MS}. It then watches
|
|
16
|
+
* Cloud until the reconnect lands and tells the waiting agents to retry.
|
|
17
|
+
*
|
|
18
|
+
* Everything that touches Slack, Cloud or an agent is injected, so the
|
|
19
|
+
* machine the failure happened on is the one that posts — any machine with
|
|
20
|
+
* Slack and a Cloud session works the same.
|
|
21
|
+
*
|
|
22
|
+
* @module services/google/google-reauth-notifier.service
|
|
23
|
+
*/
|
|
24
|
+
import { GOOGLE_WORKSPACE_CONSTANTS } from '../../constants.js';
|
|
25
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
26
|
+
import { buildConnectCard, PRODUCT_LABELS } from '../../controllers/google/google-connect-card.js';
|
|
27
|
+
const logger = LoggerService.getInstance().createComponentLogger('GoogleReauthNotifier');
|
|
28
|
+
/**
|
|
29
|
+
* The scope a `missing_scope` failure is about, per product — what a
|
|
30
|
+
* reconnect must have added for the retry to work.
|
|
31
|
+
*/
|
|
32
|
+
const EXTRA_SCOPE = {
|
|
33
|
+
gmail: GOOGLE_WORKSPACE_CONSTANTS.GMAIL_COMPOSE_SCOPE,
|
|
34
|
+
drive: GOOGLE_WORKSPACE_CONSTANTS.DRIVE_FULL_SCOPE,
|
|
35
|
+
};
|
|
36
|
+
/** What the missing scope lets the agent do, for the card. */
|
|
37
|
+
const EXTRA_PURPOSE = {
|
|
38
|
+
gmail: 'save drafts',
|
|
39
|
+
drive: 'comment on documents',
|
|
40
|
+
};
|
|
41
|
+
/** What the agent retries once the scope is there. */
|
|
42
|
+
const EXTRA_RETRY = {
|
|
43
|
+
gmail: 'your draft',
|
|
44
|
+
drive: 'your comment',
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The card's first line. Owner-facing, so it says what broke and that it is
|
|
48
|
+
* quick — not which scope is missing.
|
|
49
|
+
*
|
|
50
|
+
* @param trigger - The failure
|
|
51
|
+
* @returns Card message
|
|
52
|
+
*/
|
|
53
|
+
export function reauthCardMessage(trigger) {
|
|
54
|
+
const label = PRODUCT_LABELS[trigger.product] ?? trigger.product;
|
|
55
|
+
if (trigger.kind === 'expired')
|
|
56
|
+
return `${label} access expired — tap to reconnect.`;
|
|
57
|
+
const purpose = EXTRA_PURPOSE[trigger.product];
|
|
58
|
+
return `${label} needs re-authorization${purpose ? ` to ${purpose}` : ''} (missing permission). Tap to reconnect — takes 30 seconds on your phone.`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* What the agent is told once the owner has reconnected.
|
|
62
|
+
*
|
|
63
|
+
* @param trigger - The failure
|
|
64
|
+
* @returns Notice text
|
|
65
|
+
*/
|
|
66
|
+
export function reauthRetryNotice(trigger) {
|
|
67
|
+
const label = PRODUCT_LABELS[trigger.product] ?? trigger.product;
|
|
68
|
+
const what = trigger.kind === 'missing_scope' ? EXTRA_RETRY[trigger.product] : undefined;
|
|
69
|
+
return `[GOOGLE] ${label} reconnected — retry ${what ?? 'what failed'}.`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether a status from Cloud shows the failure fixed.
|
|
73
|
+
*
|
|
74
|
+
* - `missing_scope`: the account is connected for the product and now
|
|
75
|
+
* carries the scope the call lacked (or, for a product with no known
|
|
76
|
+
* extra, reports nothing missing).
|
|
77
|
+
* - `expired`: the account is connected for the product again (Cloud
|
|
78
|
+
* deleted the dead grant, so being there at all is new).
|
|
79
|
+
*
|
|
80
|
+
* @param status - Cloud's connections
|
|
81
|
+
* @param trigger - The failure
|
|
82
|
+
* @returns True when the agent can retry
|
|
83
|
+
*/
|
|
84
|
+
export function isReconnected(status, trigger) {
|
|
85
|
+
if (!status.connected)
|
|
86
|
+
return false;
|
|
87
|
+
const account = trigger.account?.toLowerCase();
|
|
88
|
+
const connection = account
|
|
89
|
+
? status.connections.find((c) => c.email.toLowerCase() === account)
|
|
90
|
+
: status.connections.find((c) => c.isDefault) ?? status.connections[0];
|
|
91
|
+
if (!connection || !connection.products.includes(trigger.product))
|
|
92
|
+
return false;
|
|
93
|
+
if (trigger.kind === 'expired')
|
|
94
|
+
return true;
|
|
95
|
+
const needed = EXTRA_SCOPE[trigger.product];
|
|
96
|
+
if (needed)
|
|
97
|
+
return connection.scopes.includes(needed);
|
|
98
|
+
return (connection.missingScopes ?? []).length === 0;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Throttle key: one card per product and Google account.
|
|
102
|
+
*
|
|
103
|
+
* @param trigger - The failure
|
|
104
|
+
* @returns Key
|
|
105
|
+
*/
|
|
106
|
+
function keyOf(trigger) {
|
|
107
|
+
return `${trigger.product}\u0000${(trigger.account ?? '').toLowerCase()}`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Posts reconnect cards and tells agents when to retry.
|
|
111
|
+
*/
|
|
112
|
+
export class GoogleReauthNotifier {
|
|
113
|
+
deps;
|
|
114
|
+
static instance = null;
|
|
115
|
+
pending = new Map();
|
|
116
|
+
/** Posts on the wire, so two failures at once post one card */
|
|
117
|
+
posting = new Map();
|
|
118
|
+
now;
|
|
119
|
+
setTimer;
|
|
120
|
+
/**
|
|
121
|
+
* @param deps - Collaborators
|
|
122
|
+
*/
|
|
123
|
+
constructor(deps) {
|
|
124
|
+
this.deps = deps;
|
|
125
|
+
this.now = deps.now ?? (() => Date.now());
|
|
126
|
+
this.setTimer =
|
|
127
|
+
deps.setTimer ??
|
|
128
|
+
((fn, ms) => {
|
|
129
|
+
const t = setTimeout(fn, ms);
|
|
130
|
+
t.unref?.();
|
|
131
|
+
return { cancel: () => clearTimeout(t) };
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/** The wired instance, or null when Slack is not set up on this machine. */
|
|
135
|
+
static getInstance() {
|
|
136
|
+
return GoogleReauthNotifier.instance;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Install (or, with null, remove) the process-wide instance.
|
|
140
|
+
*
|
|
141
|
+
* @param next - Notifier
|
|
142
|
+
*/
|
|
143
|
+
static setInstance(next) {
|
|
144
|
+
GoogleReauthNotifier.instance?.stop();
|
|
145
|
+
GoogleReauthNotifier.instance = next;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Ask the owner to reconnect, unless a card for this product and account
|
|
149
|
+
* went out within the throttle window — then only remember that this
|
|
150
|
+
* agent is waiting too.
|
|
151
|
+
*
|
|
152
|
+
* @param trigger - The failure
|
|
153
|
+
* @returns What happened, for the agent's hint
|
|
154
|
+
*/
|
|
155
|
+
async notify(trigger) {
|
|
156
|
+
const key = keyOf(trigger);
|
|
157
|
+
const nowMs = this.now();
|
|
158
|
+
const existing = this.pending.get(key);
|
|
159
|
+
if (existing && nowMs - existing.sentAtMs < GOOGLE_WORKSPACE_CONSTANTS.REAUTH.CARD_THROTTLE_MS) {
|
|
160
|
+
if (trigger.agentSession)
|
|
161
|
+
existing.waiters.add(trigger.agentSession);
|
|
162
|
+
// The watch on the card may have run out; someone is waiting again, so
|
|
163
|
+
// look for a reconnect made some other way (the Connections page).
|
|
164
|
+
if (!existing.timer) {
|
|
165
|
+
existing.watchUntilMs = nowMs + GOOGLE_WORKSPACE_CONSTANTS.REAUTH.POLL_GRACE_MS;
|
|
166
|
+
this.schedule(key, existing);
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
status: 'already_sent',
|
|
170
|
+
sentAt: new Date(existing.sentAtMs).toISOString(),
|
|
171
|
+
nextCardAfter: new Date(existing.sentAtMs + GOOGLE_WORKSPACE_CONSTANTS.REAUTH.CARD_THROTTLE_MS).toISOString(),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const inFlight = this.posting.get(key);
|
|
175
|
+
if (inFlight) {
|
|
176
|
+
const result = await inFlight;
|
|
177
|
+
if (trigger.agentSession)
|
|
178
|
+
this.pending.get(key)?.waiters.add(trigger.agentSession);
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
const task = this.post(key, trigger).finally(() => this.posting.delete(key));
|
|
182
|
+
this.posting.set(key, task);
|
|
183
|
+
return task;
|
|
184
|
+
}
|
|
185
|
+
/** Stop every watch (shutdown, tests). */
|
|
186
|
+
stop() {
|
|
187
|
+
for (const p of this.pending.values())
|
|
188
|
+
p.timer?.cancel();
|
|
189
|
+
this.pending.clear();
|
|
190
|
+
}
|
|
191
|
+
async post(key, trigger) {
|
|
192
|
+
const owner = this.deps.ownerUserId();
|
|
193
|
+
if (!owner)
|
|
194
|
+
return { status: 'unavailable', why: 'the owner is not known on Slack' };
|
|
195
|
+
const place = await this.deps.placeFor(trigger.agentSession, owner).catch(() => null);
|
|
196
|
+
if (!place)
|
|
197
|
+
return { status: 'unavailable', why: 'no Slack conversation with the owner from this machine' };
|
|
198
|
+
let link;
|
|
199
|
+
try {
|
|
200
|
+
link = await this.deps.connectUrl({
|
|
201
|
+
products: [trigger.product],
|
|
202
|
+
...(trigger.account ? { email: trigger.account } : {}),
|
|
203
|
+
slackUserId: owner,
|
|
204
|
+
slackChannelId: place.slackChannelId,
|
|
205
|
+
...(place.threadTs ? { slackThreadTs: place.threadTs } : {}),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
210
|
+
logger.warn('Could not mint a Google reconnect link', { product: trigger.product, error: why });
|
|
211
|
+
return { status: 'unavailable', why: `Crewly Cloud did not issue a link (${why})` };
|
|
212
|
+
}
|
|
213
|
+
const card = buildConnectCard(trigger.product, link.url, link.expiresAt, {
|
|
214
|
+
message: reauthCardMessage(trigger),
|
|
215
|
+
...(trigger.account ? { account: trigger.account } : {}),
|
|
216
|
+
});
|
|
217
|
+
const posted = await this.deps.postCard(place, owner, card.text, card.blocks).catch(() => false);
|
|
218
|
+
if (!posted)
|
|
219
|
+
return { status: 'unavailable', why: 'Slack refused the card' };
|
|
220
|
+
const sentAtMs = this.now();
|
|
221
|
+
const expiresMs = Date.parse(link.expiresAt);
|
|
222
|
+
const pending = {
|
|
223
|
+
trigger,
|
|
224
|
+
sentAtMs,
|
|
225
|
+
watchUntilMs: (Number.isFinite(expiresMs) ? expiresMs : sentAtMs) + GOOGLE_WORKSPACE_CONSTANTS.REAUTH.POLL_GRACE_MS,
|
|
226
|
+
waiters: new Set(trigger.agentSession ? [trigger.agentSession] : []),
|
|
227
|
+
};
|
|
228
|
+
this.pending.get(key)?.timer?.cancel();
|
|
229
|
+
this.pending.set(key, pending);
|
|
230
|
+
this.schedule(key, pending);
|
|
231
|
+
logger.info('Posted a Google reconnect card', {
|
|
232
|
+
product: trigger.product,
|
|
233
|
+
kind: trigger.kind,
|
|
234
|
+
account: trigger.account,
|
|
235
|
+
slackChannelId: place.slackChannelId,
|
|
236
|
+
threaded: !!place.threadTs,
|
|
237
|
+
agentSession: trigger.agentSession,
|
|
238
|
+
});
|
|
239
|
+
return { status: 'posted', expiresAt: link.expiresAt };
|
|
240
|
+
}
|
|
241
|
+
/** Check again after the poll interval, until the watch window closes. */
|
|
242
|
+
schedule(key, pending) {
|
|
243
|
+
pending.timer = this.setTimer(() => {
|
|
244
|
+
void this.check(key, pending);
|
|
245
|
+
}, GOOGLE_WORKSPACE_CONSTANTS.REAUTH.POLL_INTERVAL_MS);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* One look at Cloud. On success the waiting agents are told to retry and
|
|
249
|
+
* the throttle is lifted, so a later, different failure gets its own card.
|
|
250
|
+
*/
|
|
251
|
+
async check(key, pending) {
|
|
252
|
+
if (this.pending.get(key) !== pending)
|
|
253
|
+
return;
|
|
254
|
+
let fixed = false;
|
|
255
|
+
try {
|
|
256
|
+
fixed = isReconnected(await this.deps.status(), pending.trigger);
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
logger.debug('Could not read Google status while waiting for a reconnect', {
|
|
260
|
+
error: err instanceof Error ? err.message : String(err),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (!fixed) {
|
|
264
|
+
if (this.now() < pending.watchUntilMs)
|
|
265
|
+
this.schedule(key, pending);
|
|
266
|
+
else
|
|
267
|
+
pending.timer = undefined;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
this.pending.delete(key);
|
|
271
|
+
this.deps.clearTokenCache(pending.trigger.account);
|
|
272
|
+
const notice = reauthRetryNotice(pending.trigger);
|
|
273
|
+
for (const session of pending.waiters) {
|
|
274
|
+
await this.deps.tellAgent(session, notice).catch(() => false);
|
|
275
|
+
}
|
|
276
|
+
logger.info('Google reconnected; told the waiting agents to retry', {
|
|
277
|
+
product: pending.trigger.product,
|
|
278
|
+
account: pending.trigger.account,
|
|
279
|
+
agents: [...pending.waiters],
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=google-reauth-notifier.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-reauth-notifier.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/google-reauth-notifier.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google reconnect cards — wiring with the real Slack, Cloud and agents.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from the notifier so its rules (throttle, wording, when a
|
|
5
|
+
* reconnect counts) are testable without Slack. Everything here runs on the
|
|
6
|
+
* machine where the Google call failed: its Slack transport posts the card,
|
|
7
|
+
* its Cloud session mints the ticket, its agents are told to retry. That is
|
|
8
|
+
* the same on every machine, so an agent on a second machine (the Personal
|
|
9
|
+
* Assistant on iriss-air, 2026-10-08) gets the same card as one on the main
|
|
10
|
+
* Mac.
|
|
11
|
+
*
|
|
12
|
+
* @module services/google/google-reauth.wiring
|
|
13
|
+
*/
|
|
14
|
+
import type { SlackBlock } from '../../types/slack.types.js';
|
|
15
|
+
import { GoogleReauthNotifier, type ReauthNotifierDeps } from './google-reauth-notifier.service.js';
|
|
16
|
+
/** The Slack calls the wiring makes (a slice of SlackService). */
|
|
17
|
+
export interface ReauthSlackApi {
|
|
18
|
+
getOwnerUserId?: (() => string | null) | null;
|
|
19
|
+
openDirectMessage: (userId: string, botToken?: string) => Promise<string>;
|
|
20
|
+
sendMessage: (message: {
|
|
21
|
+
channelId: string;
|
|
22
|
+
text: string;
|
|
23
|
+
blocks?: SlackBlock[];
|
|
24
|
+
threadTs?: string;
|
|
25
|
+
botToken?: string;
|
|
26
|
+
skipChatV2Mirror?: boolean;
|
|
27
|
+
}) => Promise<string>;
|
|
28
|
+
sendEphemeral: (channelId: string, userId: string, text: string, blocks?: unknown[], botToken?: string, threadTs?: string) => Promise<boolean>;
|
|
29
|
+
}
|
|
30
|
+
/** What the wiring needs from the rest of the process. */
|
|
31
|
+
export interface ReauthWiringInput {
|
|
32
|
+
/** Deliver text to an agent; wakes it when needed */
|
|
33
|
+
sendToAgent: (session: string, text: string) => Promise<boolean>;
|
|
34
|
+
/** Seams for tests; default to the real singletons */
|
|
35
|
+
slack?: () => ReauthSlackApi;
|
|
36
|
+
/** The Slack place of the agent's current work */
|
|
37
|
+
workDestination?: (session: string) => Promise<{
|
|
38
|
+
slackChannelId: string;
|
|
39
|
+
threadTs?: string;
|
|
40
|
+
} | null>;
|
|
41
|
+
/** The owner↔agent Slack DM, when there is one */
|
|
42
|
+
agentDm?: (session: string) => {
|
|
43
|
+
slackChannelId: string;
|
|
44
|
+
} | null;
|
|
45
|
+
/** The agent's own installed bot token */
|
|
46
|
+
agentBotToken?: (session: string) => string | undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A Slack DM channel id. DMs are private to the owner and the bot, so a
|
|
50
|
+
* card there can be an ordinary message — which, unlike an ephemeral one,
|
|
51
|
+
* reaches the owner's phone as a notification and survives a reload.
|
|
52
|
+
*
|
|
53
|
+
* @param channelId - Slack channel id
|
|
54
|
+
* @returns True for `D…`
|
|
55
|
+
*/
|
|
56
|
+
export declare function isDirectMessage(channelId: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Build the notifier's collaborators.
|
|
59
|
+
*
|
|
60
|
+
* @param input - Process hooks and test seams
|
|
61
|
+
* @returns Notifier deps
|
|
62
|
+
*/
|
|
63
|
+
export declare function createReauthNotifierDeps(input: ReauthWiringInput): ReauthNotifierDeps;
|
|
64
|
+
/**
|
|
65
|
+
* Install the process-wide notifier. Called once Slack is up.
|
|
66
|
+
*
|
|
67
|
+
* @param input - Process hooks
|
|
68
|
+
* @returns The notifier
|
|
69
|
+
*/
|
|
70
|
+
export declare function startGoogleReauthNotifier(input: ReauthWiringInput): GoogleReauthNotifier;
|
|
71
|
+
//# sourceMappingURL=google-reauth.wiring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-reauth.wiring.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/google-reauth.wiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAM7D,OAAO,EAAE,oBAAoB,EAAE,KAAK,kBAAkB,EAAoB,MAAM,qCAAqC,CAAC;AAItH,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,cAAc,CAAC,EAAE,CAAC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9C,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1E,WAAW,EAAE,CAAC,OAAO,EAAE;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACtB,aAAa,EAAE,CACb,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,EAClB,QAAQ,CAAC,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,KACd,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB;AAED,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,cAAc,CAAC;IAC7B,kDAAkD;IAClD,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACrG,kDAAkD;IAClD,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjE,0CAA0C;IAC1C,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CACzD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,iBAAiB,GAAG,kBAAkB,CAkFrF;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,iBAAiB,GAAG,oBAAoB,CAKxF"}
|