@agentworkforce/sage 1.0.6 → 1.1.1

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 (74) 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 +139 -0
  8. package/dist/integrations/freshness-envelope.d.ts +17 -0
  9. package/dist/integrations/freshness-envelope.d.ts.map +1 -0
  10. package/dist/integrations/freshness-envelope.js +41 -0
  11. package/dist/integrations/freshness-envelope.test.d.ts +2 -0
  12. package/dist/integrations/freshness-envelope.test.d.ts.map +1 -0
  13. package/dist/integrations/freshness-envelope.test.js +56 -0
  14. package/dist/integrations/github-context.d.ts +2 -1
  15. package/dist/integrations/github-context.d.ts.map +1 -1
  16. package/dist/integrations/github-context.js +4 -2
  17. package/dist/integrations/github.d.ts +4 -2
  18. package/dist/integrations/github.d.ts.map +1 -1
  19. package/dist/integrations/github.js +16 -11
  20. package/dist/integrations/recent-actions-overlay.d.ts +31 -0
  21. package/dist/integrations/recent-actions-overlay.d.ts.map +1 -0
  22. package/dist/integrations/recent-actions-overlay.js +80 -0
  23. package/dist/integrations/recent-actions-overlay.test.d.ts +2 -0
  24. package/dist/integrations/recent-actions-overlay.test.d.ts.map +1 -0
  25. package/dist/integrations/recent-actions-overlay.test.js +145 -0
  26. package/dist/integrations/slack-egress.d.ts +28 -0
  27. package/dist/integrations/slack-egress.d.ts.map +1 -0
  28. package/dist/integrations/slack-egress.js +181 -0
  29. package/dist/integrations/slack-ingress.d.ts +26 -0
  30. package/dist/integrations/slack-ingress.d.ts.map +1 -0
  31. package/dist/integrations/slack-ingress.js +31 -0
  32. package/dist/memory/org-memory.d.ts.map +1 -1
  33. package/dist/memory/org-memory.js +1 -0
  34. package/dist/memory.d.ts.map +1 -1
  35. package/dist/memory.js +1 -0
  36. package/dist/nango.d.ts +1 -6
  37. package/dist/nango.d.ts.map +1 -1
  38. package/dist/nango.js +9 -34
  39. package/dist/openrouter.d.ts.map +1 -1
  40. package/dist/openrouter.js +2 -1
  41. package/dist/proactive/context-watcher.d.ts +2 -2
  42. package/dist/proactive/context-watcher.d.ts.map +1 -1
  43. package/dist/proactive/context-watcher.js +5 -3
  44. package/dist/proactive/engine.d.ts +5 -9
  45. package/dist/proactive/engine.d.ts.map +1 -1
  46. package/dist/proactive/engine.js +25 -20
  47. package/dist/proactive/evidence-sources/affirmative-reply-source.d.ts.map +1 -1
  48. package/dist/proactive/evidence-sources/affirmative-reply-source.js +4 -2
  49. package/dist/proactive/evidence-sources/explicit-close-source.d.ts.map +1 -1
  50. package/dist/proactive/evidence-sources/explicit-close-source.js +4 -2
  51. package/dist/proactive/evidence-sources/pr-merge-source.d.ts.map +1 -1
  52. package/dist/proactive/evidence-sources/pr-merge-source.js +12 -5
  53. package/dist/proactive/evidence-sources/reaction-source.d.ts.map +1 -1
  54. package/dist/proactive/evidence-sources/reaction-source.js +6 -15
  55. package/dist/proactive/follow-up-checker.d.ts +4 -4
  56. package/dist/proactive/follow-up-checker.d.ts.map +1 -1
  57. package/dist/proactive/follow-up-checker.js +40 -21
  58. package/dist/proactive/integrations/slack-egress.d.ts +2 -0
  59. package/dist/proactive/integrations/slack-egress.d.ts.map +1 -0
  60. package/dist/proactive/integrations/slack-egress.js +1 -0
  61. package/dist/proactive/pr-matcher.d.ts +2 -2
  62. package/dist/proactive/pr-matcher.d.ts.map +1 -1
  63. package/dist/proactive/pr-matcher.js +8 -6
  64. package/dist/proactive/stale-thread-detector.d.ts +2 -2
  65. package/dist/proactive/stale-thread-detector.d.ts.map +1 -1
  66. package/dist/proactive/stale-thread-detector.js +16 -23
  67. package/dist/proactive/types.d.ts +8 -6
  68. package/dist/proactive/types.d.ts.map +1 -1
  69. package/dist/slack.d.ts +3 -13
  70. package/dist/slack.d.ts.map +1 -1
  71. package/dist/slack.js +7 -108
  72. package/dist/types.d.ts +1 -2
  73. package/dist/types.d.ts.map +1 -1
  74. package/package.json +3 -1
@@ -0,0 +1,42 @@
1
+ export type ProxyMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT";
2
+ export type ProxyHeaders = Record<string, string>;
3
+ export type ProxyQuery = Record<string, string>;
4
+ export interface ProxyRequest {
5
+ method: ProxyMethod;
6
+ endpoint: string;
7
+ workspaceId?: string;
8
+ data?: unknown;
9
+ params?: ProxyQuery;
10
+ }
11
+ export interface ProxyResponse<T = unknown> {
12
+ status: number;
13
+ headers: ProxyHeaders;
14
+ data: T;
15
+ }
16
+ export interface ConnectionProvider {
17
+ readonly name: string;
18
+ proxy<T = unknown>(request: ProxyRequest): Promise<ProxyResponse<T>>;
19
+ healthCheck(): Promise<boolean>;
20
+ }
21
+ export declare class CloudProxyError extends Error {
22
+ readonly status?: number;
23
+ readonly code: string;
24
+ readonly retryAfterMs?: number;
25
+ constructor(message: string, options?: {
26
+ status?: number;
27
+ code?: string;
28
+ retryAfterMs?: number;
29
+ });
30
+ }
31
+ export declare class CloudProxyProvider implements ConnectionProvider {
32
+ readonly name = "cloud-proxy";
33
+ private readonly baseUrl;
34
+ private readonly cloudApiToken;
35
+ constructor(config: {
36
+ cloudApiUrl: string;
37
+ cloudApiToken: string;
38
+ });
39
+ proxy<T = unknown>(request: ProxyRequest): Promise<ProxyResponse<T>>;
40
+ healthCheck(): Promise<boolean>;
41
+ }
42
+ //# sourceMappingURL=cloud-proxy-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-proxy-provider.d.ts","sourceRoot":"","sources":["../../src/integrations/cloud-proxy-provider.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AACtE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAClD,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEhD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,CAAC,CAAC;CACT;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAsDD,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;gBAEnB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;CAOjG;AAwBD,qBAAa,kBAAmB,YAAW,kBAAkB;IAC3D,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE;IAK5D,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IA0DpE,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAWtC"}
@@ -0,0 +1,139 @@
1
+ const HEALTH_ENDPOINT = "/api/health";
2
+ const SLACK_PROXY_ENDPOINT = "/api/v1/proxy/slack";
3
+ const REQUEST_TIMEOUT_MS = 30_000;
4
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5
+ const SLACK_TEAM_ID_PATTERN = /^[TE][A-Z0-9]{6,}$/;
6
+ function isRecord(value) {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+ function readString(value) {
10
+ return typeof value === "string" && value.trim() ? value : undefined;
11
+ }
12
+ function readNumber(value) {
13
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
14
+ }
15
+ function requireNonEmpty(value, field) {
16
+ const normalized = value.trim();
17
+ if (!normalized) {
18
+ throw new CloudProxyError(`Cloud proxy configuration is missing ${field}`);
19
+ }
20
+ return normalized;
21
+ }
22
+ function readWorkspaceId(request) {
23
+ const workspaceId = readString(request.workspaceId);
24
+ if (!workspaceId) {
25
+ throw new CloudProxyError("Cloud Slack proxy request is missing workspaceId", {
26
+ code: "invalid_request",
27
+ });
28
+ }
29
+ return workspaceId;
30
+ }
31
+ function toCloudWorkspaceIdentity(workspaceId) {
32
+ if (SLACK_TEAM_ID_PATTERN.test(workspaceId) && !UUID_PATTERN.test(workspaceId)) {
33
+ return { slackTeamId: workspaceId };
34
+ }
35
+ return { workspaceId };
36
+ }
37
+ export class CloudProxyError extends Error {
38
+ status;
39
+ code;
40
+ retryAfterMs;
41
+ constructor(message, options) {
42
+ super(message);
43
+ this.name = "CloudProxyError";
44
+ this.status = options?.status;
45
+ this.code = options?.code ?? "cloud_proxy_error";
46
+ this.retryAfterMs = options?.retryAfterMs;
47
+ }
48
+ }
49
+ function parseEnvelope(payload) {
50
+ if (!isRecord(payload) || typeof payload.ok !== "boolean") {
51
+ throw new CloudProxyError("Cloud Slack proxy returned an invalid response");
52
+ }
53
+ if (payload.ok) {
54
+ return {
55
+ ok: true,
56
+ data: ("data" in payload ? payload.data : undefined),
57
+ };
58
+ }
59
+ return {
60
+ ok: false,
61
+ ...(readString(payload.error) ? { error: readString(payload.error) } : {}),
62
+ ...(readString(payload.code) ? { code: readString(payload.code) } : {}),
63
+ ...(readNumber(payload.retryAfterMs) !== undefined
64
+ ? { retryAfterMs: readNumber(payload.retryAfterMs) }
65
+ : {}),
66
+ };
67
+ }
68
+ export class CloudProxyProvider {
69
+ name = "cloud-proxy";
70
+ baseUrl;
71
+ cloudApiToken;
72
+ constructor(config) {
73
+ this.baseUrl = requireNonEmpty(config.cloudApiUrl, "cloudApiUrl").replace(/\/+$/, "");
74
+ this.cloudApiToken = requireNonEmpty(config.cloudApiToken, "cloudApiToken");
75
+ }
76
+ async proxy(request) {
77
+ let response;
78
+ const workspaceId = readWorkspaceId(request);
79
+ try {
80
+ response = await fetch(`${this.baseUrl}${SLACK_PROXY_ENDPOINT}`, {
81
+ method: "POST",
82
+ headers: {
83
+ Authorization: `Bearer ${this.cloudApiToken}`,
84
+ "Content-Type": "application/json",
85
+ },
86
+ body: JSON.stringify({
87
+ ...toCloudWorkspaceIdentity(workspaceId),
88
+ endpoint: request.endpoint,
89
+ method: request.method,
90
+ ...(request.data !== undefined ? { data: request.data } : {}),
91
+ ...(request.params !== undefined ? { params: request.params } : {}),
92
+ }),
93
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
94
+ });
95
+ }
96
+ catch (error) {
97
+ throw new CloudProxyError(error instanceof Error ? error.message : "Cloud Slack proxy request failed", { code: "network_error" });
98
+ }
99
+ if (!response.ok) {
100
+ throw new CloudProxyError(`Cloud Slack proxy request failed: ${response.status} ${response.statusText}`, {
101
+ code: "upstream_error",
102
+ status: response.status,
103
+ });
104
+ }
105
+ let payload;
106
+ try {
107
+ payload = parseEnvelope(await response.json());
108
+ }
109
+ catch (error) {
110
+ if (error instanceof CloudProxyError) {
111
+ throw error;
112
+ }
113
+ throw new CloudProxyError("Cloud Slack proxy returned invalid JSON");
114
+ }
115
+ if (!payload.ok) {
116
+ throw new CloudProxyError(payload.error ?? "Cloud Slack proxy returned an error", {
117
+ code: payload.code,
118
+ retryAfterMs: payload.retryAfterMs,
119
+ });
120
+ }
121
+ return {
122
+ status: response.status,
123
+ headers: {},
124
+ data: payload.data,
125
+ };
126
+ }
127
+ async healthCheck() {
128
+ try {
129
+ const response = await fetch(`${this.baseUrl}${HEALTH_ENDPOINT}`, {
130
+ method: "GET",
131
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
132
+ });
133
+ return response.ok;
134
+ }
135
+ catch {
136
+ return false;
137
+ }
138
+ }
139
+ }
@@ -0,0 +1,17 @@
1
+ export type FreshnessSource = "overlay" | "relayfile" | "proxy" | "cache" | "fallback";
2
+ export type EnvelopeStatus = "hit" | "miss" | "stale" | "error";
3
+ export interface FreshnessEnvelope<T> {
4
+ data: T | null;
5
+ status: EnvelopeStatus;
6
+ source: FreshnessSource;
7
+ asOf: number;
8
+ ageMs: number;
9
+ error?: string;
10
+ }
11
+ export declare const DEFAULT_FRESHNESS_MAX_AGE_MS = 60000;
12
+ export declare function hit<T>(data: T, source: FreshnessSource): FreshnessEnvelope<T>;
13
+ export declare function miss<T>(source: FreshnessSource): FreshnessEnvelope<T>;
14
+ export declare function stale<T>(data: T, source: FreshnessSource, ageMs: number): FreshnessEnvelope<T>;
15
+ export declare function error<T>(message: string, source: FreshnessSource): FreshnessEnvelope<T>;
16
+ export declare function isFresh<T>(envelope: FreshnessEnvelope<T>, maxAgeMs?: number): boolean;
17
+ //# sourceMappingURL=freshness-envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness-envelope.d.ts","sourceRoot":"","sources":["../../src/integrations/freshness-envelope.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,CAAC;AAEvF,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAEhE,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,eAAe,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,wBAAgB,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,eAAe,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAQ7E;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,eAAe,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAQrE;AAED,wBAAgB,KAAK,CAAC,CAAC,EACrB,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,MAAM,GACZ,iBAAiB,CAAC,CAAC,CAAC,CAQtB;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,iBAAiB,CAAC,CAAC,CAAC,CASvF;AAED,wBAAgB,OAAO,CAAC,CAAC,EACvB,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAC9B,QAAQ,SAA+B,GACtC,OAAO,CAET"}
@@ -0,0 +1,41 @@
1
+ export const DEFAULT_FRESHNESS_MAX_AGE_MS = 60_000;
2
+ export function hit(data, source) {
3
+ return {
4
+ data,
5
+ status: "hit",
6
+ source,
7
+ asOf: Date.now(),
8
+ ageMs: 0,
9
+ };
10
+ }
11
+ export function miss(source) {
12
+ return {
13
+ data: null,
14
+ status: "miss",
15
+ source,
16
+ asOf: Date.now(),
17
+ ageMs: 0,
18
+ };
19
+ }
20
+ export function stale(data, source, ageMs) {
21
+ return {
22
+ data,
23
+ status: "stale",
24
+ source,
25
+ asOf: Date.now() - ageMs,
26
+ ageMs,
27
+ };
28
+ }
29
+ export function error(message, source) {
30
+ return {
31
+ data: null,
32
+ status: "error",
33
+ source,
34
+ asOf: Date.now(),
35
+ ageMs: 0,
36
+ error: message,
37
+ };
38
+ }
39
+ export function isFresh(envelope, maxAgeMs = DEFAULT_FRESHNESS_MAX_AGE_MS) {
40
+ return envelope.status === "hit" && envelope.ageMs <= maxAgeMs;
41
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=freshness-envelope.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness-envelope.test.d.ts","sourceRoot":"","sources":["../../src/integrations/freshness-envelope.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,56 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { error, hit, isFresh, miss, stale } from "./freshness-envelope.js";
3
+ describe("freshness envelope", () => {
4
+ const now = new Date("2026-04-15T12:00:00.000Z");
5
+ beforeEach(() => {
6
+ vi.useFakeTimers();
7
+ vi.setSystemTime(now);
8
+ });
9
+ afterEach(() => {
10
+ vi.useRealTimers();
11
+ });
12
+ it("creates a hit envelope with data and source", () => {
13
+ const data = { workspaceId: "rw_cloudws1" };
14
+ expect(hit(data, "overlay")).toEqual({
15
+ data,
16
+ status: "hit",
17
+ source: "overlay",
18
+ asOf: now.getTime(),
19
+ ageMs: 0,
20
+ });
21
+ });
22
+ it("creates a miss envelope with null data", () => {
23
+ expect(miss("relayfile")).toEqual({
24
+ data: null,
25
+ status: "miss",
26
+ source: "relayfile",
27
+ asOf: now.getTime(),
28
+ ageMs: 0,
29
+ });
30
+ });
31
+ it("creates a stale envelope carrying ageMs", () => {
32
+ expect(stale({ count: 2 }, "cache", 5_000)).toEqual({
33
+ data: { count: 2 },
34
+ status: "stale",
35
+ source: "cache",
36
+ asOf: now.getTime() - 5_000,
37
+ ageMs: 5_000,
38
+ });
39
+ });
40
+ it("creates an error envelope carrying the error string", () => {
41
+ expect(error("proxy unavailable", "proxy")).toEqual({
42
+ data: null,
43
+ status: "error",
44
+ source: "proxy",
45
+ asOf: now.getTime(),
46
+ ageMs: 0,
47
+ error: "proxy unavailable",
48
+ });
49
+ });
50
+ it("checks freshness against a strict max age", () => {
51
+ const envelope = hit({ id: "fresh-context" }, "cache");
52
+ expect(isFresh({ ...envelope, ageMs: 99 }, 100)).toBe(true);
53
+ expect(isFresh({ ...envelope, ageMs: 100 }, 100)).toBe(false);
54
+ expect(isFresh({ ...envelope, ageMs: 101 }, 100)).toBe(false);
55
+ });
56
+ });
@@ -6,10 +6,11 @@ export interface GitHubContextProviderOptions {
6
6
  }
7
7
  export declare class GitHubContextProvider {
8
8
  private readonly nangoClient;
9
+ private readonly providerConfigKey;
9
10
  private readonly cache;
10
11
  private readonly cacheTtlMs;
11
12
  private readonly logger;
12
- constructor(nangoClient: NangoClient, options?: GitHubContextProviderOptions);
13
+ constructor(nangoClient: NangoClient, providerConfigKey: string, options?: GitHubContextProviderOptions);
13
14
  getOrgsAndRepos(connectionId: string, relayfileReader?: SageRelayFileReader | null): Promise<GitHubOrgDiscovery>;
14
15
  private setCache;
15
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"github-context.d.ts","sourceRoot":"","sources":["../../src/integrations/github-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AASjE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,qBAAa,qBAAqB;IAM9B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAL9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;gBAG5B,WAAW,EAAE,WAAW,EACzC,OAAO,GAAE,4BAAiC;IAMtC,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,eAAe,CAAC,EAAE,mBAAmB,GAAG,IAAI,GAC3C,OAAO,CAAC,kBAAkB,CAAC;IAwB9B,OAAO,CAAC,QAAQ;CAMjB"}
1
+ {"version":3,"file":"github-context.d.ts","sourceRoot":"","sources":["../../src/integrations/github-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AASjE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,qBAAa,qBAAqB;IAM9B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IANpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;gBAG5B,WAAW,EAAE,WAAW,EACxB,iBAAiB,EAAE,MAAM,EAC1C,OAAO,GAAE,4BAAiC;IAMtC,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,eAAe,CAAC,EAAE,mBAAmB,GAAG,IAAI,GAC3C,OAAO,CAAC,kBAAkB,CAAC;IAwB9B,OAAO,CAAC,QAAQ;CAMjB"}
@@ -1,11 +1,13 @@
1
1
  const DEFAULT_CACHE_TTL_MS = 5 * 60_000;
2
2
  export class GitHubContextProvider {
3
3
  nangoClient;
4
+ providerConfigKey;
4
5
  cache = new Map();
5
6
  cacheTtlMs;
6
7
  logger;
7
- constructor(nangoClient, options = {}) {
8
+ constructor(nangoClient, providerConfigKey, options = {}) {
8
9
  this.nangoClient = nangoClient;
10
+ this.providerConfigKey = providerConfigKey;
9
11
  this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
10
12
  this.logger = options.logger ?? console;
11
13
  }
@@ -27,7 +29,7 @@ export class GitHubContextProvider {
27
29
  this.logger.warn('[sage] RelayFile GitHub discovery failed, falling back to Nango', error);
28
30
  }
29
31
  }
30
- const discovery = await this.nangoClient.discoverGitHubOrgs(connectionId);
32
+ const discovery = await this.nangoClient.discoverGitHubOrgs(connectionId, this.providerConfigKey);
31
33
  this.setCache(connectionId, discovery);
32
34
  return cloneGitHubOrgDiscovery(discovery);
33
35
  }
@@ -3,16 +3,18 @@
3
3
  *
4
4
  * All GitHub API calls are routed through Nango's proxy so that
5
5
  * authentication is handled automatically via the stored connection.
6
- * Uses the 'github' integration key for repo access.
6
+ * Uses the workspace-resolved provider config key for repo access.
7
7
  */
8
8
  import { NangoClient } from '../nango.js';
9
9
  import type { IntegrationResult, GitHubFile, GitHubIssue, GitHubPR, GitHubSearchResult } from './types.js';
10
10
  export declare class GitHubIntegration {
11
11
  private readonly nango;
12
12
  private readonly connectionId;
13
- constructor({ nangoClient, connectionId, }: {
13
+ private readonly providerConfigKey;
14
+ constructor({ nangoClient, connectionId, providerConfigKey, }: {
14
15
  nangoClient: NangoClient;
15
16
  connectionId: string;
17
+ providerConfigKey?: string | null;
16
18
  });
17
19
  private errorResult;
18
20
  private execute;
@@ -1 +1 @@
1
- {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/integrations/github.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAkC,MAAM,aAAa,CAAC;AAC1E,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,kBAAkB,EACnB,MAAM,YAAY,CAAC;AA6EpB,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAE1B,EACV,WAAW,EACX,YAAY,GACb,EAAE;QACD,WAAW,EAAE,WAAW,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;KACtB;IAKD,OAAO,CAAC,WAAW;YAOL,OAAO;IAcf,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;IAmCxF,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,WAAW,EAAE,CAAC,CAAC;IAmCrF,QAAQ,CACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAmCnC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAuC7F,aAAa,CACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,iBAAiB,CAAC,MAAM,EAAE,CAAC,CAAC;IAmBjC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;CA+BrG"}
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/integrations/github.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAc,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,kBAAkB,EACnB,MAAM,YAAY,CAAC;AA4EpB,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;gBAE/B,EACV,WAAW,EACX,YAAY,EACZ,iBAAiB,GAClB,EAAE;QACD,WAAW,EAAE,WAAW,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACnC;IAWD,OAAO,CAAC,WAAW;YAOL,OAAO;IAcf,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;IAmCxF,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,WAAW,EAAE,CAAC,CAAC;IAmCrF,QAAQ,CACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAmCnC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAuC7F,aAAa,CACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,iBAAiB,CAAC,MAAM,EAAE,CAAC,CAAC;IAmBjC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;CA+BrG"}
@@ -3,11 +3,10 @@
3
3
  *
4
4
  * All GitHub API calls are routed through Nango's proxy so that
5
5
  * authentication is handled automatically via the stored connection.
6
- * Uses the 'github' integration key for repo access.
6
+ * Uses the workspace-resolved provider config key for repo access.
7
7
  */
8
- import { NangoError, NANGO_INTEGRATIONS } from '../nango.js';
8
+ import { NangoError } from '../nango.js';
9
9
  const SOURCE = 'github';
10
- const PROVIDER = NANGO_INTEGRATIONS.GITHUB;
11
10
  function result(data, summary) {
12
11
  return {
13
12
  source: SOURCE,
@@ -64,9 +63,15 @@ function encodeRepoPath(path) {
64
63
  export class GitHubIntegration {
65
64
  nango;
66
65
  connectionId;
67
- constructor({ nangoClient, connectionId, }) {
66
+ providerConfigKey;
67
+ constructor({ nangoClient, connectionId, providerConfigKey, }) {
68
+ const resolvedProviderConfigKey = providerConfigKey?.trim();
69
+ if (!resolvedProviderConfigKey) {
70
+ throw new Error('GitHubIntegration requires a providerConfigKey');
71
+ }
68
72
  this.nango = nangoClient;
69
73
  this.connectionId = connectionId;
74
+ this.providerConfigKey = resolvedProviderConfigKey;
70
75
  }
71
76
  errorResult(action, fallback, error) {
72
77
  const prefix = isRateLimited(error)
@@ -88,7 +93,7 @@ export class GitHubIntegration {
88
93
  return this.execute(`searching code for "${query}"${repo ? ` in ${repo}` : ''}`, { items: [] }, async () => {
89
94
  const data = await this.nango.proxy({
90
95
  connectionId: this.connectionId,
91
- providerConfigKey: PROVIDER,
96
+ providerConfigKey: this.providerConfigKey,
92
97
  method: 'GET',
93
98
  endpoint: `/search/code?q=${encodeURIComponent(scopedQuery)}`,
94
99
  headers: { Accept: 'application/vnd.github.text-match+json' },
@@ -108,7 +113,7 @@ export class GitHubIntegration {
108
113
  return this.execute(`searching issues for "${query}"${repo ? ` in ${repo}` : ''}`, [], async () => {
109
114
  const data = await this.nango.proxy({
110
115
  connectionId: this.connectionId,
111
- providerConfigKey: PROVIDER,
116
+ providerConfigKey: this.providerConfigKey,
112
117
  method: 'GET',
113
118
  endpoint: `/search/issues?q=${encodeURIComponent(scopedQuery)}`,
114
119
  });
@@ -130,7 +135,7 @@ export class GitHubIntegration {
130
135
  return this.execute(`reading file ${owner}/${repo}/${path}${ref ? ` @ ${ref}` : ''}`, { path, content: '', sha: '' }, async () => {
131
136
  const data = await this.nango.proxy({
132
137
  connectionId: this.connectionId,
133
- providerConfigKey: PROVIDER,
138
+ providerConfigKey: this.providerConfigKey,
134
139
  method: 'GET',
135
140
  endpoint,
136
141
  });
@@ -148,13 +153,13 @@ export class GitHubIntegration {
148
153
  const [pr, files] = await Promise.all([
149
154
  this.nango.proxy({
150
155
  connectionId: this.connectionId,
151
- providerConfigKey: PROVIDER,
156
+ providerConfigKey: this.providerConfigKey,
152
157
  method: 'GET',
153
158
  endpoint: `/repos/${owner}/${repo}/pulls/${number}`,
154
159
  }),
155
160
  this.nango.proxy({
156
161
  connectionId: this.connectionId,
157
- providerConfigKey: PROVIDER,
162
+ providerConfigKey: this.providerConfigKey,
158
163
  method: 'GET',
159
164
  endpoint: `/repos/${owner}/${repo}/pulls/${number}/files`,
160
165
  }),
@@ -174,7 +179,7 @@ export class GitHubIntegration {
174
179
  return this.execute(`listing repository contents for ${owner}/${repo}${path ? `/${path}` : ''}`, [], async () => {
175
180
  const data = await this.nango.proxy({
176
181
  connectionId: this.connectionId,
177
- providerConfigKey: PROVIDER,
182
+ providerConfigKey: this.providerConfigKey,
178
183
  method: 'GET',
179
184
  endpoint: `/repos/${owner}/${repo}/contents${encodedPath}`,
180
185
  });
@@ -185,7 +190,7 @@ export class GitHubIntegration {
185
190
  return this.execute(`loading issue #${number} in ${owner}/${repo}`, { number, title: '', body: '', state: 'error', labels: [], url: '' }, async () => {
186
191
  const data = await this.nango.proxy({
187
192
  connectionId: this.connectionId,
188
- providerConfigKey: PROVIDER,
193
+ providerConfigKey: this.providerConfigKey,
189
194
  method: 'GET',
190
195
  endpoint: `/repos/${owner}/${repo}/issues/${number}`,
191
196
  });
@@ -0,0 +1,31 @@
1
+ export declare const DEFAULT_OVERLAY_TTL_MS = 120000;
2
+ export declare const DEFAULT_OVERLAY_MAX_ENTRIES = 500;
3
+ export interface OverlayEntry {
4
+ path: string;
5
+ data: Record<string, unknown>;
6
+ writtenAt: number;
7
+ provider: string;
8
+ action: string;
9
+ }
10
+ export interface RecentActionsOverlayOptions {
11
+ ttlMs?: number;
12
+ maxEntries?: number;
13
+ }
14
+ export declare class RecentActionsOverlay {
15
+ private readonly ttlMs;
16
+ private readonly maxEntries;
17
+ private readonly entries;
18
+ constructor(options?: RecentActionsOverlayOptions);
19
+ record(entry: Omit<OverlayEntry, "writtenAt">): void;
20
+ lookup(path: string): OverlayEntry | null;
21
+ search(query: {
22
+ provider?: string;
23
+ pathPrefix?: string;
24
+ since?: number;
25
+ }): OverlayEntry[];
26
+ clear(): void;
27
+ get size(): number;
28
+ private isExpired;
29
+ private evictOldestEntries;
30
+ }
31
+ //# sourceMappingURL=recent-actions-overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recent-actions-overlay.d.ts","sourceRoot":"","sources":["../../src/integrations/recent-actions-overlay.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,SAAU,CAAC;AAC9C,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;gBAE/C,OAAO,CAAC,EAAE,2BAA2B;IAKjD,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,GAAG,IAAI;IAcpD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAgBzC,MAAM,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,YAAY,EAAE;IA2BzF,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI,MAAM,CAWjB;IAED,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,kBAAkB;CAU3B"}
@@ -0,0 +1,80 @@
1
+ export const DEFAULT_OVERLAY_TTL_MS = 120_000;
2
+ export const DEFAULT_OVERLAY_MAX_ENTRIES = 500;
3
+ export class RecentActionsOverlay {
4
+ ttlMs;
5
+ maxEntries;
6
+ entries = new Map();
7
+ constructor(options) {
8
+ this.ttlMs = options?.ttlMs ?? DEFAULT_OVERLAY_TTL_MS;
9
+ this.maxEntries = options?.maxEntries ?? DEFAULT_OVERLAY_MAX_ENTRIES;
10
+ }
11
+ record(entry) {
12
+ const overlayEntry = {
13
+ ...entry,
14
+ writtenAt: Date.now(),
15
+ };
16
+ if (this.entries.has(entry.path)) {
17
+ this.entries.delete(entry.path);
18
+ }
19
+ this.entries.set(entry.path, overlayEntry);
20
+ this.evictOldestEntries();
21
+ }
22
+ lookup(path) {
23
+ const entry = this.entries.get(path);
24
+ if (!entry) {
25
+ return null;
26
+ }
27
+ if (this.isExpired(entry, Date.now())) {
28
+ this.entries.delete(path);
29
+ return null;
30
+ }
31
+ this.entries.delete(path);
32
+ this.entries.set(path, entry);
33
+ return entry;
34
+ }
35
+ search(query) {
36
+ const now = Date.now();
37
+ const results = [];
38
+ for (const entry of this.entries.values()) {
39
+ if (this.isExpired(entry, now)) {
40
+ continue;
41
+ }
42
+ if (query.provider !== undefined && entry.provider !== query.provider) {
43
+ continue;
44
+ }
45
+ if (query.pathPrefix !== undefined && !entry.path.startsWith(query.pathPrefix)) {
46
+ continue;
47
+ }
48
+ if (query.since !== undefined && entry.writtenAt < query.since) {
49
+ continue;
50
+ }
51
+ results.push(entry);
52
+ }
53
+ return results.sort((left, right) => right.writtenAt - left.writtenAt);
54
+ }
55
+ clear() {
56
+ this.entries.clear();
57
+ }
58
+ get size() {
59
+ const now = Date.now();
60
+ let count = 0;
61
+ for (const entry of this.entries.values()) {
62
+ if (!this.isExpired(entry, now)) {
63
+ count += 1;
64
+ }
65
+ }
66
+ return count;
67
+ }
68
+ isExpired(entry, now) {
69
+ return now - entry.writtenAt >= this.ttlMs;
70
+ }
71
+ evictOldestEntries() {
72
+ while (this.entries.size > this.maxEntries) {
73
+ const oldestKey = this.entries.keys().next().value;
74
+ if (oldestKey === undefined) {
75
+ return;
76
+ }
77
+ this.entries.delete(oldestKey);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=recent-actions-overlay.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recent-actions-overlay.test.d.ts","sourceRoot":"","sources":["../../src/integrations/recent-actions-overlay.test.ts"],"names":[],"mappings":""}