@pinet/slack-bridge 0.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 (179) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +570 -0
  3. package/dist/activity-log.d.ts +62 -0
  4. package/dist/activity-log.js +293 -0
  5. package/dist/agent-completion-runtime.d.ts +17 -0
  6. package/dist/agent-completion-runtime.js +26 -0
  7. package/dist/agent-event-runtime.d.ts +13 -0
  8. package/dist/agent-event-runtime.js +27 -0
  9. package/dist/agent-prompt-guidance.d.ts +22 -0
  10. package/dist/agent-prompt-guidance.js +46 -0
  11. package/dist/broker/adapters/slack.d.ts +74 -0
  12. package/dist/broker/adapters/slack.js +563 -0
  13. package/dist/broker/adapters/types.d.ts +1 -0
  14. package/dist/broker/adapters/types.js +1 -0
  15. package/dist/broker/agent-messaging.d.ts +1 -0
  16. package/dist/broker/agent-messaging.js +1 -0
  17. package/dist/broker/auth.d.ts +1 -0
  18. package/dist/broker/auth.js +1 -0
  19. package/dist/broker/client.d.ts +167 -0
  20. package/dist/broker/client.js +624 -0
  21. package/dist/broker/control-plane-dashboard.d.ts +98 -0
  22. package/dist/broker/control-plane-dashboard.js +213 -0
  23. package/dist/broker/ghost-reaper.d.ts +49 -0
  24. package/dist/broker/ghost-reaper.js +209 -0
  25. package/dist/broker/index.d.ts +36 -0
  26. package/dist/broker/index.js +90 -0
  27. package/dist/broker/leader.d.ts +1 -0
  28. package/dist/broker/leader.js +1 -0
  29. package/dist/broker/maintenance.d.ts +1 -0
  30. package/dist/broker/maintenance.js +1 -0
  31. package/dist/broker/message-send.d.ts +1 -0
  32. package/dist/broker/message-send.js +1 -0
  33. package/dist/broker/paths.d.ts +1 -0
  34. package/dist/broker/paths.js +1 -0
  35. package/dist/broker/raw-tcp-loopback.d.ts +1 -0
  36. package/dist/broker/raw-tcp-loopback.js +1 -0
  37. package/dist/broker/router.d.ts +1 -0
  38. package/dist/broker/router.js +1 -0
  39. package/dist/broker/schema.d.ts +9 -0
  40. package/dist/broker/schema.js +63 -0
  41. package/dist/broker/socket-server.d.ts +120 -0
  42. package/dist/broker/socket-server.js +1087 -0
  43. package/dist/broker/types.d.ts +1 -0
  44. package/dist/broker/types.js +1 -0
  45. package/dist/broker-delivery.d.ts +10 -0
  46. package/dist/broker-delivery.js +26 -0
  47. package/dist/broker-inbound-persistence.d.ts +11 -0
  48. package/dist/broker-inbound-persistence.js +22 -0
  49. package/dist/broker-prompt-loader.d.ts +34 -0
  50. package/dist/broker-prompt-loader.js +185 -0
  51. package/dist/broker-runtime-access.d.ts +14 -0
  52. package/dist/broker-runtime-access.js +20 -0
  53. package/dist/broker-runtime.d.ts +100 -0
  54. package/dist/broker-runtime.js +533 -0
  55. package/dist/broker-thread-owner-hints.d.ts +10 -0
  56. package/dist/broker-thread-owner-hints.js +14 -0
  57. package/dist/canvases.d.ts +80 -0
  58. package/dist/canvases.js +221 -0
  59. package/dist/command-registration-runtime.d.ts +9 -0
  60. package/dist/command-registration-runtime.js +9 -0
  61. package/dist/core-tool-guardrails.d.ts +24 -0
  62. package/dist/core-tool-guardrails.js +66 -0
  63. package/dist/deploy-manifest.d.ts +27 -0
  64. package/dist/deploy-manifest.js +197 -0
  65. package/dist/follower-delivery.d.ts +16 -0
  66. package/dist/follower-delivery.js +70 -0
  67. package/dist/follower-runtime.d.ts +61 -0
  68. package/dist/follower-runtime.js +350 -0
  69. package/dist/git-metadata.d.ts +35 -0
  70. package/dist/git-metadata.js +88 -0
  71. package/dist/guardrails.d.ts +69 -0
  72. package/dist/guardrails.js +283 -0
  73. package/dist/helpers.d.ts +550 -0
  74. package/dist/helpers.js +2711 -0
  75. package/dist/home-tab.d.ts +27 -0
  76. package/dist/home-tab.js +242 -0
  77. package/dist/imessage-tools.d.ts +33 -0
  78. package/dist/imessage-tools.js +96 -0
  79. package/dist/inbox-drain-runtime.d.ts +30 -0
  80. package/dist/inbox-drain-runtime.js +71 -0
  81. package/dist/index.d.ts +2 -0
  82. package/dist/index.js +1330 -0
  83. package/dist/persisted-runtime-state.d.ts +52 -0
  84. package/dist/persisted-runtime-state.js +98 -0
  85. package/dist/pinet-activity-formatting.d.ts +20 -0
  86. package/dist/pinet-activity-formatting.js +43 -0
  87. package/dist/pinet-agent-status.d.ts +36 -0
  88. package/dist/pinet-agent-status.js +48 -0
  89. package/dist/pinet-commands.d.ts +70 -0
  90. package/dist/pinet-commands.js +402 -0
  91. package/dist/pinet-confirmation-replies.d.ts +7 -0
  92. package/dist/pinet-confirmation-replies.js +30 -0
  93. package/dist/pinet-control-plane-dashboard.d.ts +34 -0
  94. package/dist/pinet-control-plane-dashboard.js +86 -0
  95. package/dist/pinet-home-tabs.d.ts +36 -0
  96. package/dist/pinet-home-tabs.js +101 -0
  97. package/dist/pinet-maintenance-delivery.d.ts +22 -0
  98. package/dist/pinet-maintenance-delivery.js +45 -0
  99. package/dist/pinet-mesh-ops.d.ts +97 -0
  100. package/dist/pinet-mesh-ops.js +266 -0
  101. package/dist/pinet-registration-gate.d.ts +14 -0
  102. package/dist/pinet-registration-gate.js +42 -0
  103. package/dist/pinet-remote-control-acks.d.ts +16 -0
  104. package/dist/pinet-remote-control-acks.js +46 -0
  105. package/dist/pinet-remote-control.d.ts +17 -0
  106. package/dist/pinet-remote-control.js +74 -0
  107. package/dist/pinet-runtime-composition.d.ts +27 -0
  108. package/dist/pinet-runtime-composition.js +20 -0
  109. package/dist/pinet-tools.d.ts +67 -0
  110. package/dist/pinet-tools.js +1211 -0
  111. package/dist/prompts/broker/default.md +41 -0
  112. package/dist/prompts/broker/tmux.md +49 -0
  113. package/dist/ralph-loop.d.ts +76 -0
  114. package/dist/ralph-loop.js +524 -0
  115. package/dist/reaction-triggers.d.ts +25 -0
  116. package/dist/reaction-triggers.js +171 -0
  117. package/dist/repo-tool-guardrails.d.ts +20 -0
  118. package/dist/repo-tool-guardrails.js +81 -0
  119. package/dist/runtime-agent-context.d.ts +93 -0
  120. package/dist/runtime-agent-context.js +302 -0
  121. package/dist/runtime-mode.d.ts +10 -0
  122. package/dist/runtime-mode.js +39 -0
  123. package/dist/scheduled-wakeups.d.ts +1 -0
  124. package/dist/scheduled-wakeups.js +1 -0
  125. package/dist/session-ui-runtime.d.ts +20 -0
  126. package/dist/session-ui-runtime.js +163 -0
  127. package/dist/single-player-runtime.d.ts +71 -0
  128. package/dist/single-player-runtime.js +436 -0
  129. package/dist/skins/cosmere.json +1571 -0
  130. package/dist/skins/foundation.json +1304 -0
  131. package/dist/slack-access.d.ts +186 -0
  132. package/dist/slack-access.js +462 -0
  133. package/dist/slack-api.d.ts +2 -0
  134. package/dist/slack-api.js +1 -0
  135. package/dist/slack-block-kit.d.ts +36 -0
  136. package/dist/slack-block-kit.js +242 -0
  137. package/dist/slack-export.d.ts +31 -0
  138. package/dist/slack-export.js +206 -0
  139. package/dist/slack-message-context.d.ts +15 -0
  140. package/dist/slack-message-context.js +207 -0
  141. package/dist/slack-modals.d.ts +13 -0
  142. package/dist/slack-modals.js +79 -0
  143. package/dist/slack-pinet-runtime-adapter.d.ts +16 -0
  144. package/dist/slack-pinet-runtime-adapter.js +72 -0
  145. package/dist/slack-presence.d.ts +36 -0
  146. package/dist/slack-presence.js +89 -0
  147. package/dist/slack-request-runtime.d.ts +8 -0
  148. package/dist/slack-request-runtime.js +19 -0
  149. package/dist/slack-runtime-access.d.ts +28 -0
  150. package/dist/slack-runtime-access.js +107 -0
  151. package/dist/slack-scope-diagnostics.d.ts +31 -0
  152. package/dist/slack-scope-diagnostics.js +235 -0
  153. package/dist/slack-socket-dedup.d.ts +7 -0
  154. package/dist/slack-socket-dedup.js +135 -0
  155. package/dist/slack-thread-status.d.ts +41 -0
  156. package/dist/slack-thread-status.js +156 -0
  157. package/dist/slack-tool-policy-runtime.d.ts +36 -0
  158. package/dist/slack-tool-policy-runtime.js +96 -0
  159. package/dist/slack-tools.d.ts +52 -0
  160. package/dist/slack-tools.js +2688 -0
  161. package/dist/slack-turn-guardrails.d.ts +19 -0
  162. package/dist/slack-turn-guardrails.js +36 -0
  163. package/dist/slack-upload.d.ts +49 -0
  164. package/dist/slack-upload.js +224 -0
  165. package/dist/task-assignments.d.ts +45 -0
  166. package/dist/task-assignments.js +527 -0
  167. package/dist/thread-confirmations.d.ts +22 -0
  168. package/dist/thread-confirmations.js +139 -0
  169. package/dist/tool-registration-runtime.d.ts +13 -0
  170. package/dist/tool-registration-runtime.js +13 -0
  171. package/dist/ttl-cache.d.ts +47 -0
  172. package/dist/ttl-cache.js +105 -0
  173. package/manifest.yaml +57 -0
  174. package/package.json +60 -0
  175. package/skills/pinet-skin-creator/SKILL.md +109 -0
  176. package/skills/pinet-skin-creator/references/descriptor-format.md +99 -0
  177. package/skills/pinet-skin-creator/references/safety-checklist.md +56 -0
  178. package/skills/pinet-skin-creator/templates/pinet-skin-descriptor.json +77 -0
  179. package/skills/slack-bridge/SKILL.md +360 -0
@@ -0,0 +1,13 @@
1
+ export type SlackModalView = Record<string, unknown>;
2
+ export declare function normalizeSlackModalViewInput(view: unknown): SlackModalView;
3
+ export interface SlackModalThreadContext {
4
+ threadTs: string;
5
+ channel: string;
6
+ }
7
+ export interface DecodedSlackModalPrivateMetadata {
8
+ raw: string | null;
9
+ value: unknown;
10
+ threadContext: SlackModalThreadContext | null;
11
+ }
12
+ export declare function encodeSlackModalPrivateMetadata(privateMetadata: string | undefined, threadContext: SlackModalThreadContext | null): string | undefined;
13
+ export declare function decodeSlackModalPrivateMetadata(privateMetadata: string | undefined): DecodedSlackModalPrivateMetadata;
@@ -0,0 +1,79 @@
1
+ const PI_SLACK_MODAL_CONTEXT_KEY = "__piSlackModalContext";
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function asString(value) {
6
+ return typeof value === "string" && value.length > 0 ? value : undefined;
7
+ }
8
+ function asOptionalString(value) {
9
+ return typeof value === "string" ? value : null;
10
+ }
11
+ export function normalizeSlackModalViewInput(view) {
12
+ if (!isRecord(view)) {
13
+ throw new Error("Slack modal view must be a JSON object.");
14
+ }
15
+ const cloned = structuredClone(view);
16
+ if (cloned.type !== "modal") {
17
+ throw new Error('Slack modal view.type must be "modal".');
18
+ }
19
+ return cloned;
20
+ }
21
+ export function encodeSlackModalPrivateMetadata(privateMetadata, threadContext) {
22
+ if (!threadContext) {
23
+ return privateMetadata;
24
+ }
25
+ const raw = privateMetadata?.trim();
26
+ let value = raw ?? null;
27
+ if (raw) {
28
+ try {
29
+ value = JSON.parse(raw);
30
+ }
31
+ catch {
32
+ value = raw;
33
+ }
34
+ }
35
+ if (isRecord(value)) {
36
+ return JSON.stringify({
37
+ ...value,
38
+ [PI_SLACK_MODAL_CONTEXT_KEY]: threadContext,
39
+ });
40
+ }
41
+ return JSON.stringify({
42
+ [PI_SLACK_MODAL_CONTEXT_KEY]: threadContext,
43
+ value,
44
+ });
45
+ }
46
+ export function decodeSlackModalPrivateMetadata(privateMetadata) {
47
+ const raw = asOptionalString(privateMetadata);
48
+ if (!raw) {
49
+ return { raw, value: null, threadContext: null };
50
+ }
51
+ try {
52
+ const parsed = JSON.parse(raw);
53
+ if (isRecord(parsed)) {
54
+ const context = parsed[PI_SLACK_MODAL_CONTEXT_KEY];
55
+ const threadContext = isRecord(context)
56
+ ? {
57
+ threadTs: asString(context.threadTs) ?? "",
58
+ channel: asString(context.channel) ?? "",
59
+ }
60
+ : null;
61
+ const normalizedContext = threadContext && threadContext.threadTs && threadContext.channel ? threadContext : null;
62
+ if (PI_SLACK_MODAL_CONTEXT_KEY in parsed) {
63
+ const clone = { ...parsed };
64
+ delete clone[PI_SLACK_MODAL_CONTEXT_KEY];
65
+ const userValue = Object.keys(clone).length === 1 && "value" in clone ? clone.value : clone;
66
+ return {
67
+ raw,
68
+ value: userValue,
69
+ threadContext: normalizedContext,
70
+ };
71
+ }
72
+ return { raw, value: parsed, threadContext: normalizedContext };
73
+ }
74
+ return { raw, value: parsed, threadContext: null };
75
+ }
76
+ catch {
77
+ return { raw, value: raw, threadContext: null };
78
+ }
79
+ }
@@ -0,0 +1,16 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import type { ThreadInfo } from "./broker/index.js";
3
+ import type { PinetRuntimeAdapterFactory } from "./pinet-runtime-composition.js";
4
+ import type { SlackThreadContext } from "./slack-access.js";
5
+ import type { SlackBridgeSettings } from "./helpers.js";
6
+ export declare function readStoredSlackThreadContext(metadata: Record<string, unknown> | null | undefined): SlackThreadContext | null;
7
+ export declare function shouldRouteKnownSlackThread(thread: Pick<ThreadInfo, "source" | "channel" | "metadata"> | null): boolean;
8
+ export interface SlackPinetRuntimeAdapterDeps {
9
+ getSettings: () => SlackBridgeSettings;
10
+ getBotToken: () => string;
11
+ getAppToken: () => string;
12
+ getAllowedUsers: () => Set<string> | null;
13
+ shouldAllowAllWorkspaceUsers: () => boolean;
14
+ onAppHomeOpened: (userId: string, ctx: ExtensionContext) => Promise<void> | void;
15
+ }
16
+ export declare function createSlackPinetRuntimeAdapterFactory(deps: SlackPinetRuntimeAdapterDeps): PinetRuntimeAdapterFactory;
@@ -0,0 +1,72 @@
1
+ import { SlackAdapter } from "./broker/adapters/slack.js";
2
+ export function readStoredSlackThreadContext(metadata) {
3
+ const value = metadata?.slackThreadContext;
4
+ if (typeof value !== "object" || value === null || Array.isArray(value))
5
+ return null;
6
+ const record = value;
7
+ if (typeof record.channelId !== "string" || record.channelId.length === 0)
8
+ return null;
9
+ if (typeof record.scope !== "object" || record.scope === null || Array.isArray(record.scope)) {
10
+ return null;
11
+ }
12
+ return {
13
+ channelId: record.channelId,
14
+ ...(typeof record.teamId === "string" && record.teamId.length > 0
15
+ ? { teamId: record.teamId }
16
+ : {}),
17
+ scope: record.scope,
18
+ };
19
+ }
20
+ export function shouldRouteKnownSlackThread(thread) {
21
+ if (!thread || thread.source !== "slack")
22
+ return false;
23
+ if (!thread.channel.startsWith("D"))
24
+ return true;
25
+ return readStoredSlackThreadContext(thread.metadata) !== null;
26
+ }
27
+ function getKnownSlackThread(broker, threadTs) {
28
+ const thread = broker.db.getThread(threadTs);
29
+ if (!thread || thread.source !== "slack")
30
+ return null;
31
+ return {
32
+ channelId: thread.channel,
33
+ context: readStoredSlackThreadContext(thread.metadata),
34
+ };
35
+ }
36
+ function rememberKnownSlackThread(broker, threadTs, channelId, context) {
37
+ const existingMetadata = broker.db.getThread(threadTs)?.metadata ?? {};
38
+ broker.db.updateThread(threadTs, {
39
+ source: "slack",
40
+ channel: channelId,
41
+ metadata: {
42
+ ...existingMetadata,
43
+ ...(context ? { slackThreadContext: context } : {}),
44
+ },
45
+ });
46
+ }
47
+ export function createSlackPinetRuntimeAdapterFactory(deps) {
48
+ return ({ broker, ctx }) => {
49
+ const settings = deps.getSettings();
50
+ const allowedUsers = deps.getAllowedUsers();
51
+ const adapter = new SlackAdapter({
52
+ botToken: deps.getBotToken(),
53
+ appToken: deps.getAppToken(),
54
+ allowedUsers: allowedUsers ? [...allowedUsers] : undefined,
55
+ allowAllWorkspaceUsers: deps.shouldAllowAllWorkspaceUsers(),
56
+ suggestedPrompts: settings.suggestedPrompts,
57
+ reactionCommands: settings.reactionCommands,
58
+ isKnownThread: (threadTs) => shouldRouteKnownSlackThread(broker.db.getThread(threadTs)),
59
+ getKnownThread: (threadTs) => getKnownSlackThread(broker, threadTs),
60
+ rememberKnownThread: (threadTs, channelId, context) => {
61
+ rememberKnownSlackThread(broker, threadTs, channelId, context);
62
+ },
63
+ onAppHomeOpened: async ({ userId }) => {
64
+ await deps.onAppHomeOpened(userId, ctx);
65
+ },
66
+ });
67
+ return {
68
+ adapter,
69
+ getBotUserId: () => adapter.getBotUserId(),
70
+ };
71
+ };
72
+ }
@@ -0,0 +1,36 @@
1
+ export interface SlackPresenceDirectoryUser {
2
+ id?: string;
3
+ name?: string;
4
+ real_name?: string;
5
+ deleted?: boolean;
6
+ profile?: {
7
+ display_name?: string;
8
+ real_name?: string;
9
+ };
10
+ }
11
+ export interface SlackPresenceSnapshot {
12
+ userId: string;
13
+ userName: string;
14
+ presence: string;
15
+ dndEnabled: boolean;
16
+ dndEndTs?: number;
17
+ dndEndAt?: string;
18
+ autoAway?: boolean;
19
+ manualAway?: boolean;
20
+ connectionCount?: number;
21
+ lastActivity?: number;
22
+ online?: boolean;
23
+ }
24
+ export interface SlackDndInfoLike {
25
+ dnd_enabled?: unknown;
26
+ next_dnd_end_ts?: unknown;
27
+ snooze_enabled?: unknown;
28
+ snooze_endtime?: unknown;
29
+ }
30
+ export declare function stripSlackUserReference(value: string): string;
31
+ export declare function isSlackUserId(value: string): boolean;
32
+ export declare function getBestSlackPresenceUserName(user: SlackPresenceDirectoryUser): string;
33
+ export declare function findSlackPresenceDirectoryUser(users: SlackPresenceDirectoryUser[], identifier: string): SlackPresenceDirectoryUser | null;
34
+ export declare function resolveSlackPresenceDndEndTs(dndInfo: SlackDndInfoLike): number | undefined;
35
+ export declare function formatSlackPresenceTimestamp(ts: number | undefined): string | undefined;
36
+ export declare function formatSlackPresenceLine(snapshot: SlackPresenceSnapshot): string;
@@ -0,0 +1,89 @@
1
+ export function stripSlackUserReference(value) {
2
+ const trimmed = value.trim();
3
+ const mentionMatch = trimmed.match(/^<@([A-Z0-9]+)>$/i);
4
+ if (mentionMatch?.[1]) {
5
+ return mentionMatch[1];
6
+ }
7
+ return trimmed.replace(/^@/, "").trim();
8
+ }
9
+ export function isSlackUserId(value) {
10
+ return /^[UW][A-Z0-9]{2,}$/i.test(stripSlackUserReference(value));
11
+ }
12
+ export function getBestSlackPresenceUserName(user) {
13
+ const displayName = user.profile?.display_name?.trim();
14
+ if (displayName)
15
+ return displayName;
16
+ const realName = user.real_name?.trim() ?? user.profile?.real_name?.trim();
17
+ if (realName)
18
+ return realName;
19
+ const handle = user.name?.trim();
20
+ if (handle)
21
+ return handle;
22
+ return user.id?.trim() || "unknown-user";
23
+ }
24
+ function normalizeLookupValue(value) {
25
+ return stripSlackUserReference(value).trim().toLowerCase();
26
+ }
27
+ export function findSlackPresenceDirectoryUser(users, identifier) {
28
+ const normalized = normalizeLookupValue(identifier);
29
+ if (!normalized)
30
+ return null;
31
+ for (const user of users) {
32
+ if (user.deleted)
33
+ continue;
34
+ const candidates = [
35
+ user.id,
36
+ user.name,
37
+ user.real_name,
38
+ user.profile?.display_name,
39
+ user.profile?.real_name,
40
+ ]
41
+ .map((value) => value?.trim())
42
+ .filter((value) => Boolean(value))
43
+ .map((value) => value.toLowerCase());
44
+ if (candidates.includes(normalized)) {
45
+ return user;
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+ function parsePositiveTs(value) {
51
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
52
+ return value;
53
+ }
54
+ if (typeof value === "string" && value.trim().length > 0) {
55
+ const parsed = Number(value);
56
+ if (Number.isFinite(parsed) && parsed > 0) {
57
+ return parsed;
58
+ }
59
+ }
60
+ return undefined;
61
+ }
62
+ export function resolveSlackPresenceDndEndTs(dndInfo) {
63
+ if (dndInfo.snooze_enabled === true) {
64
+ return parsePositiveTs(dndInfo.snooze_endtime);
65
+ }
66
+ if (dndInfo.dnd_enabled === true) {
67
+ return parsePositiveTs(dndInfo.next_dnd_end_ts);
68
+ }
69
+ return undefined;
70
+ }
71
+ export function formatSlackPresenceTimestamp(ts) {
72
+ if (ts == null || !Number.isFinite(ts) || ts <= 0) {
73
+ return undefined;
74
+ }
75
+ return new Date(ts * 1000).toISOString();
76
+ }
77
+ export function formatSlackPresenceLine(snapshot) {
78
+ const segments = [`${snapshot.userName} (${snapshot.userId})`, `presence: ${snapshot.presence}`];
79
+ if (snapshot.dndEnabled) {
80
+ segments.push(snapshot.dndEndAt ? `DND: on until ${snapshot.dndEndAt}` : "DND: on (end time unknown)");
81
+ }
82
+ else {
83
+ segments.push("DND: off");
84
+ }
85
+ if (snapshot.online != null) {
86
+ segments.push(`online: ${snapshot.online ? "yes" : "no"}`);
87
+ }
88
+ return segments.join(" | ");
89
+ }
@@ -0,0 +1,8 @@
1
+ import { type SlackResult } from "./slack-api.js";
2
+ export type SlackRequestCall = (method: string, token: string, body?: Record<string, unknown>) => Promise<SlackResult>;
3
+ export interface SlackRequestRuntime {
4
+ slack: SlackRequestCall;
5
+ reset: () => void;
6
+ abortAndWait: () => Promise<void>;
7
+ }
8
+ export declare function createSlackRequestRuntime(): SlackRequestRuntime;
@@ -0,0 +1,19 @@
1
+ import { createAbortableOperationTracker } from "./helpers.js";
2
+ import { callSlackApi } from "./slack-api.js";
3
+ export function createSlackRequestRuntime() {
4
+ let slackRequests = createAbortableOperationTracker();
5
+ const slack = async (method, token, body) => {
6
+ return slackRequests.run((signal) => callSlackApi(method, token, body, { signal }));
7
+ };
8
+ function reset() {
9
+ slackRequests = createAbortableOperationTracker();
10
+ }
11
+ async function abortAndWait() {
12
+ await slackRequests.abortAndWait();
13
+ }
14
+ return {
15
+ slack,
16
+ reset,
17
+ abortAndWait,
18
+ };
19
+ }
@@ -0,0 +1,28 @@
1
+ import { type SlackAccessCache, type SlackCall, type SlackSuggestedPrompt } from "./slack-access.js";
2
+ import type { SinglePlayerThreadInfo } from "./single-player-runtime.js";
3
+ export interface SlackRuntimeAccessDeps {
4
+ slack: SlackCall;
5
+ getBotToken: () => string;
6
+ userNames: SlackAccessCache<string, string>;
7
+ channelCache: SlackAccessCache<string, string>;
8
+ persistState: () => void;
9
+ isSinglePlayerShuttingDown: () => boolean;
10
+ getSuggestedPrompts: () => SlackSuggestedPrompt[] | undefined;
11
+ getAgentName: () => string;
12
+ getThreads: () => Map<string, SinglePlayerThreadInfo>;
13
+ getBrokerRole: () => "broker" | "follower" | null;
14
+ resolveBrokerThreadChannel?: (threadTs: string) => string | null;
15
+ resolveFollowerThreadChannel?: (threadTs: string) => Promise<string | null>;
16
+ }
17
+ export interface SlackRuntimeAccess {
18
+ addReaction: (channel: string, ts: string, emoji: string) => Promise<void>;
19
+ removeReaction: (channel: string, ts: string, emoji: string) => Promise<void>;
20
+ resolveUser: (userId: string) => Promise<string>;
21
+ rememberChannel: (name: string, channelId: string) => void;
22
+ resolveChannel: (nameOrId: string) => Promise<string>;
23
+ resolveFollowerReplyChannel: (threadTs: string | undefined) => Promise<string | null>;
24
+ clearThreadStatus: (channelId: string, threadTs: string) => Promise<void>;
25
+ setSuggestedPrompts: (channelId: string, threadTs: string) => Promise<void>;
26
+ fetchSlackMessageByTs: (channel: string, messageTs: string) => Promise<Record<string, unknown> | null>;
27
+ }
28
+ export declare function createSlackRuntimeAccess(deps: SlackRuntimeAccessDeps): SlackRuntimeAccess;
@@ -0,0 +1,107 @@
1
+ import { resolveFollowerThreadChannel as resolveFollowerThreadChannelState } from "./helpers.js";
2
+ import { addSlackReaction, clearSlackThreadStatus, fetchSlackMessageByTs as fetchSlackMessageByTsFromSlack, removeSlackReaction, resolveSlackChannelId, resolveSlackUserName, setSlackSuggestedPrompts, } from "./slack-access.js";
3
+ export function createSlackRuntimeAccess(deps) {
4
+ return {
5
+ addReaction: async (channel, ts, emoji) => {
6
+ await addSlackReaction({
7
+ slack: deps.slack,
8
+ token: deps.getBotToken(),
9
+ channel,
10
+ timestamp: ts,
11
+ emoji,
12
+ });
13
+ },
14
+ removeReaction: async (channel, ts, emoji) => {
15
+ await removeSlackReaction({
16
+ slack: deps.slack,
17
+ token: deps.getBotToken(),
18
+ channel,
19
+ timestamp: ts,
20
+ emoji,
21
+ });
22
+ },
23
+ resolveUser: async (userId) => {
24
+ const hadCachedUser = deps.userNames.get(userId) != null;
25
+ const name = await resolveSlackUserName({
26
+ slack: deps.slack,
27
+ token: deps.getBotToken(),
28
+ userId,
29
+ cache: deps.userNames,
30
+ shouldUseResult: () => !deps.isSinglePlayerShuttingDown(),
31
+ });
32
+ if (!hadCachedUser && deps.userNames.get(userId) != null) {
33
+ deps.persistState();
34
+ }
35
+ return name;
36
+ },
37
+ rememberChannel: (name, channelId) => {
38
+ deps.channelCache.set(name.replace(/^#/, ""), channelId);
39
+ },
40
+ resolveChannel: async (nameOrId) => {
41
+ return resolveSlackChannelId({
42
+ slack: deps.slack,
43
+ token: deps.getBotToken(),
44
+ nameOrId,
45
+ cache: deps.channelCache,
46
+ });
47
+ },
48
+ resolveFollowerReplyChannel: async (threadTs) => {
49
+ if (!threadTs) {
50
+ return null;
51
+ }
52
+ const threads = deps.getThreads();
53
+ const existingThread = threads.get(threadTs);
54
+ const brokerRole = deps.getBrokerRole();
55
+ const resolveThread = brokerRole === "broker"
56
+ ? async (nextThreadTs) => deps.resolveBrokerThreadChannel?.(nextThreadTs) ?? null
57
+ : brokerRole === "follower"
58
+ ? deps.resolveFollowerThreadChannel
59
+ : undefined;
60
+ const resolved = await resolveFollowerThreadChannelState(threadTs, existingThread, resolveThread);
61
+ if (resolved.threadUpdate && resolved.changed) {
62
+ threads.set(threadTs, {
63
+ ...(existingThread ?? {}),
64
+ ...resolved.threadUpdate,
65
+ });
66
+ deps.persistState();
67
+ }
68
+ return resolved.channelId;
69
+ },
70
+ clearThreadStatus: async (channelId, threadTs) => {
71
+ await clearSlackThreadStatus({
72
+ slack: deps.slack,
73
+ token: deps.getBotToken(),
74
+ channelId,
75
+ threadTs,
76
+ });
77
+ },
78
+ setSuggestedPrompts: async (channelId, threadTs) => {
79
+ const prompts = deps.getSuggestedPrompts() ?? [
80
+ {
81
+ title: "Status",
82
+ message: `Hey ${deps.getAgentName()}, what are you working on right now?`,
83
+ },
84
+ {
85
+ title: "Help",
86
+ message: `${deps.getAgentName()}, I need help with something in the codebase`,
87
+ },
88
+ { title: "Review", message: `${deps.getAgentName()}, summarise the recent changes` },
89
+ ];
90
+ await setSlackSuggestedPrompts({
91
+ slack: deps.slack,
92
+ token: deps.getBotToken(),
93
+ channelId,
94
+ threadTs,
95
+ prompts,
96
+ });
97
+ },
98
+ fetchSlackMessageByTs: async (channel, messageTs) => {
99
+ return fetchSlackMessageByTsFromSlack({
100
+ slack: deps.slack,
101
+ token: deps.getBotToken(),
102
+ channel,
103
+ messageTs,
104
+ });
105
+ },
106
+ };
107
+ }
@@ -0,0 +1,31 @@
1
+ export type SlackScopeDiagnosticsStatus = "not_checked" | "pending" | "healthy" | "drift" | "unavailable";
2
+ export interface SlackScopeProbeResult {
3
+ key: string;
4
+ surface: "files" | "bookmarks" | "pins";
5
+ method: string;
6
+ expectedScopes: string[];
7
+ status: "ok" | "missing" | "unavailable";
8
+ missingScopes: string[];
9
+ error?: string;
10
+ neededScopes?: string[];
11
+ providedScopes?: string[];
12
+ }
13
+ export interface SlackScopeDiagnostics {
14
+ status: SlackScopeDiagnosticsStatus;
15
+ checkedAt: string | null;
16
+ summary: string;
17
+ surfaces: Array<"files" | "bookmarks" | "pins">;
18
+ missingScopes: string[];
19
+ results: SlackScopeProbeResult[];
20
+ error?: string;
21
+ }
22
+ export interface DetectSlackScopeDiagnosticsOptions {
23
+ token: string;
24
+ fetchImpl?: typeof fetch;
25
+ now?: () => string;
26
+ }
27
+ export declare function createUncheckedSlackScopeDiagnostics(): SlackScopeDiagnostics;
28
+ export declare function createPendingSlackScopeDiagnostics(): SlackScopeDiagnostics;
29
+ export declare function detectSlackScopeDiagnostics(options: DetectSlackScopeDiagnosticsOptions): Promise<SlackScopeDiagnostics>;
30
+ export declare function formatSlackScopeDiagnosticsStatus(diagnostics: SlackScopeDiagnostics): string;
31
+ export declare function buildSlackScopeDriftWarning(diagnostics: SlackScopeDiagnostics): string | null;