@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
|
@@ -2,7 +2,6 @@ import { env } from "node:process";
|
|
|
2
2
|
import { readPrDiff } from "../integrations/pr-diff.js";
|
|
3
3
|
import { chat } from "../openrouter.js";
|
|
4
4
|
import { PlanOutcomeStore } from "../memory/plan-outcomes.js";
|
|
5
|
-
import { postSlackMessageChunkedViaNango } from "../slack.js";
|
|
6
5
|
import { collectPlannedPaths, detectDrift, renderDriftSummary } from "./plan-drift.js";
|
|
7
6
|
const AGENT_ID = "sage";
|
|
8
7
|
const CONVERSATION_TAG = "conversation";
|
|
@@ -275,7 +274,7 @@ async function hasAlreadyNotified(workspaceId, prNumber) {
|
|
|
275
274
|
const markers = await listMarkers(workspaceId);
|
|
276
275
|
return markers.some((marker) => marker.includes(`pr=${prNumber}`));
|
|
277
276
|
}
|
|
278
|
-
async function maybePostPlanDrift(prData, workspaceId, memory, reader,
|
|
277
|
+
async function maybePostPlanDrift(prData, workspaceId, memory, reader, egress, notifyChannel) {
|
|
279
278
|
if (!isPlanDriftEnabled() || !reader?.isEnabled()) {
|
|
280
279
|
return;
|
|
281
280
|
}
|
|
@@ -306,7 +305,7 @@ async function maybePostPlanDrift(prData, workspaceId, memory, reader, nangoClie
|
|
|
306
305
|
repo: prData.repo,
|
|
307
306
|
...(prData.url ? { url: prData.url } : {}),
|
|
308
307
|
});
|
|
309
|
-
const postResult = await
|
|
308
|
+
const postResult = await postSlackNotificationViaEgress(egress, targetChannel, message, targetThreadTs);
|
|
310
309
|
if (!postResult.ok) {
|
|
311
310
|
console.warn(`[proactive/pr-matcher] Failed to post plan drift for #${prData.number}: ${postResult.error}`);
|
|
312
311
|
}
|
|
@@ -323,7 +322,10 @@ async function maybePostPlanDrift(prData, workspaceId, memory, reader, nangoClie
|
|
|
323
322
|
createdAt: Date.now(),
|
|
324
323
|
});
|
|
325
324
|
}
|
|
326
|
-
|
|
325
|
+
function postSlackNotificationViaEgress(egress, channel, message, threadTs) {
|
|
326
|
+
return egress.postMessageChunked(channel, message, threadTs, MAX_REPLY_CHARS);
|
|
327
|
+
}
|
|
328
|
+
export async function matchPRToPlans(prData, memory, egress, notifyChannel, reader) {
|
|
327
329
|
if (!notifyChannel) {
|
|
328
330
|
console.warn("[proactive/pr-matcher] No notification channel configured");
|
|
329
331
|
return false;
|
|
@@ -367,14 +369,14 @@ export async function matchPRToPlans(prData, memory, nangoClient, slackConnectio
|
|
|
367
369
|
console.log(`[proactive/pr-matcher] No confident match for PR #${prData.number}`);
|
|
368
370
|
return false;
|
|
369
371
|
}
|
|
370
|
-
const result = await
|
|
372
|
+
const result = await postSlackNotificationViaEgress(egress, notifyChannel, decision.message);
|
|
371
373
|
if (!result.ok) {
|
|
372
374
|
console.warn(`[proactive/pr-matcher] Failed to post PR match for #${prData.number}: ${result.error}`);
|
|
373
375
|
return false;
|
|
374
376
|
}
|
|
375
377
|
await memory.saveWorkspaceContext(`pr=${prData.number} repo=${prData.repo} title=${prData.title}`, [PR_MATCH_TAG]);
|
|
376
378
|
try {
|
|
377
|
-
await maybePostPlanDrift(prData, workspaceId, memory, reader,
|
|
379
|
+
await maybePostPlanDrift(prData, workspaceId, memory, reader, egress, notifyChannel);
|
|
378
380
|
}
|
|
379
381
|
catch (error) {
|
|
380
382
|
console.error(`[proactive/pr-matcher] Plan drift boomerang failed for #${prData.number}`, error);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { SlackEgress } from "../integrations/slack-egress.js";
|
|
1
2
|
import type { SageMemory } from "../memory.js";
|
|
2
|
-
import type { NangoClient } from "../nango.js";
|
|
3
3
|
interface ActiveThreadContext {
|
|
4
4
|
workspaceId: string;
|
|
5
5
|
channel: string;
|
|
6
6
|
}
|
|
7
|
-
export declare function detectStaleThreads(memory: SageMemory, workspaceId: string,
|
|
7
|
+
export declare function detectStaleThreads(memory: SageMemory, workspaceId: string, egress: SlackEgress, activeThreads: Map<string, ActiveThreadContext>, notifyChannel?: string): Promise<number>;
|
|
8
8
|
export {};
|
|
9
9
|
//# sourceMappingURL=stale-thread-detector.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stale-thread-detector.d.ts","sourceRoot":"","sources":["../../src/proactive/stale-thread-detector.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stale-thread-detector.d.ts","sourceRoot":"","sources":["../../src/proactive/stale-thread-detector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AA+B/C,UAAU,mBAAmB;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AA2MD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,WAAW,EACnB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC/C,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 { fetchThreadHistoryViaNango, postSlackMessageChunkedViaNango } from "../slack.js";
|
|
4
3
|
const AGENT_ID = "sage";
|
|
5
4
|
const HAIKU_MODEL = "anthropic/claude-haiku-4-5";
|
|
6
5
|
const THREE_DAYS_MS = 3 * 24 * 60 * 60_000;
|
|
@@ -134,24 +133,8 @@ async function listRecentlyAlertedThreadIds(workspaceId, now) {
|
|
|
134
133
|
}
|
|
135
134
|
return alertedThreadIds;
|
|
136
135
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
channel: ref.channel,
|
|
140
|
-
ts: ref.threadTs,
|
|
141
|
-
limit: "50",
|
|
142
|
-
});
|
|
143
|
-
const response = await nangoClient.proxy({
|
|
144
|
-
connectionId: slackConnectionId,
|
|
145
|
-
providerConfigKey: slackProviderConfigKey,
|
|
146
|
-
method: "GET",
|
|
147
|
-
endpoint: `/conversations.replies?${params.toString()}`,
|
|
148
|
-
});
|
|
149
|
-
if (!response.ok || !Array.isArray(response.messages) || response.messages.length === 0) {
|
|
150
|
-
return 0;
|
|
151
|
-
}
|
|
152
|
-
return response.messages
|
|
153
|
-
.filter((message) => !message.subtype || message.subtype === "bot_message")
|
|
154
|
-
.reduce((latest, message) => Math.max(latest, parseSlackTimestamp(message.ts)), 0);
|
|
136
|
+
function getLastActivityMs(history) {
|
|
137
|
+
return history.reduce((latest, message) => Math.max(latest, parseSlackTimestamp(message.ts)), 0);
|
|
155
138
|
}
|
|
156
139
|
async function evaluateThread(ref, history, quietPeriod) {
|
|
157
140
|
const response = await chat([
|
|
@@ -169,7 +152,16 @@ async function evaluateThread(ref, history, quietPeriod) {
|
|
|
169
152
|
], { model: HAIKU_MODEL, temperature: 0 });
|
|
170
153
|
return parseDecision(response.content);
|
|
171
154
|
}
|
|
172
|
-
|
|
155
|
+
function postStaleThreadAlertViaEgress(egress, channel, message) {
|
|
156
|
+
return egress.postMessageChunked(channel, message, undefined, MAX_REPLY_CHARS);
|
|
157
|
+
}
|
|
158
|
+
function fetchThreadHistoryViaEgress(egress, channel, threadTs, slackBotUserId) {
|
|
159
|
+
return egress.fetchThreadHistory(channel, threadTs, slackBotUserId);
|
|
160
|
+
}
|
|
161
|
+
function getSlackBotUserId(egress) {
|
|
162
|
+
return egress.getBotUserId();
|
|
163
|
+
}
|
|
164
|
+
export async function detectStaleThreads(memory, workspaceId, egress, activeThreads, notifyChannel) {
|
|
173
165
|
if (activeThreads.size === 0) {
|
|
174
166
|
console.log("[proactive/stale-threads] No active threads to inspect");
|
|
175
167
|
return 0;
|
|
@@ -185,6 +177,7 @@ export async function detectStaleThreads(memory, workspaceId, nangoClient, slack
|
|
|
185
177
|
return 0;
|
|
186
178
|
}
|
|
187
179
|
let alerts = 0;
|
|
180
|
+
const slackBotUserId = await getSlackBotUserId(egress);
|
|
188
181
|
for (const ref of threadRefs) {
|
|
189
182
|
if (alerts >= MAX_ALERTS_PER_RUN) {
|
|
190
183
|
break;
|
|
@@ -193,11 +186,11 @@ export async function detectStaleThreads(memory, workspaceId, nangoClient, slack
|
|
|
193
186
|
continue;
|
|
194
187
|
}
|
|
195
188
|
try {
|
|
196
|
-
const history = await
|
|
189
|
+
const history = await fetchThreadHistoryViaEgress(egress, ref.channel, ref.threadTs, slackBotUserId);
|
|
197
190
|
if (history.length < 2 || !history.some((message) => message.role === "assistant")) {
|
|
198
191
|
continue;
|
|
199
192
|
}
|
|
200
|
-
const lastActivityMs =
|
|
193
|
+
const lastActivityMs = getLastActivityMs(history);
|
|
201
194
|
if (!lastActivityMs || now - lastActivityMs < THREE_DAYS_MS) {
|
|
202
195
|
continue;
|
|
203
196
|
}
|
|
@@ -206,7 +199,7 @@ export async function detectStaleThreads(memory, workspaceId, nangoClient, slack
|
|
|
206
199
|
if (!decision?.shouldAlert || !decision.message) {
|
|
207
200
|
continue;
|
|
208
201
|
}
|
|
209
|
-
const result = await
|
|
202
|
+
const result = await postStaleThreadAlertViaEgress(egress, notifyChannel ?? ref.channel, decision.message);
|
|
210
203
|
if (!result.ok) {
|
|
211
204
|
console.warn(`[proactive/stale-threads] Failed to post alert for ${ref.threadTs}: ${result.error}`);
|
|
212
205
|
continue;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { SageMemory } from "../memory.js";
|
|
2
|
-
import type {
|
|
3
|
-
import type { ThreadMessage } from "../slack.js";
|
|
2
|
+
import type { SlackEgress, ThreadMessage } from "../integrations/slack-egress.js";
|
|
4
3
|
export type EvidenceSourceId = "pr-merge" | "affirmative-reply" | "reaction" | "explicit-close";
|
|
5
4
|
export type FollowUpStatus = "open" | "likely-done" | "closed" | "stale";
|
|
6
5
|
export type FollowUpAction = "send-follow-up" | "close" | "likely-done" | "open" | "ping-stale";
|
|
@@ -39,14 +38,17 @@ export interface ClosureDecision {
|
|
|
39
38
|
confidence: number;
|
|
40
39
|
reason: string;
|
|
41
40
|
}
|
|
41
|
+
export interface GitHubProxyClient {
|
|
42
|
+
proxy<T>(config: Record<string, unknown>): Promise<T>;
|
|
43
|
+
}
|
|
42
44
|
export interface CollectCtx {
|
|
43
45
|
memory: SageMemory;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
now: number;
|
|
47
|
+
egress: SlackEgress;
|
|
48
|
+
githubProxy?: GitHubProxyClient;
|
|
47
49
|
slackBotUserId?: string;
|
|
48
50
|
githubConnectionId?: string;
|
|
49
|
-
|
|
51
|
+
githubProviderConfigKey?: string;
|
|
50
52
|
}
|
|
51
53
|
export type { ThreadMessage };
|
|
52
54
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/proactive/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/proactive/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAElF,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,mBAAmB,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAEhG,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEzE,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG,OAAO,GAAG,aAAa,GAAG,MAAM,GAAG,YAAY,CAAC;AAEhG,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,YAAY,EAAE,aAAa,EAAE,CAAC"}
|
package/dist/slack.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
export declare const SLACK_MESSAGE_MAX_CHARS = 3000;
|
|
2
2
|
export interface SlackEvent {
|
|
3
3
|
type: "url_verification" | "mention" | "message" | "unknown";
|
|
4
4
|
challenge?: string;
|
|
@@ -15,6 +15,8 @@ export interface ThreadMessage {
|
|
|
15
15
|
content: string;
|
|
16
16
|
ts?: string;
|
|
17
17
|
}
|
|
18
|
+
export declare function cleanSlackText(text: string | undefined): string;
|
|
19
|
+
export declare function splitSlackChunk(text: string, maxChars: number, separators?: string[]): string[];
|
|
18
20
|
export declare function verifySlackSignature(body: string, timestamp: string, signature: string, signingSecret: string): boolean;
|
|
19
21
|
export interface NangoSlackEnvelope {
|
|
20
22
|
slackPayload: unknown;
|
|
@@ -41,16 +43,4 @@ export declare function parseSlackEvent(payload: unknown): SlackEvent;
|
|
|
41
43
|
*/
|
|
42
44
|
export declare function markdownToSlackMrkdwn(text: string): string;
|
|
43
45
|
export declare function chunkSlackMessage(text: string, maxChars?: number): string[];
|
|
44
|
-
export declare function postSlackMessageViaNango(channel: string, text: string, threadTs: string | undefined, nangoClient: NangoClient, connectionId: string, providerConfigKey: string): Promise<{
|
|
45
|
-
ok: boolean;
|
|
46
|
-
ts?: string;
|
|
47
|
-
error?: string;
|
|
48
|
-
}>;
|
|
49
|
-
export declare function postSlackMessageChunkedViaNango(channel: string, text: string, threadTs: string | undefined, nangoClient: NangoClient, connectionId: string, providerConfigKey: string, maxChars?: number): Promise<{
|
|
50
|
-
ok: boolean;
|
|
51
|
-
ts?: string;
|
|
52
|
-
error?: string;
|
|
53
|
-
}>;
|
|
54
|
-
export declare function addSlackReactionViaNango(channel: string, timestamp: string, emoji: string, nangoClient: NangoClient, connectionId: string, providerConfigKey: string): Promise<void>;
|
|
55
|
-
export declare function fetchThreadHistoryViaNango(channel: string, threadTs: string, slackBotUserId: string | undefined, nangoClient: NangoClient, connectionId: string, providerConfigKey: string): Promise<ThreadMessage[]>;
|
|
56
46
|
//# sourceMappingURL=slack.d.ts.map
|
package/dist/slack.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../src/slack.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../src/slack.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,uBAAuB,OAAQ,CAAC;AAE7C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,kBAAkB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAUD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAE/D;AAiBD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,UAAU,GAAE,MAAM,EAA8B,GAC/C,MAAM,EAAE,CA+CV;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CA2BT;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,kBAAkB,CAmB5E;AAQD,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CA+C5D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA4C1D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAQ,GAAG,MAAM,EAAE,CAG1E"}
|
package/dist/slack.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
-
|
|
2
|
+
// Legacy compatibility surface for shared Slack parsing and formatting utils.
|
|
3
|
+
// All outbound Slack API work lives in src/integrations/slack-egress.ts.
|
|
3
4
|
const SLACK_SIGNATURE_VERSION = "v0";
|
|
4
5
|
const MAX_TIMESTAMP_AGE_SECONDS = 60 * 5;
|
|
5
6
|
const SLACK_MENTION_PATTERN = /<@[A-Z0-9]+(?:\|[^>]+)?>/g;
|
|
6
|
-
const
|
|
7
|
+
export const SLACK_MESSAGE_MAX_CHARS = 3_000;
|
|
7
8
|
function isRecord(value) {
|
|
8
9
|
return typeof value === "object" && value !== null;
|
|
9
10
|
}
|
|
10
11
|
function asString(value) {
|
|
11
12
|
return typeof value === "string" ? value : undefined;
|
|
12
13
|
}
|
|
13
|
-
function cleanSlackText(text) {
|
|
14
|
+
export function cleanSlackText(text) {
|
|
14
15
|
return (text ?? "").replaceAll(SLACK_MENTION_PATTERN, "").trim();
|
|
15
16
|
}
|
|
16
17
|
function hasBotIdentity(event) {
|
|
@@ -26,7 +27,7 @@ function hardSplit(text, maxChars) {
|
|
|
26
27
|
}
|
|
27
28
|
return chunks;
|
|
28
29
|
}
|
|
29
|
-
function splitSlackChunk(text, maxChars, separators = ["\n\n", "\n", ". ", " "]) {
|
|
30
|
+
export function splitSlackChunk(text, maxChars, separators = ["\n\n", "\n", ". ", " "]) {
|
|
30
31
|
const normalized = text.trim();
|
|
31
32
|
if (!normalized) {
|
|
32
33
|
return [];
|
|
@@ -211,108 +212,6 @@ export function markdownToSlackMrkdwn(text) {
|
|
|
211
212
|
return result.trim();
|
|
212
213
|
}
|
|
213
214
|
export function chunkSlackMessage(text, maxChars = 3_000) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
export async function postSlackMessageViaNango(channel, text, threadTs, nangoClient, connectionId, providerConfigKey) {
|
|
217
|
-
const body = {
|
|
218
|
-
channel,
|
|
219
|
-
text,
|
|
220
|
-
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
221
|
-
unfurl_links: false,
|
|
222
|
-
unfurl_media: false,
|
|
223
|
-
};
|
|
224
|
-
try {
|
|
225
|
-
const data = await nangoClient.proxy({
|
|
226
|
-
connectionId,
|
|
227
|
-
providerConfigKey,
|
|
228
|
-
method: "POST",
|
|
229
|
-
endpoint: "/chat.postMessage",
|
|
230
|
-
data: body,
|
|
231
|
-
});
|
|
232
|
-
return {
|
|
233
|
-
ok: data.ok === true,
|
|
234
|
-
...(data.ts ? { ts: data.ts } : {}),
|
|
235
|
-
...(data.error ? { error: data.error } : {}),
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
catch (error) {
|
|
239
|
-
return {
|
|
240
|
-
ok: false,
|
|
241
|
-
error: error instanceof Error ? error.message : "Slack request failed via Nango",
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
export async function postSlackMessageChunkedViaNango(channel, text, threadTs, nangoClient, connectionId, providerConfigKey, maxChars = 3_000) {
|
|
246
|
-
const chunks = chunkSlackMessage(text, maxChars);
|
|
247
|
-
if (!chunks.length) {
|
|
248
|
-
return { ok: false, error: "Cannot post an empty Slack message" };
|
|
249
|
-
}
|
|
250
|
-
let firstTs;
|
|
251
|
-
let replyThreadTs = threadTs;
|
|
252
|
-
for (const chunk of chunks) {
|
|
253
|
-
const result = await postSlackMessageViaNango(channel, chunk, replyThreadTs, nangoClient, connectionId, providerConfigKey);
|
|
254
|
-
if (!result.ok) {
|
|
255
|
-
return {
|
|
256
|
-
ok: false,
|
|
257
|
-
...(firstTs ? { ts: firstTs } : {}),
|
|
258
|
-
...(result.error ? { error: result.error } : {}),
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
firstTs ??= result.ts;
|
|
262
|
-
replyThreadTs ??= result.ts;
|
|
263
|
-
}
|
|
264
|
-
return {
|
|
265
|
-
ok: true,
|
|
266
|
-
...(firstTs ? { ts: firstTs } : {}),
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
export async function addSlackReactionViaNango(channel, timestamp, emoji, nangoClient, connectionId, providerConfigKey) {
|
|
270
|
-
try {
|
|
271
|
-
await nangoClient.proxy({
|
|
272
|
-
connectionId,
|
|
273
|
-
providerConfigKey,
|
|
274
|
-
method: "POST",
|
|
275
|
-
endpoint: "/reactions.add",
|
|
276
|
-
data: { channel, timestamp, name: emoji },
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
catch {
|
|
280
|
-
// Non-fatal — don't block processing if reaction fails
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
export async function fetchThreadHistoryViaNango(channel, threadTs, slackBotUserId, nangoClient, connectionId, providerConfigKey) {
|
|
284
|
-
try {
|
|
285
|
-
const params = new URLSearchParams({
|
|
286
|
-
channel,
|
|
287
|
-
ts: threadTs,
|
|
288
|
-
limit: "50",
|
|
289
|
-
});
|
|
290
|
-
const data = await nangoClient.proxy({
|
|
291
|
-
connectionId,
|
|
292
|
-
providerConfigKey,
|
|
293
|
-
method: "GET",
|
|
294
|
-
endpoint: `/conversations.replies?${params.toString()}`,
|
|
295
|
-
});
|
|
296
|
-
if (!data.ok || !Array.isArray(data.messages)) {
|
|
297
|
-
return [];
|
|
298
|
-
}
|
|
299
|
-
return data.messages
|
|
300
|
-
.filter((message) => !message.subtype || message.subtype === "bot_message")
|
|
301
|
-
.sort((left, right) => Number(left.ts ?? "0") - Number(right.ts ?? "0"))
|
|
302
|
-
.map((message) => {
|
|
303
|
-
const content = cleanSlackText(message.text);
|
|
304
|
-
const isAssistant = Boolean(message.bot_id) || (!!slackBotUserId && message.user === slackBotUserId);
|
|
305
|
-
return {
|
|
306
|
-
role: isAssistant ? "assistant" : "user",
|
|
307
|
-
content,
|
|
308
|
-
...(message.ts ? { ts: message.ts } : {}),
|
|
309
|
-
};
|
|
310
|
-
})
|
|
311
|
-
.filter((message) => message.content.length > 0)
|
|
312
|
-
.slice(-20);
|
|
313
|
-
}
|
|
314
|
-
catch (error) {
|
|
315
|
-
console.error("Failed to fetch Slack thread history via Nango", error);
|
|
316
|
-
return [];
|
|
317
|
-
}
|
|
215
|
+
const boundedMaxChars = Math.min(Math.max(1, maxChars), SLACK_MESSAGE_MAX_CHARS);
|
|
216
|
+
return splitSlackChunk(text, boundedMaxChars);
|
|
318
217
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -14,8 +14,7 @@ export interface SageBindings {
|
|
|
14
14
|
RELAYFILE_WORKSPACE_ID?: string;
|
|
15
15
|
RELAY_JWT_SECRET?: string;
|
|
16
16
|
NANGO_GITHUB_CONNECTION_ID?: string;
|
|
17
|
-
|
|
18
|
-
NANGO_SLACK_PROVIDER_CONFIG_KEY?: string;
|
|
17
|
+
NANGO_GITHUB_PROVIDER_CONFIG_KEY?: string;
|
|
19
18
|
SLACK_BOT_USER_ID?: string;
|
|
20
19
|
SLACK_SIGNING_SECRET?: string;
|
|
21
20
|
SAGE_NOTIFY_CHANNEL?: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAI/E;;GAEG;AACH,MAAM,WAAW,YAAY;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAI/E;;GAEG;AACH,MAAM,WAAW,YAAY;IAE3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAE5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;CAClF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentworkforce/sage",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/AgentWorkforce/sage.git"
|
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
"@agentcron/sdk": "^0.1.0",
|
|
39
39
|
"@nangohq/node": "^0.42.0",
|
|
40
40
|
"@nangohq/types": "^0.69.48",
|
|
41
|
+
"@relayfile/adapter-slack": "^0.1.3",
|
|
42
|
+
"@relayfile/provider-nango": "^0.2.1",
|
|
41
43
|
"agent-trajectories": "^0.5.0",
|
|
42
44
|
"hono": "^4.0.0"
|
|
43
45
|
},
|