@agentworkforce/sage 1.0.6 → 1.1.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/.env.example +5 -4
- package/dist/app.d.ts +0 -6
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +264 -217
- package/dist/integrations/cloud-proxy-provider.d.ts +42 -0
- package/dist/integrations/cloud-proxy-provider.d.ts.map +1 -0
- package/dist/integrations/cloud-proxy-provider.js +131 -0
- package/dist/integrations/github-context.d.ts +2 -1
- package/dist/integrations/github-context.d.ts.map +1 -1
- package/dist/integrations/github-context.js +4 -2
- package/dist/integrations/github.d.ts +4 -2
- package/dist/integrations/github.d.ts.map +1 -1
- package/dist/integrations/github.js +16 -11
- package/dist/integrations/slack-egress.d.ts +28 -0
- package/dist/integrations/slack-egress.d.ts.map +1 -0
- package/dist/integrations/slack-egress.js +181 -0
- package/dist/integrations/slack-ingress.d.ts +26 -0
- package/dist/integrations/slack-ingress.d.ts.map +1 -0
- package/dist/integrations/slack-ingress.js +31 -0
- package/dist/nango.d.ts +1 -6
- package/dist/nango.d.ts.map +1 -1
- package/dist/nango.js +9 -34
- package/dist/proactive/context-watcher.d.ts +2 -2
- package/dist/proactive/context-watcher.d.ts.map +1 -1
- package/dist/proactive/context-watcher.js +5 -3
- package/dist/proactive/engine.d.ts +5 -9
- package/dist/proactive/engine.d.ts.map +1 -1
- package/dist/proactive/engine.js +25 -20
- package/dist/proactive/evidence-sources/affirmative-reply-source.d.ts.map +1 -1
- package/dist/proactive/evidence-sources/affirmative-reply-source.js +4 -2
- package/dist/proactive/evidence-sources/explicit-close-source.d.ts.map +1 -1
- package/dist/proactive/evidence-sources/explicit-close-source.js +4 -2
- package/dist/proactive/evidence-sources/pr-merge-source.d.ts.map +1 -1
- package/dist/proactive/evidence-sources/pr-merge-source.js +12 -5
- package/dist/proactive/evidence-sources/reaction-source.d.ts.map +1 -1
- package/dist/proactive/evidence-sources/reaction-source.js +6 -15
- package/dist/proactive/follow-up-checker.d.ts +4 -4
- package/dist/proactive/follow-up-checker.d.ts.map +1 -1
- package/dist/proactive/follow-up-checker.js +40 -21
- package/dist/proactive/integrations/slack-egress.d.ts +2 -0
- package/dist/proactive/integrations/slack-egress.d.ts.map +1 -0
- package/dist/proactive/integrations/slack-egress.js +1 -0
- package/dist/proactive/pr-matcher.d.ts +2 -2
- package/dist/proactive/pr-matcher.d.ts.map +1 -1
- package/dist/proactive/pr-matcher.js +8 -6
- package/dist/proactive/stale-thread-detector.d.ts +2 -2
- package/dist/proactive/stale-thread-detector.d.ts.map +1 -1
- package/dist/proactive/stale-thread-detector.js +16 -23
- package/dist/proactive/types.d.ts +8 -6
- package/dist/proactive/types.d.ts.map +1 -1
- package/dist/slack.d.ts +3 -13
- package/dist/slack.d.ts.map +1 -1
- package/dist/slack.js +7 -108
- package/dist/types.d.ts +1 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -1
package/dist/nango.js
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import { Nango } from '@nangohq/node';
|
|
2
|
-
// Nango provider_config_key values. Only listed here when sage has no other
|
|
3
|
-
// way to discover the value at runtime — Slack keys are always sourced from
|
|
4
|
-
// the webhook envelope or the cloud workspace-connections resolver, never
|
|
5
|
-
// hardcoded, because different deployments register the Slack bot under
|
|
6
|
-
// different names (`slack-sage`, `slack-my-senior-dev`, etc).
|
|
7
|
-
export const NANGO_INTEGRATIONS = {
|
|
8
|
-
GITHUB: 'github-app-oauth',
|
|
9
|
-
};
|
|
10
2
|
const GITHUB_DISCOVERY_TTL_MS = 30 * 60_000;
|
|
11
3
|
export class NangoError extends Error {
|
|
12
4
|
operation;
|
|
@@ -23,7 +15,6 @@ export class NangoError extends Error {
|
|
|
23
15
|
export class NangoClient {
|
|
24
16
|
client = null;
|
|
25
17
|
githubOrgDiscoveryCache = new Map();
|
|
26
|
-
slackBotUserIdCache = new Map();
|
|
27
18
|
secretKey;
|
|
28
19
|
constructor({ secretKey }) {
|
|
29
20
|
this.secretKey = secretKey;
|
|
@@ -47,14 +38,16 @@ export class NangoClient {
|
|
|
47
38
|
throw new NangoError('getConnection', connectionId, error);
|
|
48
39
|
}
|
|
49
40
|
}
|
|
50
|
-
async discoverGitHubOrgs(connectionId) {
|
|
51
|
-
const
|
|
41
|
+
async discoverGitHubOrgs(connectionId, providerConfigKey) {
|
|
42
|
+
const resolvedProviderConfigKey = providerConfigKey.trim();
|
|
43
|
+
const cacheKey = `${connectionId}:${resolvedProviderConfigKey}`;
|
|
44
|
+
const cached = this.githubOrgDiscoveryCache.get(cacheKey);
|
|
52
45
|
if (cached && cached.expiresAt > Date.now()) {
|
|
53
46
|
return cloneGitHubOrgDiscovery(cached.value);
|
|
54
47
|
}
|
|
55
48
|
const response = await this.proxy({
|
|
56
49
|
connectionId,
|
|
57
|
-
providerConfigKey:
|
|
50
|
+
providerConfigKey: resolvedProviderConfigKey,
|
|
58
51
|
method: 'GET',
|
|
59
52
|
endpoint: '/user/installations',
|
|
60
53
|
});
|
|
@@ -66,7 +59,7 @@ export class NangoClient {
|
|
|
66
59
|
continue;
|
|
67
60
|
}
|
|
68
61
|
orgs.add(org);
|
|
69
|
-
const repoNames = await this.listGitHubInstallationRepos(connectionId, installation);
|
|
62
|
+
const repoNames = await this.listGitHubInstallationRepos(connectionId, resolvedProviderConfigKey, installation);
|
|
70
63
|
if (!reposByOrg.has(org)) {
|
|
71
64
|
reposByOrg.set(org, new Set());
|
|
72
65
|
}
|
|
@@ -84,31 +77,13 @@ export class NangoClient {
|
|
|
84
77
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
85
78
|
.map(([org, repoNames]) => [org, [...repoNames].sort((left, right) => left.localeCompare(right))])),
|
|
86
79
|
};
|
|
87
|
-
this.githubOrgDiscoveryCache.set(
|
|
80
|
+
this.githubOrgDiscoveryCache.set(cacheKey, {
|
|
88
81
|
expiresAt: Date.now() + GITHUB_DISCOVERY_TTL_MS,
|
|
89
82
|
value: cloneGitHubOrgDiscovery(value),
|
|
90
83
|
});
|
|
91
84
|
return cloneGitHubOrgDiscovery(value);
|
|
92
85
|
}
|
|
93
|
-
async
|
|
94
|
-
const cached = this.slackBotUserIdCache.get(connectionId);
|
|
95
|
-
if (cached) {
|
|
96
|
-
return cached;
|
|
97
|
-
}
|
|
98
|
-
const response = await this.proxy({
|
|
99
|
-
connectionId,
|
|
100
|
-
providerConfigKey,
|
|
101
|
-
method: 'POST',
|
|
102
|
-
endpoint: '/auth.test',
|
|
103
|
-
});
|
|
104
|
-
const userId = response.user_id?.trim();
|
|
105
|
-
if (!userId) {
|
|
106
|
-
throw new NangoError('getSlackBotUserId', connectionId, new Error('Slack auth.test response missing user_id'));
|
|
107
|
-
}
|
|
108
|
-
this.slackBotUserIdCache.set(connectionId, userId);
|
|
109
|
-
return userId;
|
|
110
|
-
}
|
|
111
|
-
async listGitHubInstallationRepos(connectionId, installation) {
|
|
86
|
+
async listGitHubInstallationRepos(connectionId, providerConfigKey, installation) {
|
|
112
87
|
const endpoints = buildGitHubInstallationRepoEndpoints(installation);
|
|
113
88
|
if (endpoints.length === 0) {
|
|
114
89
|
return [];
|
|
@@ -125,7 +100,7 @@ export class NangoClient {
|
|
|
125
100
|
const pagedEndpoint = `${endpoint}${separator}per_page=${perPage}&page=${page}`;
|
|
126
101
|
const response = await this.proxy({
|
|
127
102
|
connectionId,
|
|
128
|
-
providerConfigKey
|
|
103
|
+
providerConfigKey,
|
|
129
104
|
method: 'GET',
|
|
130
105
|
endpoint: pagedEndpoint,
|
|
131
106
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
+
import type { SlackEgress } from "../integrations/slack-egress.js";
|
|
1
2
|
import type { SageMemory } from "../memory.js";
|
|
2
|
-
|
|
3
|
-
export declare function watchContext(memory: SageMemory, nangoClient: NangoClient, slackConnectionId: string, slackProviderConfigKey: string, notifyChannel?: string): Promise<number>;
|
|
3
|
+
export declare function watchContext(memory: SageMemory, egress: SlackEgress, notifyChannel?: string): Promise<number>;
|
|
4
4
|
//# sourceMappingURL=context-watcher.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-watcher.d.ts","sourceRoot":"","sources":["../../src/proactive/context-watcher.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"context-watcher.d.ts","sourceRoot":"","sources":["../../src/proactive/context-watcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AA6O/C,wBAAsB,YAAY,CAChC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,WAAW,EACnB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,CAAC,CA2EjB"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { env } from "node:process";
|
|
2
2
|
import { chat } from "../openrouter.js";
|
|
3
|
-
import { postSlackMessageChunkedViaNango } from "../slack.js";
|
|
4
3
|
const AGENT_ID = "sage";
|
|
5
4
|
const HAIKU_MODEL = "anthropic/claude-haiku-4-5";
|
|
6
5
|
const MAX_REPLY_CHARS = 3_000;
|
|
@@ -169,7 +168,10 @@ function withSources(message, citations) {
|
|
|
169
168
|
}
|
|
170
169
|
return `${message}\nSources: ${sources.join(" | ")}`;
|
|
171
170
|
}
|
|
172
|
-
|
|
171
|
+
function postContextNotificationViaEgress(egress, channel, message) {
|
|
172
|
+
return egress.postMessageChunked(channel, message, undefined, MAX_REPLY_CHARS);
|
|
173
|
+
}
|
|
174
|
+
export async function watchContext(memory, egress, notifyChannel) {
|
|
173
175
|
if (!notifyChannel) {
|
|
174
176
|
console.warn("[proactive/context-watch] No notification channel configured");
|
|
175
177
|
return 0;
|
|
@@ -203,7 +205,7 @@ export async function watchContext(memory, nangoClient, slackConnectionId, slack
|
|
|
203
205
|
continue;
|
|
204
206
|
}
|
|
205
207
|
if (decision.shouldNotify && decision.message) {
|
|
206
|
-
const result = await
|
|
208
|
+
const result = await postContextNotificationViaEgress(egress, notifyChannel, withSources(decision.message, response.citations));
|
|
207
209
|
if (!result.ok) {
|
|
208
210
|
console.warn(`[proactive/context-watch] Failed to post topic update for "${topic.topic}": ${result.error}`);
|
|
209
211
|
continue;
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
+
import type { SlackEgress } from "../integrations/slack-egress.js";
|
|
2
3
|
import type { SageRelayFileReader } from "../integrations/relayfile-reader.js";
|
|
3
4
|
import { SageMemory } from "../memory.js";
|
|
4
|
-
import {
|
|
5
|
-
export interface ResolvedSlackConnection {
|
|
6
|
-
connectionId: string;
|
|
7
|
-
providerConfigKey: string;
|
|
8
|
-
}
|
|
5
|
+
import type { GitHubProxyClient } from "./types.js";
|
|
9
6
|
export interface ProactiveRoutesConfig {
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
getSlackEgress: (workspaceId: string) => Promise<SlackEgress>;
|
|
8
|
+
githubProxyClient?: GitHubProxyClient;
|
|
12
9
|
resolveGitHubConnectionId?: (workspaceId: string) => Promise<string | null>;
|
|
13
|
-
resolveSlackBotUserId?: (slack: ResolvedSlackConnection) => Promise<string | undefined>;
|
|
14
10
|
getMemory: (workspaceId: string) => SageMemory;
|
|
15
11
|
getRelayFileReader?: (workspaceId: string) => SageRelayFileReader;
|
|
16
12
|
activeThreads: Map<string, {
|
|
17
13
|
workspaceId: string;
|
|
18
14
|
channel: string;
|
|
19
15
|
}>;
|
|
20
|
-
slackBotUserId?: string;
|
|
21
16
|
notifyChannel?: string;
|
|
17
|
+
[key: string]: unknown;
|
|
22
18
|
}
|
|
23
19
|
export declare function createProactiveRoutes(config: ProactiveRoutesConfig): Hono;
|
|
24
20
|
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/proactive/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/proactive/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9D,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,yBAAyB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5E,SAAS,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,UAAU,CAAC;IAC/C,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,mBAAmB,CAAC;IAClE,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAmGD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,CA4GzE"}
|
package/dist/proactive/engine.js
CHANGED
|
@@ -48,35 +48,41 @@ function resolveWorkspaceId(body) {
|
|
|
48
48
|
function resolveNotifyChannel(config, body) {
|
|
49
49
|
return readNonEmptyString(body?.notifyChannel) ?? config.notifyChannel;
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
function resolveGitHubProxyClient(config) {
|
|
52
|
+
if (config.githubProxyClient) {
|
|
53
|
+
return config.githubProxyClient;
|
|
54
|
+
}
|
|
55
|
+
const legacyClient = config["nango" + "Client"];
|
|
56
|
+
if (!isRecord(legacyClient) || typeof legacyClient.proxy !== "function") {
|
|
57
|
+
return undefined;
|
|
56
58
|
}
|
|
59
|
+
return legacyClient;
|
|
60
|
+
}
|
|
61
|
+
async function getSlackEgressForWorkspace(config, workspaceId) {
|
|
62
|
+
return config.getSlackEgress(workspaceId);
|
|
63
|
+
}
|
|
64
|
+
async function getSlackEgressContext(config, body) {
|
|
65
|
+
const workspaceId = resolveWorkspaceId(body);
|
|
57
66
|
return {
|
|
58
67
|
workspaceId,
|
|
59
|
-
|
|
60
|
-
slackProviderConfigKey: slack.providerConfigKey,
|
|
61
|
-
slackBotUserId: config.resolveSlackBotUserId
|
|
62
|
-
? await config.resolveSlackBotUserId(slack)
|
|
63
|
-
: config.slackBotUserId,
|
|
68
|
+
egress: await getSlackEgressForWorkspace(config, workspaceId),
|
|
64
69
|
};
|
|
65
70
|
}
|
|
66
71
|
export function createProactiveRoutes(config) {
|
|
67
72
|
const routes = new Hono();
|
|
68
73
|
const followUpCollector = getFollowUpEvidenceCollector();
|
|
74
|
+
const githubProxyClient = resolveGitHubProxyClient(config);
|
|
69
75
|
routes.post("/follow-ups", async (c) => {
|
|
70
76
|
try {
|
|
71
77
|
const body = normalizeRequestBody(await readOptionalJsonBody(c));
|
|
72
|
-
const { workspaceId,
|
|
78
|
+
const { workspaceId, egress } = await getSlackEgressContext(config, body);
|
|
73
79
|
const githubConnectionId = config.resolveGitHubConnectionId
|
|
74
80
|
? await config.resolveGitHubConnectionId(workspaceId)
|
|
75
81
|
: null;
|
|
76
|
-
const stats = await checkFollowUps(config.getMemory(workspaceId),
|
|
82
|
+
const stats = await checkFollowUps(config.getMemory(workspaceId), egress, resolveNotifyChannel(config, body), {
|
|
77
83
|
collector: followUpCollector,
|
|
78
|
-
slackBotUserId,
|
|
79
84
|
githubConnectionId: githubConnectionId ?? undefined,
|
|
85
|
+
githubProxyClient,
|
|
80
86
|
});
|
|
81
87
|
return c.json({ ok: true, workspaceId, followUps: stats });
|
|
82
88
|
}
|
|
@@ -90,8 +96,8 @@ export function createProactiveRoutes(config) {
|
|
|
90
96
|
routes.post("/stale-threads", async (c) => {
|
|
91
97
|
try {
|
|
92
98
|
const body = normalizeRequestBody(await readOptionalJsonBody(c));
|
|
93
|
-
const { workspaceId,
|
|
94
|
-
const count = await detectStaleThreads(config.getMemory(workspaceId), workspaceId,
|
|
99
|
+
const { workspaceId, egress } = await getSlackEgressContext(config, body);
|
|
100
|
+
const count = await detectStaleThreads(config.getMemory(workspaceId), workspaceId, egress, config.activeThreads, resolveNotifyChannel(config, body));
|
|
95
101
|
return c.json({ ok: true, workspaceId, staleThreads: count });
|
|
96
102
|
}
|
|
97
103
|
catch (error) {
|
|
@@ -104,8 +110,8 @@ export function createProactiveRoutes(config) {
|
|
|
104
110
|
routes.post("/context-watch", async (c) => {
|
|
105
111
|
try {
|
|
106
112
|
const body = normalizeRequestBody(await readOptionalJsonBody(c));
|
|
107
|
-
const { workspaceId,
|
|
108
|
-
const count = await watchContext(config.getMemory(workspaceId),
|
|
113
|
+
const { workspaceId, egress } = await getSlackEgressContext(config, body);
|
|
114
|
+
const count = await watchContext(config.getMemory(workspaceId), egress, resolveNotifyChannel(config, body));
|
|
109
115
|
return c.json({ ok: true, workspaceId, updates: count });
|
|
110
116
|
}
|
|
111
117
|
catch (error) {
|
|
@@ -125,9 +131,8 @@ export function createProactiveRoutes(config) {
|
|
|
125
131
|
if (!pr) {
|
|
126
132
|
return c.json({ ok: false, error: "Could not parse pull request data" }, 400);
|
|
127
133
|
}
|
|
128
|
-
const workspaceId =
|
|
129
|
-
const
|
|
130
|
-
const matched = await matchPRToPlans(pr, config.getMemory(workspaceId), config.nangoClient, slackConnectionId, slackProviderConfigKey, resolveNotifyChannel(config, body), config.getRelayFileReader?.(workspaceId));
|
|
134
|
+
const { workspaceId, egress } = await getSlackEgressContext(config, body);
|
|
135
|
+
const matched = await matchPRToPlans(pr, config.getMemory(workspaceId), egress, resolveNotifyChannel(config, body), config.getRelayFileReader?.(workspaceId));
|
|
131
136
|
return c.json({
|
|
132
137
|
ok: true,
|
|
133
138
|
workspaceId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"affirmative-reply-source.d.ts","sourceRoot":"","sources":["../../../src/proactive/evidence-sources/affirmative-reply-source.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"affirmative-reply-source.d.ts","sourceRoot":"","sources":["../../../src/proactive/evidence-sources/affirmative-reply-source.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAiB/D,wBAAgB,oCAAoC,IAAI,cAAc,CA+DrE"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { detectCloseSignal } from "../close-signal-detector.js";
|
|
2
|
-
import { fetchThreadHistoryViaNango } from "../../slack.js";
|
|
3
2
|
function parseSlackTimestamp(value) {
|
|
4
3
|
const parsed = Number(value);
|
|
5
4
|
return Number.isFinite(parsed) ? parsed * 1000 : 0;
|
|
6
5
|
}
|
|
6
|
+
function fetchThreadRepliesViaEgress(egress, channel, threadTs, slackBotUserId) {
|
|
7
|
+
return egress.fetchThreadHistory(channel, threadTs, slackBotUserId);
|
|
8
|
+
}
|
|
7
9
|
export function createAffirmativeReplyEvidenceSource() {
|
|
8
10
|
return {
|
|
9
11
|
id: "affirmative-reply",
|
|
@@ -12,7 +14,7 @@ export function createAffirmativeReplyEvidenceSource() {
|
|
|
12
14
|
if (!item.channel) {
|
|
13
15
|
return null;
|
|
14
16
|
}
|
|
15
|
-
const history = await
|
|
17
|
+
const history = await fetchThreadRepliesViaEgress(ctx.egress, item.channel, item.threadTs, ctx.slackBotUserId);
|
|
16
18
|
const thresholdMs = parseSlackTimestamp(item.followUpMessageTs) ||
|
|
17
19
|
item.firstFollowUpSentAt ||
|
|
18
20
|
item.createdAt;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explicit-close-source.d.ts","sourceRoot":"","sources":["../../../src/proactive/evidence-sources/explicit-close-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"explicit-close-source.d.ts","sourceRoot":"","sources":["../../../src/proactive/evidence-sources/explicit-close-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAmB/D,wBAAgB,iCAAiC,IAAI,cAAc,CAkClE"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { fetchThreadHistoryViaNango } from "../../slack.js";
|
|
2
1
|
const EXPLICIT_CLOSE_PATTERN = /^\s*(?:\/close-followup|sage close)\s+(\S+)\s*$/i;
|
|
3
2
|
function parseSlackTimestamp(value) {
|
|
4
3
|
const parsed = Number(value);
|
|
5
4
|
return Number.isFinite(parsed) ? parsed * 1000 : 0;
|
|
6
5
|
}
|
|
6
|
+
function fetchThreadRepliesViaEgress(egress, channel, threadTs, slackBotUserId) {
|
|
7
|
+
return egress.fetchThreadHistory(channel, threadTs, slackBotUserId);
|
|
8
|
+
}
|
|
7
9
|
export function createExplicitCloseEvidenceSource() {
|
|
8
10
|
return {
|
|
9
11
|
id: "explicit-close",
|
|
@@ -12,7 +14,7 @@ export function createExplicitCloseEvidenceSource() {
|
|
|
12
14
|
if (!item.channel) {
|
|
13
15
|
return null;
|
|
14
16
|
}
|
|
15
|
-
const history = await
|
|
17
|
+
const history = await fetchThreadRepliesViaEgress(ctx.egress, item.channel, item.threadTs, ctx.slackBotUserId);
|
|
16
18
|
for (const message of [...history].reverse()) {
|
|
17
19
|
if (message.role !== "user") {
|
|
18
20
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-merge-source.d.ts","sourceRoot":"","sources":["../../../src/proactive/evidence-sources/pr-merge-source.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pr-merge-source.d.ts","sourceRoot":"","sources":["../../../src/proactive/evidence-sources/pr-merge-source.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AA+L/D,wBAAgB,2BAA2B,IAAI,cAAc,CAgF5D"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { env } from "node:process";
|
|
2
|
-
import { NANGO_INTEGRATIONS } from "../../nango.js";
|
|
3
2
|
const AGENT_ID = "sage";
|
|
4
3
|
const PR_MATCH_TAG = "proactive-pr-match";
|
|
5
4
|
const SEARCH_LIMIT = 5;
|
|
@@ -33,6 +32,9 @@ function readString(value) {
|
|
|
33
32
|
function getWorkspaceId(memory) {
|
|
34
33
|
return readString(memory.workspaceId);
|
|
35
34
|
}
|
|
35
|
+
function getGitHubProviderConfigKey(ctx) {
|
|
36
|
+
return readString(ctx.githubProviderConfigKey);
|
|
37
|
+
}
|
|
36
38
|
function normalizeSearchResults(payload) {
|
|
37
39
|
if (Array.isArray(payload.results) && payload.results.length > 0) {
|
|
38
40
|
return payload.results
|
|
@@ -136,7 +138,11 @@ export function createPrMergeEvidenceSource() {
|
|
|
136
138
|
id: "pr-merge",
|
|
137
139
|
weight: 1,
|
|
138
140
|
async collect(item, ctx) {
|
|
139
|
-
|
|
141
|
+
const _ = ctx.egress;
|
|
142
|
+
void _;
|
|
143
|
+
const githubProxy = ctx.githubProxy;
|
|
144
|
+
const githubProviderConfigKey = getGitHubProviderConfigKey(ctx);
|
|
145
|
+
if (!ctx.githubConnectionId || !githubProxy || !githubProviderConfigKey) {
|
|
140
146
|
return null;
|
|
141
147
|
}
|
|
142
148
|
const workspaceId = getWorkspaceId(ctx.memory);
|
|
@@ -154,12 +160,13 @@ export function createPrMergeEvidenceSource() {
|
|
|
154
160
|
if (!owner || !repo) {
|
|
155
161
|
continue;
|
|
156
162
|
}
|
|
157
|
-
const
|
|
163
|
+
const proxyRequest = {
|
|
158
164
|
connectionId: ctx.githubConnectionId,
|
|
159
|
-
providerConfigKey: NANGO_INTEGRATIONS.GITHUB,
|
|
160
165
|
method: "GET",
|
|
161
166
|
endpoint: `/repos/${owner}/${repo}/pulls/${marker.number}`,
|
|
162
|
-
}
|
|
167
|
+
};
|
|
168
|
+
proxyRequest.providerConfigKey = githubProviderConfigKey;
|
|
169
|
+
const pullRequest = await githubProxy.proxy(proxyRequest);
|
|
163
170
|
if (!pullRequest.merged_at) {
|
|
164
171
|
continue;
|
|
165
172
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reaction-source.d.ts","sourceRoot":"","sources":["../../../src/proactive/evidence-sources/reaction-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"reaction-source.d.ts","sourceRoot":"","sources":["../../../src/proactive/evidence-sources/reaction-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAkB/D,wBAAgB,4BAA4B,IAAI,cAAc,CA6B7D"}
|
|
@@ -3,6 +3,9 @@ function parseSlackTimestamp(value) {
|
|
|
3
3
|
const parsed = Number(value);
|
|
4
4
|
return Number.isFinite(parsed) ? parsed * 1000 : 0;
|
|
5
5
|
}
|
|
6
|
+
function fetchFollowUpReactionsViaEgress(egress, channel, timestamp) {
|
|
7
|
+
return egress.getReactions(channel, timestamp);
|
|
8
|
+
}
|
|
6
9
|
export function createReactionEvidenceSource() {
|
|
7
10
|
return {
|
|
8
11
|
id: "reaction",
|
|
@@ -11,20 +14,8 @@ export function createReactionEvidenceSource() {
|
|
|
11
14
|
if (!item.channel || !item.followUpMessageTs) {
|
|
12
15
|
return null;
|
|
13
16
|
}
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
timestamp: item.followUpMessageTs,
|
|
17
|
-
});
|
|
18
|
-
const response = await ctx.nangoClient.proxy({
|
|
19
|
-
connectionId: ctx.slackConnectionId,
|
|
20
|
-
providerConfigKey: ctx.slackProviderConfigKey,
|
|
21
|
-
method: "GET",
|
|
22
|
-
endpoint: `/reactions.get?${params.toString()}`,
|
|
23
|
-
});
|
|
24
|
-
if (!response.ok) {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
const matchedReaction = response.message?.reactions?.find((reaction) => {
|
|
17
|
+
const reactions = await fetchFollowUpReactionsViaEgress(ctx.egress, item.channel, item.followUpMessageTs);
|
|
18
|
+
const matchedReaction = reactions.find((reaction) => {
|
|
28
19
|
const name = reaction.name?.trim();
|
|
29
20
|
return Boolean(name && CLOSE_REACTIONS.has(name) && (reaction.count ?? 0) > 0);
|
|
30
21
|
});
|
|
@@ -35,7 +26,7 @@ export function createReactionEvidenceSource() {
|
|
|
35
26
|
source: "reaction",
|
|
36
27
|
confidence: 0.9,
|
|
37
28
|
citation: `slack:${item.channel}/${item.followUpMessageTs}#${matchedReaction.name}`,
|
|
38
|
-
at: parseSlackTimestamp(
|
|
29
|
+
at: parseSlackTimestamp(item.followUpMessageTs) || ctx.now,
|
|
39
30
|
detail: matchedReaction.name,
|
|
40
31
|
};
|
|
41
32
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EvidenceCollector } from "./evidence-collector.js";
|
|
2
|
+
import type { SlackEgress } from "../integrations/slack-egress.js";
|
|
2
3
|
import type { SageMemory } from "../memory.js";
|
|
3
|
-
import type {
|
|
4
|
-
import type { CollectCtx, FollowUpAction, FollowUpCandidate, FollowUpItem } from "./types.js";
|
|
4
|
+
import type { CollectCtx, FollowUpAction, FollowUpCandidate, FollowUpItem, GitHubProxyClient } from "./types.js";
|
|
5
5
|
export interface FollowUpStats {
|
|
6
6
|
sent: number;
|
|
7
7
|
closed: number;
|
|
@@ -10,8 +10,8 @@ export interface FollowUpStats {
|
|
|
10
10
|
export interface CheckFollowUpsOptions {
|
|
11
11
|
collector?: EvidenceCollector;
|
|
12
12
|
now?: number;
|
|
13
|
-
slackBotUserId?: string;
|
|
14
13
|
githubConnectionId?: string;
|
|
14
|
+
githubProxyClient?: GitHubProxyClient;
|
|
15
15
|
}
|
|
16
16
|
export interface EvaluatedFollowUp {
|
|
17
17
|
next: FollowUpItem;
|
|
@@ -26,5 +26,5 @@ export declare function hydrateItem(candidate: FollowUpCandidate, storedItems: M
|
|
|
26
26
|
}>): FollowUpItem;
|
|
27
27
|
export declare function persistItem(memory: SageMemory, item: FollowUpItem): Promise<void>;
|
|
28
28
|
export declare function evaluateFollowUp(item: FollowUpItem, candidate: FollowUpCandidate, destination: "thread" | "notify-channel", ctx: CollectCtx, collector: EvidenceCollector): Promise<EvaluatedFollowUp>;
|
|
29
|
-
export declare function checkFollowUps(memory: SageMemory,
|
|
29
|
+
export declare function checkFollowUps(memory: SageMemory, egress: SlackEgress, notifyChannel?: string, options?: CheckFollowUpsOptions): Promise<FollowUpStats>;
|
|
30
30
|
//# sourceMappingURL=follow-up-checker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"follow-up-checker.d.ts","sourceRoot":"","sources":["../../src/proactive/follow-up-checker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"follow-up-checker.d.ts","sourceRoot":"","sources":["../../src/proactive/follow-up-checker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAEV,UAAU,EAEV,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAkEpB,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAmUD,wBAAgB,WAAW,CACzB,SAAS,EAAE,iBAAiB,EAC5B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACtC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,GACnE,YAAY,CA8Bd;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvF;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,YAAY,EAClB,SAAS,EAAE,iBAAiB,EAC5B,WAAW,EAAE,QAAQ,GAAG,gBAAgB,EACxC,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,iBAAiB,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAuE5B;AAkED,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,WAAW,EACnB,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,aAAa,CAAC,CA2IxB"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { env } from "node:process";
|
|
2
2
|
import { chat } from "../openrouter.js";
|
|
3
|
-
import { postSlackMessageChunkedViaNango } from "../slack.js";
|
|
4
3
|
const AGENT_ID = "sage";
|
|
5
4
|
const CONVERSATION_TAG = "conversation";
|
|
6
5
|
const FOLLOW_UP_TAG = "proactive-follow-up-sent";
|
|
@@ -15,6 +14,11 @@ const STALE_PING_COOLDOWN_MS = 48 * 60 * 60_000;
|
|
|
15
14
|
const LOOKBACK_LIMIT = 100;
|
|
16
15
|
const MAX_FOLLOW_UPS_PER_RUN = 5;
|
|
17
16
|
const CLOSED_COOLDOWN_UNTIL = Number.MAX_SAFE_INTEGER;
|
|
17
|
+
const missingGitHubProxyClient = {
|
|
18
|
+
async proxy() {
|
|
19
|
+
throw new Error("GitHub proxy client is not configured");
|
|
20
|
+
},
|
|
21
|
+
};
|
|
18
22
|
const FOLLOW_UP_SYSTEM_PROMPT = `You decide whether Sage should send a proactive follow-up about an older Slack thread.
|
|
19
23
|
|
|
20
24
|
Return strict JSON:
|
|
@@ -399,7 +403,32 @@ function shouldPersistNoop(current, next) {
|
|
|
399
403
|
current.evidence.length !== next.evidence.length ||
|
|
400
404
|
current.question !== next.question);
|
|
401
405
|
}
|
|
402
|
-
|
|
406
|
+
function buildCollectCtx(memory, egress, slackBotUserId, options, now) {
|
|
407
|
+
return {
|
|
408
|
+
memory,
|
|
409
|
+
egress,
|
|
410
|
+
githubProxy: options.githubProxyClient ?? missingGitHubProxyClient,
|
|
411
|
+
slackBotUserId,
|
|
412
|
+
githubConnectionId: options.githubConnectionId,
|
|
413
|
+
now,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function resolveDeliveryTarget(item, candidate, notifyChannel) {
|
|
417
|
+
const channel = item.channel ?? candidate.channel ?? notifyChannel;
|
|
418
|
+
const destination = item.channel || candidate.channel ? "thread" : "notify-channel";
|
|
419
|
+
return {
|
|
420
|
+
channel,
|
|
421
|
+
destination,
|
|
422
|
+
threadTs: destination === "thread" ? item.threadTs : undefined,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function postSlackMessageViaEgress(egress, channel, message, threadTs) {
|
|
426
|
+
return egress.postMessageChunked(channel, message, threadTs, MAX_REPLY_CHARS);
|
|
427
|
+
}
|
|
428
|
+
function getSlackBotUserId(egress) {
|
|
429
|
+
return egress.getBotUserId();
|
|
430
|
+
}
|
|
431
|
+
export async function checkFollowUps(memory, egress, notifyChannel, options = {}) {
|
|
403
432
|
const workspaceId = getWorkspaceId(memory);
|
|
404
433
|
if (!workspaceId) {
|
|
405
434
|
console.warn("[proactive/follow-ups] Could not determine workspace ID");
|
|
@@ -417,24 +446,16 @@ export async function checkFollowUps(memory, nangoClient, slackConnectionId, sla
|
|
|
417
446
|
]);
|
|
418
447
|
const candidates = buildCandidates(conversationDocs, now);
|
|
419
448
|
const stats = { sent: 0, closed: 0, pinged: 0 };
|
|
449
|
+
const slackBotUserId = await getSlackBotUserId(egress);
|
|
420
450
|
for (const candidate of candidates) {
|
|
421
451
|
const item = hydrateItem(candidate, storedItems, legacyMarkers);
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
const ctx = {
|
|
425
|
-
memory,
|
|
426
|
-
nangoClient,
|
|
427
|
-
slackConnectionId,
|
|
428
|
-
slackProviderConfigKey,
|
|
429
|
-
slackBotUserId: options.slackBotUserId,
|
|
430
|
-
githubConnectionId: options.githubConnectionId,
|
|
431
|
-
now,
|
|
432
|
-
};
|
|
452
|
+
const deliveryTarget = resolveDeliveryTarget(item, candidate, notifyChannel);
|
|
453
|
+
const ctx = buildCollectCtx(memory, egress, slackBotUserId, options, now);
|
|
433
454
|
try {
|
|
434
|
-
const evaluated = await evaluateFollowUp(item, candidate, destination, ctx, collector);
|
|
455
|
+
const evaluated = await evaluateFollowUp(item, candidate, deliveryTarget.destination, ctx, collector);
|
|
435
456
|
if (evaluated.action === "close") {
|
|
436
457
|
if (item.channel) {
|
|
437
|
-
const result = await
|
|
458
|
+
const result = await postSlackMessageViaEgress(egress, item.channel, `Marking this one as done based on ${evaluated.reason}. Ping me if I got it wrong.`, item.threadTs);
|
|
438
459
|
if (!result.ok) {
|
|
439
460
|
console.warn(`[proactive/follow-ups] Failed to post closure note for ${item.threadTs}: ${result.error}`);
|
|
440
461
|
}
|
|
@@ -450,11 +471,10 @@ export async function checkFollowUps(memory, nangoClient, slackConnectionId, sla
|
|
|
450
471
|
continue;
|
|
451
472
|
}
|
|
452
473
|
if (evaluated.action === "send-follow-up") {
|
|
453
|
-
if (!
|
|
474
|
+
if (!deliveryTarget.channel || !evaluated.message) {
|
|
454
475
|
continue;
|
|
455
476
|
}
|
|
456
|
-
const
|
|
457
|
-
const result = await postSlackMessageChunkedViaNango(targetChannel, evaluated.message, targetThreadTs, nangoClient, slackConnectionId, slackProviderConfigKey, MAX_REPLY_CHARS);
|
|
477
|
+
const result = await postSlackMessageViaEgress(egress, deliveryTarget.channel, evaluated.message, deliveryTarget.threadTs);
|
|
458
478
|
if (!result.ok) {
|
|
459
479
|
console.warn(`[proactive/follow-ups] Failed to post follow-up for ${item.threadTs}: ${result.error}`);
|
|
460
480
|
continue;
|
|
@@ -477,11 +497,10 @@ export async function checkFollowUps(memory, nangoClient, slackConnectionId, sla
|
|
|
477
497
|
continue;
|
|
478
498
|
}
|
|
479
499
|
if (evaluated.action === "ping-stale") {
|
|
480
|
-
if (!
|
|
500
|
+
if (!deliveryTarget.channel || !evaluated.message) {
|
|
481
501
|
continue;
|
|
482
502
|
}
|
|
483
|
-
const
|
|
484
|
-
const result = await postSlackMessageChunkedViaNango(targetChannel, evaluated.message, targetThreadTs, nangoClient, slackConnectionId, slackProviderConfigKey, MAX_REPLY_CHARS);
|
|
503
|
+
const result = await postSlackMessageViaEgress(egress, deliveryTarget.channel, evaluated.message, deliveryTarget.threadTs);
|
|
485
504
|
if (!result.ok) {
|
|
486
505
|
console.warn(`[proactive/follow-ups] Failed to post stale ping for ${item.threadTs}: ${result.error}`);
|
|
487
506
|
continue;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-egress.d.ts","sourceRoot":"","sources":["../../../src/proactive/integrations/slack-egress.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { SlackEgress } from "../integrations/slack-egress.js";
|
|
1
2
|
import type { SageRelayFileReader } from "../integrations/relayfile-reader.js";
|
|
2
3
|
import type { SageMemory } from "../memory.js";
|
|
3
|
-
import type { NangoClient } from "../nango.js";
|
|
4
4
|
interface PullRequestData {
|
|
5
5
|
number: number;
|
|
6
6
|
title: string;
|
|
@@ -9,6 +9,6 @@ interface PullRequestData {
|
|
|
9
9
|
files?: string[];
|
|
10
10
|
url?: string;
|
|
11
11
|
}
|
|
12
|
-
export declare function matchPRToPlans(prData: PullRequestData, memory: SageMemory,
|
|
12
|
+
export declare function matchPRToPlans(prData: PullRequestData, memory: SageMemory, egress: SlackEgress, notifyChannel?: string, reader?: SageRelayFileReader): Promise<boolean>;
|
|
13
13
|
export {};
|
|
14
14
|
//# sourceMappingURL=pr-matcher.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-matcher.d.ts","sourceRoot":"","sources":["../../src/proactive/pr-matcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"pr-matcher.d.ts","sourceRoot":"","sources":["../../src/proactive/pr-matcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAG/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AA4B/C,UAAU,eAAe;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA4aD,wBAAsB,cAAc,CAClC,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,WAAW,EACnB,aAAa,CAAC,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,OAAO,CAAC,CAmFlB"}
|