@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.
Files changed (56) hide show
  1. package/.env.example +5 -4
  2. package/dist/app.d.ts +0 -6
  3. package/dist/app.d.ts.map +1 -1
  4. package/dist/app.js +264 -217
  5. package/dist/integrations/cloud-proxy-provider.d.ts +42 -0
  6. package/dist/integrations/cloud-proxy-provider.d.ts.map +1 -0
  7. package/dist/integrations/cloud-proxy-provider.js +131 -0
  8. package/dist/integrations/github-context.d.ts +2 -1
  9. package/dist/integrations/github-context.d.ts.map +1 -1
  10. package/dist/integrations/github-context.js +4 -2
  11. package/dist/integrations/github.d.ts +4 -2
  12. package/dist/integrations/github.d.ts.map +1 -1
  13. package/dist/integrations/github.js +16 -11
  14. package/dist/integrations/slack-egress.d.ts +28 -0
  15. package/dist/integrations/slack-egress.d.ts.map +1 -0
  16. package/dist/integrations/slack-egress.js +181 -0
  17. package/dist/integrations/slack-ingress.d.ts +26 -0
  18. package/dist/integrations/slack-ingress.d.ts.map +1 -0
  19. package/dist/integrations/slack-ingress.js +31 -0
  20. package/dist/nango.d.ts +1 -6
  21. package/dist/nango.d.ts.map +1 -1
  22. package/dist/nango.js +9 -34
  23. package/dist/proactive/context-watcher.d.ts +2 -2
  24. package/dist/proactive/context-watcher.d.ts.map +1 -1
  25. package/dist/proactive/context-watcher.js +5 -3
  26. package/dist/proactive/engine.d.ts +5 -9
  27. package/dist/proactive/engine.d.ts.map +1 -1
  28. package/dist/proactive/engine.js +25 -20
  29. package/dist/proactive/evidence-sources/affirmative-reply-source.d.ts.map +1 -1
  30. package/dist/proactive/evidence-sources/affirmative-reply-source.js +4 -2
  31. package/dist/proactive/evidence-sources/explicit-close-source.d.ts.map +1 -1
  32. package/dist/proactive/evidence-sources/explicit-close-source.js +4 -2
  33. package/dist/proactive/evidence-sources/pr-merge-source.d.ts.map +1 -1
  34. package/dist/proactive/evidence-sources/pr-merge-source.js +12 -5
  35. package/dist/proactive/evidence-sources/reaction-source.d.ts.map +1 -1
  36. package/dist/proactive/evidence-sources/reaction-source.js +6 -15
  37. package/dist/proactive/follow-up-checker.d.ts +4 -4
  38. package/dist/proactive/follow-up-checker.d.ts.map +1 -1
  39. package/dist/proactive/follow-up-checker.js +40 -21
  40. package/dist/proactive/integrations/slack-egress.d.ts +2 -0
  41. package/dist/proactive/integrations/slack-egress.d.ts.map +1 -0
  42. package/dist/proactive/integrations/slack-egress.js +1 -0
  43. package/dist/proactive/pr-matcher.d.ts +2 -2
  44. package/dist/proactive/pr-matcher.d.ts.map +1 -1
  45. package/dist/proactive/pr-matcher.js +8 -6
  46. package/dist/proactive/stale-thread-detector.d.ts +2 -2
  47. package/dist/proactive/stale-thread-detector.d.ts.map +1 -1
  48. package/dist/proactive/stale-thread-detector.js +16 -23
  49. package/dist/proactive/types.d.ts +8 -6
  50. package/dist/proactive/types.d.ts.map +1 -1
  51. package/dist/slack.d.ts +3 -13
  52. package/dist/slack.d.ts.map +1 -1
  53. package/dist/slack.js +7 -108
  54. package/dist/types.d.ts +1 -2
  55. package/dist/types.d.ts.map +1 -1
  56. 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, nangoClient, slackConnectionId, slackProviderConfigKey, notifyChannel) {
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 postSlackMessageChunkedViaNango(targetChannel, message, targetThreadTs, nangoClient, slackConnectionId, slackProviderConfigKey, MAX_REPLY_CHARS);
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
- export async function matchPRToPlans(prData, memory, nangoClient, slackConnectionId, slackProviderConfigKey, notifyChannel, reader) {
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 postSlackMessageChunkedViaNango(notifyChannel, decision.message, undefined, nangoClient, slackConnectionId, slackProviderConfigKey, MAX_REPLY_CHARS);
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, nangoClient, slackConnectionId, slackProviderConfigKey, notifyChannel);
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, nangoClient: NangoClient, slackConnectionId: string, slackProviderConfigKey: string, activeThreads: Map<string, ActiveThreadContext>, slackBotUserId?: string, notifyChannel?: string): Promise<number>;
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":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAgC/C,UAAU,mBAAmB;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAyND,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,WAAW,EACxB,iBAAiB,EAAE,MAAM,EACzB,sBAAsB,EAAE,MAAM,EAC9B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC/C,cAAc,CAAC,EAAE,MAAM,EACvB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,CAAC,CAqFjB"}
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
- async function fetchLastActivityMs(ref, nangoClient, slackConnectionId, slackProviderConfigKey) {
138
- const params = new URLSearchParams({
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
- export async function detectStaleThreads(memory, workspaceId, nangoClient, slackConnectionId, slackProviderConfigKey, activeThreads, slackBotUserId, notifyChannel) {
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 fetchThreadHistoryViaNango(ref.channel, ref.threadTs, slackBotUserId, nangoClient, slackConnectionId, slackProviderConfigKey);
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 = await fetchLastActivityMs(ref, nangoClient, slackConnectionId, slackProviderConfigKey);
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 postSlackMessageChunkedViaNango(notifyChannel ?? ref.channel, decision.message, undefined, nangoClient, slackConnectionId, slackProviderConfigKey, MAX_REPLY_CHARS);
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 { NangoClient } from "../nango.js";
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
- nangoClient: NangoClient;
45
- slackConnectionId: string;
46
- slackProviderConfigKey: string;
46
+ now: number;
47
+ egress: SlackEgress;
48
+ githubProxy?: GitHubProxyClient;
47
49
  slackBotUserId?: string;
48
50
  githubConnectionId?: string;
49
- now: number;
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,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,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,UAAU;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,YAAY,EAAE,aAAa,EAAE,CAAC"}
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
- import type { NangoClient } from "./nango.js";
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
@@ -1 +1 @@
1
- {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../src/slack.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAQ9C,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;AAgGD,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,CAE1E;AAED,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6BvD;AAED,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,SAAQ,GACf,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAkCvD;AAED,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,aAAa,EAAE,CAAC,CAqC1B"}
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
- const SLACK_API_BASE = "https://slack.com/api";
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 REQUEST_TIMEOUT_MS = 30_000;
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
- return splitSlackChunk(text, maxChars);
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
- NANGO_SLACK_CONNECTION_ID?: string;
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;
@@ -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;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAE5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,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,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,+BAA+B,CAAC,EAAE,MAAM,CAAC;IACzC,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"}
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.6",
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
  },