@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.
- package/LICENSE +21 -0
- package/README.md +570 -0
- package/dist/activity-log.d.ts +62 -0
- package/dist/activity-log.js +293 -0
- package/dist/agent-completion-runtime.d.ts +17 -0
- package/dist/agent-completion-runtime.js +26 -0
- package/dist/agent-event-runtime.d.ts +13 -0
- package/dist/agent-event-runtime.js +27 -0
- package/dist/agent-prompt-guidance.d.ts +22 -0
- package/dist/agent-prompt-guidance.js +46 -0
- package/dist/broker/adapters/slack.d.ts +74 -0
- package/dist/broker/adapters/slack.js +563 -0
- package/dist/broker/adapters/types.d.ts +1 -0
- package/dist/broker/adapters/types.js +1 -0
- package/dist/broker/agent-messaging.d.ts +1 -0
- package/dist/broker/agent-messaging.js +1 -0
- package/dist/broker/auth.d.ts +1 -0
- package/dist/broker/auth.js +1 -0
- package/dist/broker/client.d.ts +167 -0
- package/dist/broker/client.js +624 -0
- package/dist/broker/control-plane-dashboard.d.ts +98 -0
- package/dist/broker/control-plane-dashboard.js +213 -0
- package/dist/broker/ghost-reaper.d.ts +49 -0
- package/dist/broker/ghost-reaper.js +209 -0
- package/dist/broker/index.d.ts +36 -0
- package/dist/broker/index.js +90 -0
- package/dist/broker/leader.d.ts +1 -0
- package/dist/broker/leader.js +1 -0
- package/dist/broker/maintenance.d.ts +1 -0
- package/dist/broker/maintenance.js +1 -0
- package/dist/broker/message-send.d.ts +1 -0
- package/dist/broker/message-send.js +1 -0
- package/dist/broker/paths.d.ts +1 -0
- package/dist/broker/paths.js +1 -0
- package/dist/broker/raw-tcp-loopback.d.ts +1 -0
- package/dist/broker/raw-tcp-loopback.js +1 -0
- package/dist/broker/router.d.ts +1 -0
- package/dist/broker/router.js +1 -0
- package/dist/broker/schema.d.ts +9 -0
- package/dist/broker/schema.js +63 -0
- package/dist/broker/socket-server.d.ts +120 -0
- package/dist/broker/socket-server.js +1087 -0
- package/dist/broker/types.d.ts +1 -0
- package/dist/broker/types.js +1 -0
- package/dist/broker-delivery.d.ts +10 -0
- package/dist/broker-delivery.js +26 -0
- package/dist/broker-inbound-persistence.d.ts +11 -0
- package/dist/broker-inbound-persistence.js +22 -0
- package/dist/broker-prompt-loader.d.ts +34 -0
- package/dist/broker-prompt-loader.js +185 -0
- package/dist/broker-runtime-access.d.ts +14 -0
- package/dist/broker-runtime-access.js +20 -0
- package/dist/broker-runtime.d.ts +100 -0
- package/dist/broker-runtime.js +533 -0
- package/dist/broker-thread-owner-hints.d.ts +10 -0
- package/dist/broker-thread-owner-hints.js +14 -0
- package/dist/canvases.d.ts +80 -0
- package/dist/canvases.js +221 -0
- package/dist/command-registration-runtime.d.ts +9 -0
- package/dist/command-registration-runtime.js +9 -0
- package/dist/core-tool-guardrails.d.ts +24 -0
- package/dist/core-tool-guardrails.js +66 -0
- package/dist/deploy-manifest.d.ts +27 -0
- package/dist/deploy-manifest.js +197 -0
- package/dist/follower-delivery.d.ts +16 -0
- package/dist/follower-delivery.js +70 -0
- package/dist/follower-runtime.d.ts +61 -0
- package/dist/follower-runtime.js +350 -0
- package/dist/git-metadata.d.ts +35 -0
- package/dist/git-metadata.js +88 -0
- package/dist/guardrails.d.ts +69 -0
- package/dist/guardrails.js +283 -0
- package/dist/helpers.d.ts +550 -0
- package/dist/helpers.js +2711 -0
- package/dist/home-tab.d.ts +27 -0
- package/dist/home-tab.js +242 -0
- package/dist/imessage-tools.d.ts +33 -0
- package/dist/imessage-tools.js +96 -0
- package/dist/inbox-drain-runtime.d.ts +30 -0
- package/dist/inbox-drain-runtime.js +71 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1330 -0
- package/dist/persisted-runtime-state.d.ts +52 -0
- package/dist/persisted-runtime-state.js +98 -0
- package/dist/pinet-activity-formatting.d.ts +20 -0
- package/dist/pinet-activity-formatting.js +43 -0
- package/dist/pinet-agent-status.d.ts +36 -0
- package/dist/pinet-agent-status.js +48 -0
- package/dist/pinet-commands.d.ts +70 -0
- package/dist/pinet-commands.js +402 -0
- package/dist/pinet-confirmation-replies.d.ts +7 -0
- package/dist/pinet-confirmation-replies.js +30 -0
- package/dist/pinet-control-plane-dashboard.d.ts +34 -0
- package/dist/pinet-control-plane-dashboard.js +86 -0
- package/dist/pinet-home-tabs.d.ts +36 -0
- package/dist/pinet-home-tabs.js +101 -0
- package/dist/pinet-maintenance-delivery.d.ts +22 -0
- package/dist/pinet-maintenance-delivery.js +45 -0
- package/dist/pinet-mesh-ops.d.ts +97 -0
- package/dist/pinet-mesh-ops.js +266 -0
- package/dist/pinet-registration-gate.d.ts +14 -0
- package/dist/pinet-registration-gate.js +42 -0
- package/dist/pinet-remote-control-acks.d.ts +16 -0
- package/dist/pinet-remote-control-acks.js +46 -0
- package/dist/pinet-remote-control.d.ts +17 -0
- package/dist/pinet-remote-control.js +74 -0
- package/dist/pinet-runtime-composition.d.ts +27 -0
- package/dist/pinet-runtime-composition.js +20 -0
- package/dist/pinet-tools.d.ts +67 -0
- package/dist/pinet-tools.js +1211 -0
- package/dist/prompts/broker/default.md +41 -0
- package/dist/prompts/broker/tmux.md +49 -0
- package/dist/ralph-loop.d.ts +76 -0
- package/dist/ralph-loop.js +524 -0
- package/dist/reaction-triggers.d.ts +25 -0
- package/dist/reaction-triggers.js +171 -0
- package/dist/repo-tool-guardrails.d.ts +20 -0
- package/dist/repo-tool-guardrails.js +81 -0
- package/dist/runtime-agent-context.d.ts +93 -0
- package/dist/runtime-agent-context.js +302 -0
- package/dist/runtime-mode.d.ts +10 -0
- package/dist/runtime-mode.js +39 -0
- package/dist/scheduled-wakeups.d.ts +1 -0
- package/dist/scheduled-wakeups.js +1 -0
- package/dist/session-ui-runtime.d.ts +20 -0
- package/dist/session-ui-runtime.js +163 -0
- package/dist/single-player-runtime.d.ts +71 -0
- package/dist/single-player-runtime.js +436 -0
- package/dist/skins/cosmere.json +1571 -0
- package/dist/skins/foundation.json +1304 -0
- package/dist/slack-access.d.ts +186 -0
- package/dist/slack-access.js +462 -0
- package/dist/slack-api.d.ts +2 -0
- package/dist/slack-api.js +1 -0
- package/dist/slack-block-kit.d.ts +36 -0
- package/dist/slack-block-kit.js +242 -0
- package/dist/slack-export.d.ts +31 -0
- package/dist/slack-export.js +206 -0
- package/dist/slack-message-context.d.ts +15 -0
- package/dist/slack-message-context.js +207 -0
- package/dist/slack-modals.d.ts +13 -0
- package/dist/slack-modals.js +79 -0
- package/dist/slack-pinet-runtime-adapter.d.ts +16 -0
- package/dist/slack-pinet-runtime-adapter.js +72 -0
- package/dist/slack-presence.d.ts +36 -0
- package/dist/slack-presence.js +89 -0
- package/dist/slack-request-runtime.d.ts +8 -0
- package/dist/slack-request-runtime.js +19 -0
- package/dist/slack-runtime-access.d.ts +28 -0
- package/dist/slack-runtime-access.js +107 -0
- package/dist/slack-scope-diagnostics.d.ts +31 -0
- package/dist/slack-scope-diagnostics.js +235 -0
- package/dist/slack-socket-dedup.d.ts +7 -0
- package/dist/slack-socket-dedup.js +135 -0
- package/dist/slack-thread-status.d.ts +41 -0
- package/dist/slack-thread-status.js +156 -0
- package/dist/slack-tool-policy-runtime.d.ts +36 -0
- package/dist/slack-tool-policy-runtime.js +96 -0
- package/dist/slack-tools.d.ts +52 -0
- package/dist/slack-tools.js +2688 -0
- package/dist/slack-turn-guardrails.d.ts +19 -0
- package/dist/slack-turn-guardrails.js +36 -0
- package/dist/slack-upload.d.ts +49 -0
- package/dist/slack-upload.js +224 -0
- package/dist/task-assignments.d.ts +45 -0
- package/dist/task-assignments.js +527 -0
- package/dist/thread-confirmations.d.ts +22 -0
- package/dist/thread-confirmations.js +139 -0
- package/dist/tool-registration-runtime.d.ts +13 -0
- package/dist/tool-registration-runtime.js +13 -0
- package/dist/ttl-cache.d.ts +47 -0
- package/dist/ttl-cache.js +105 -0
- package/manifest.yaml +57 -0
- package/package.json +60 -0
- package/skills/pinet-skin-creator/SKILL.md +109 -0
- package/skills/pinet-skin-creator/references/descriptor-format.md +99 -0
- package/skills/pinet-skin-creator/references/safety-checklist.md +56 -0
- package/skills/pinet-skin-creator/templates/pinet-skin-descriptor.json +77 -0
- package/skills/slack-bridge/SKILL.md +360 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1330 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import { createGitContextCache, probeGitBranch, probeGitContext } from "./git-metadata.js";
|
|
4
|
+
import { loadSettings as loadSettingsFromFile, buildAllowlist, reloadPinetRuntimeSafely, buildPinetOwnerToken, resolveAgentIdentity, resolveBrokerStableId, resolveAgentStableId, resolveAllowAllWorkspaceUsers, trackBrokerInboundThread, } from "./helpers.js";
|
|
5
|
+
import { buildSecurityPrompt } from "./guardrails.js";
|
|
6
|
+
import { TtlCache, TtlSet } from "./ttl-cache.js";
|
|
7
|
+
import { resolveReactionCommands } from "./reaction-triggers.js";
|
|
8
|
+
import { DEFAULT_SOCKET_PATH } from "./broker/client.js";
|
|
9
|
+
import { createCommandRegistrationRuntime } from "./command-registration-runtime.js";
|
|
10
|
+
import { createToolRegistrationRuntime } from "./tool-registration-runtime.js";
|
|
11
|
+
import { createSlackRuntimeAccess } from "./slack-runtime-access.js";
|
|
12
|
+
import { createThreadConfirmationPolicy } from "./thread-confirmations.js";
|
|
13
|
+
import { persistDeliveredInboundMessage } from "./broker-inbound-persistence.js";
|
|
14
|
+
import { createIMessageAdapter, detectIMessageMvpEnvironment, formatIMessageMvpReadiness, } from "@pinet/imessage-bridge";
|
|
15
|
+
import { SLACK_SOCKET_DELIVERY_DEDUP_MAX_SIZE, SLACK_SOCKET_DELIVERY_DEDUP_TTL_MS, } from "./slack-access.js";
|
|
16
|
+
import { createFollowerDeliveryState, markFollowerInboxIdsDelivered, queueFollowerInboxIds, } from "./follower-delivery.js";
|
|
17
|
+
import { createFollowerRuntime } from "./follower-runtime.js";
|
|
18
|
+
import { createSinglePlayerRuntime, } from "./single-player-runtime.js";
|
|
19
|
+
import { createBrokerRuntime } from "./broker-runtime.js";
|
|
20
|
+
import { createSlackPinetRuntimeAdapterFactory } from "./slack-pinet-runtime-adapter.js";
|
|
21
|
+
import { SlackActivityLogger } from "./activity-log.js";
|
|
22
|
+
import { createBrokerDeliveryState, queueBrokerInboxIds } from "./broker-delivery.js";
|
|
23
|
+
import { buildBrokerControlPlaneDashboardSnapshot } from "./broker/control-plane-dashboard.js";
|
|
24
|
+
import { createPinetHomeTabs } from "./pinet-home-tabs.js";
|
|
25
|
+
import { createPinetAgentStatus } from "./pinet-agent-status.js";
|
|
26
|
+
import { createBrokerThreadOwnerHints } from "./broker-thread-owner-hints.js";
|
|
27
|
+
import { createPersistedRuntimeState } from "./persisted-runtime-state.js";
|
|
28
|
+
import { createRuntimeAgentContext } from "./runtime-agent-context.js";
|
|
29
|
+
import { createPinetActivityFormatting } from "./pinet-activity-formatting.js";
|
|
30
|
+
import { createPinetControlPlaneDashboard } from "./pinet-control-plane-dashboard.js";
|
|
31
|
+
import { createPinetMaintenanceDelivery } from "./pinet-maintenance-delivery.js";
|
|
32
|
+
import { createPinetRemoteControlAcks } from "./pinet-remote-control-acks.js";
|
|
33
|
+
import { createPinetRemoteControl } from "./pinet-remote-control.js";
|
|
34
|
+
import { createPinetMeshOps } from "./pinet-mesh-ops.js";
|
|
35
|
+
import { consumePinetReadConfirmationReplies } from "./pinet-confirmation-replies.js";
|
|
36
|
+
import { createAgentPromptGuidance } from "./agent-prompt-guidance.js";
|
|
37
|
+
import { createAgentEventRuntime } from "./agent-event-runtime.js";
|
|
38
|
+
import { createSessionUiRuntime } from "./session-ui-runtime.js";
|
|
39
|
+
import { createSlackRequestRuntime } from "./slack-request-runtime.js";
|
|
40
|
+
import { createPinetRegistrationGate } from "./pinet-registration-gate.js";
|
|
41
|
+
import { createBrokerRuntimeAccess } from "./broker-runtime-access.js";
|
|
42
|
+
import { createInboxDrainRuntime } from "./inbox-drain-runtime.js";
|
|
43
|
+
import { createAgentCompletionRuntime } from "./agent-completion-runtime.js";
|
|
44
|
+
import { sendBrokerMessage } from "./broker/message-send.js";
|
|
45
|
+
import { SlackThreadStatusManager } from "./slack-thread-status.js";
|
|
46
|
+
import { isBrokerManagedFollowerLaunch, resolveSlackBridgeStartupRuntimeMode, } from "./runtime-mode.js";
|
|
47
|
+
import { buildSlackScopeDriftWarning, createPendingSlackScopeDiagnostics, createUncheckedSlackScopeDiagnostics, detectSlackScopeDiagnostics, } from "./slack-scope-diagnostics.js";
|
|
48
|
+
// Settings and helpers imported from ./helpers.js
|
|
49
|
+
export default function (pi) {
|
|
50
|
+
let settings = loadSettingsFromFile();
|
|
51
|
+
let botToken = settings.botToken ?? process.env.SLACK_BOT_TOKEN;
|
|
52
|
+
let appToken = settings.appToken ?? process.env.SLACK_APP_TOKEN;
|
|
53
|
+
if (!botToken || !appToken)
|
|
54
|
+
return;
|
|
55
|
+
const slackRequestRuntime = createSlackRequestRuntime();
|
|
56
|
+
const { slack } = slackRequestRuntime;
|
|
57
|
+
// allowedUsers / allowAllWorkspaceUsers: settings.json takes priority, env vars as fallback
|
|
58
|
+
let allowedUsers = buildAllowlist(settings, process.env.SLACK_ALLOWED_USERS, process.env.SLACK_ALLOW_ALL_WORKSPACE_USERS);
|
|
59
|
+
let reactionCommands = resolveReactionCommands(settings.reactionCommands);
|
|
60
|
+
const initialIdentity = resolveAgentIdentity(settings, process.env.PI_NICKNAME, process.cwd());
|
|
61
|
+
let agentName = initialIdentity.name;
|
|
62
|
+
let agentEmoji = initialIdentity.emoji;
|
|
63
|
+
let agentStableId = resolveAgentStableId(undefined, undefined, os.hostname(), process.cwd());
|
|
64
|
+
let brokerStableId = resolveBrokerStableId(undefined, os.hostname(), process.cwd());
|
|
65
|
+
let agentOwnerToken = buildPinetOwnerToken(agentStableId);
|
|
66
|
+
let activeSkinTheme = null;
|
|
67
|
+
let agentPersonality = null;
|
|
68
|
+
const agentAliases = new Set();
|
|
69
|
+
// Security guardrails
|
|
70
|
+
let guardrails = settings.security ?? {};
|
|
71
|
+
let securityPrompt = buildSecurityPrompt(guardrails);
|
|
72
|
+
let botUserId = null;
|
|
73
|
+
let slackScopeDiagnostics = createUncheckedSlackScopeDiagnostics();
|
|
74
|
+
let slackScopeDiagnosticsRefresh = null;
|
|
75
|
+
let lastSlackScopeDriftWarning = "";
|
|
76
|
+
const threads = new Map();
|
|
77
|
+
const pendingEyes = new Map(); // thread_ts → message ts list // thread_ts values showing "is thinking…"
|
|
78
|
+
const userNames = new TtlCache({ maxSize: 2000, ttlMs: 60 * 60 * 1000 });
|
|
79
|
+
let lastDmChannel = null;
|
|
80
|
+
const channelCache = new TtlCache({ maxSize: 500, ttlMs: 30 * 60 * 1000 });
|
|
81
|
+
const unclaimedThreads = new TtlSet({ maxSize: 5000, ttlMs: 5 * 60 * 1000 });
|
|
82
|
+
const processedSlackSocketDeliveries = new TtlSet({
|
|
83
|
+
maxSize: SLACK_SOCKET_DELIVERY_DEDUP_MAX_SIZE,
|
|
84
|
+
ttlMs: SLACK_SOCKET_DELIVERY_DEDUP_TTL_MS,
|
|
85
|
+
});
|
|
86
|
+
const { formatAction: formatConfirmationAction, registerRequest: registerConfirmationRequest, consumeReply: consumeConfirmationReply, requireToolPolicy, } = createThreadConfirmationPolicy({
|
|
87
|
+
getGuardrails: () => guardrails,
|
|
88
|
+
});
|
|
89
|
+
// ─── State persistence ──────────────────────────────
|
|
90
|
+
const persistedRuntimeState = createPersistedRuntimeState({
|
|
91
|
+
pi,
|
|
92
|
+
threads,
|
|
93
|
+
userNames,
|
|
94
|
+
getLastDmChannel: () => lastDmChannel,
|
|
95
|
+
setLastDmChannel: (channelId) => {
|
|
96
|
+
lastDmChannel = channelId;
|
|
97
|
+
},
|
|
98
|
+
getAgentName: () => agentName,
|
|
99
|
+
setAgentName: (name) => {
|
|
100
|
+
agentName = name;
|
|
101
|
+
},
|
|
102
|
+
getAgentEmoji: () => agentEmoji,
|
|
103
|
+
setAgentEmoji: (emoji) => {
|
|
104
|
+
agentEmoji = emoji;
|
|
105
|
+
},
|
|
106
|
+
getAgentStableId: () => agentStableId,
|
|
107
|
+
setAgentStableId: (stableId) => {
|
|
108
|
+
agentStableId = stableId;
|
|
109
|
+
},
|
|
110
|
+
getBrokerStableId: () => brokerStableId,
|
|
111
|
+
setBrokerStableId: (stableId) => {
|
|
112
|
+
brokerStableId = stableId;
|
|
113
|
+
},
|
|
114
|
+
getBrokerRole: () => brokerRole,
|
|
115
|
+
getActiveSkinTheme: () => activeSkinTheme,
|
|
116
|
+
setActiveSkinTheme: (theme) => {
|
|
117
|
+
activeSkinTheme = theme;
|
|
118
|
+
},
|
|
119
|
+
getAgentPersonality: () => agentPersonality,
|
|
120
|
+
setAgentPersonality: (personality) => {
|
|
121
|
+
agentPersonality = personality;
|
|
122
|
+
},
|
|
123
|
+
agentAliases,
|
|
124
|
+
setAgentOwnerToken: (ownerToken) => {
|
|
125
|
+
agentOwnerToken = ownerToken;
|
|
126
|
+
},
|
|
127
|
+
getSettings: () => settings,
|
|
128
|
+
formatError: msg,
|
|
129
|
+
});
|
|
130
|
+
const { persistState, flushPersist, restorePersistedRuntimeState } = persistedRuntimeState;
|
|
131
|
+
// ─── Inbox queue ────────────────────────────────────
|
|
132
|
+
const inbox = [];
|
|
133
|
+
const brokerDeliveryState = createBrokerDeliveryState();
|
|
134
|
+
let drainInboxPort = null;
|
|
135
|
+
const sessionUiRuntime = createSessionUiRuntime({
|
|
136
|
+
getAgentName: () => agentName,
|
|
137
|
+
getAgentEmoji: () => agentEmoji,
|
|
138
|
+
getInboxLength: () => inbox.length,
|
|
139
|
+
drainInbox: () => {
|
|
140
|
+
drainInboxPort?.();
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const { updateBadge, setExtStatus, maybeDrainInboxIfIdle } = sessionUiRuntime;
|
|
144
|
+
let reportAgentStatus = async () => { };
|
|
145
|
+
let deliverTrackedSlackFollowUpMessage = () => false;
|
|
146
|
+
const inboxDrainRuntime = createInboxDrainRuntime({
|
|
147
|
+
sendUserMessage: (body) => {
|
|
148
|
+
pi.sendUserMessage(body);
|
|
149
|
+
},
|
|
150
|
+
isIdle: () => sessionUiRuntime.getExtensionContext()?.isIdle?.() ?? true,
|
|
151
|
+
takeInboxMessages: (maxMessages) => inbox.splice(0, maxMessages ?? inbox.length),
|
|
152
|
+
restoreInboxMessages: (messages) => {
|
|
153
|
+
inbox.unshift(...messages);
|
|
154
|
+
},
|
|
155
|
+
updateBadge,
|
|
156
|
+
reportStatus: (status) => reportAgentStatus(status),
|
|
157
|
+
userNames,
|
|
158
|
+
getSecurityPrompt: () => securityPrompt,
|
|
159
|
+
deliverTrackedSlackFollowUpMessage: (options) => deliverTrackedSlackFollowUpMessage(options),
|
|
160
|
+
getBrokerRole: () => brokerRole,
|
|
161
|
+
hasFollowerClient: () => brokerClient?.client != null,
|
|
162
|
+
flushFollowerDeliveredAcks: () => followerRuntime.flushDeliveredAcks(),
|
|
163
|
+
markBrokerInboxIdsDelivered: (inboxIds) => {
|
|
164
|
+
brokerRuntime.markDelivered(inboxIds);
|
|
165
|
+
},
|
|
166
|
+
getFollowerDeliveryState: () => followerDeliveryState,
|
|
167
|
+
});
|
|
168
|
+
const { deliverFollowUpMessage, flushDeliveredFollowerAcks, drainInbox } = inboxDrainRuntime;
|
|
169
|
+
drainInboxPort = drainInbox;
|
|
170
|
+
// ─── Helpers ─────────────────────────────────────────
|
|
171
|
+
const gitContextCache = createGitContextCache(() => probeGitContext(process.cwd()));
|
|
172
|
+
const runtimeAgentContext = createRuntimeAgentContext({
|
|
173
|
+
cwd: process.cwd(),
|
|
174
|
+
getSettings: () => settings,
|
|
175
|
+
setSettings: (nextSettings) => {
|
|
176
|
+
settings = nextSettings;
|
|
177
|
+
},
|
|
178
|
+
getBotToken: () => botToken,
|
|
179
|
+
setBotToken: (token) => {
|
|
180
|
+
botToken = token;
|
|
181
|
+
},
|
|
182
|
+
getAppToken: () => appToken,
|
|
183
|
+
setAppToken: (token) => {
|
|
184
|
+
appToken = token;
|
|
185
|
+
},
|
|
186
|
+
getAllowedUsers: () => allowedUsers,
|
|
187
|
+
setAllowedUsers: (users) => {
|
|
188
|
+
allowedUsers = users;
|
|
189
|
+
},
|
|
190
|
+
getGuardrails: () => guardrails,
|
|
191
|
+
setGuardrails: (nextGuardrails) => {
|
|
192
|
+
guardrails = nextGuardrails;
|
|
193
|
+
},
|
|
194
|
+
getReactionCommands: () => reactionCommands,
|
|
195
|
+
setReactionCommands: (commands) => {
|
|
196
|
+
reactionCommands = commands;
|
|
197
|
+
},
|
|
198
|
+
getSecurityPrompt: () => securityPrompt,
|
|
199
|
+
setSecurityPrompt: (prompt) => {
|
|
200
|
+
securityPrompt = prompt;
|
|
201
|
+
},
|
|
202
|
+
getAgentName: () => agentName,
|
|
203
|
+
setAgentName: (name) => {
|
|
204
|
+
agentName = name;
|
|
205
|
+
},
|
|
206
|
+
getAgentEmoji: () => agentEmoji,
|
|
207
|
+
setAgentEmoji: (emoji) => {
|
|
208
|
+
agentEmoji = emoji;
|
|
209
|
+
},
|
|
210
|
+
getAgentStableId: () => agentStableId,
|
|
211
|
+
getBrokerStableId: () => brokerStableId,
|
|
212
|
+
getBrokerRole: () => brokerRole,
|
|
213
|
+
getAgentOwnerToken: () => agentOwnerToken,
|
|
214
|
+
setAgentOwnerToken: (ownerToken) => {
|
|
215
|
+
agentOwnerToken = ownerToken;
|
|
216
|
+
},
|
|
217
|
+
getActiveSkinTheme: () => activeSkinTheme,
|
|
218
|
+
setActiveSkinTheme: (theme) => {
|
|
219
|
+
activeSkinTheme = theme;
|
|
220
|
+
},
|
|
221
|
+
getAgentPersonality: () => agentPersonality,
|
|
222
|
+
setAgentPersonality: (personality) => {
|
|
223
|
+
agentPersonality = personality;
|
|
224
|
+
},
|
|
225
|
+
getAgentAliases: () => agentAliases,
|
|
226
|
+
getThreads: () => threads,
|
|
227
|
+
getExtensionContext: sessionUiRuntime.getExtensionContext,
|
|
228
|
+
persistState,
|
|
229
|
+
updateBadge,
|
|
230
|
+
getGitContext: () => gitContextCache.get(),
|
|
231
|
+
});
|
|
232
|
+
const { isUserAllowed, maybeWarnSlackUserAccess, maybeWarnSlackGuardrailPosture, applyLocalAgentIdentity, refreshSettings, snapshotReloadableRuntime, restoreReloadableRuntime, getAgentMetadata, getMeshRoleFromMetadata, buildSkinMetadata, applyRegistrationIdentity, } = runtimeAgentContext;
|
|
233
|
+
async function refreshSlackScopeDiagnostics(ctx) {
|
|
234
|
+
if (!botToken) {
|
|
235
|
+
slackScopeDiagnostics = createUncheckedSlackScopeDiagnostics();
|
|
236
|
+
lastSlackScopeDriftWarning = "";
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
slackScopeDiagnostics = createPendingSlackScopeDiagnostics();
|
|
240
|
+
const diagnostics = await detectSlackScopeDiagnostics({ token: botToken });
|
|
241
|
+
slackScopeDiagnostics = diagnostics;
|
|
242
|
+
const warning = buildSlackScopeDriftWarning(diagnostics);
|
|
243
|
+
if (!warning) {
|
|
244
|
+
lastSlackScopeDriftWarning = "";
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (warning === lastSlackScopeDriftWarning) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
lastSlackScopeDriftWarning = warning;
|
|
251
|
+
console.warn(`[slack-bridge] ${warning}`);
|
|
252
|
+
ctx?.ui.notify(warning, "warning");
|
|
253
|
+
}
|
|
254
|
+
async function ensureSlackScopeDiagnostics(ctx) {
|
|
255
|
+
if (slackScopeDiagnosticsRefresh) {
|
|
256
|
+
await slackScopeDiagnosticsRefresh;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
slackScopeDiagnosticsRefresh = refreshSlackScopeDiagnostics(ctx)
|
|
260
|
+
.catch((error) => {
|
|
261
|
+
slackScopeDiagnostics = {
|
|
262
|
+
status: "unavailable",
|
|
263
|
+
checkedAt: new Date().toISOString(),
|
|
264
|
+
summary: `unavailable (${msg(error)})`,
|
|
265
|
+
surfaces: [],
|
|
266
|
+
missingScopes: [],
|
|
267
|
+
results: [],
|
|
268
|
+
error: msg(error),
|
|
269
|
+
};
|
|
270
|
+
lastSlackScopeDriftWarning = "";
|
|
271
|
+
})
|
|
272
|
+
.finally(() => {
|
|
273
|
+
slackScopeDiagnosticsRefresh = null;
|
|
274
|
+
});
|
|
275
|
+
await slackScopeDiagnosticsRefresh;
|
|
276
|
+
}
|
|
277
|
+
const agentPromptGuidance = createAgentPromptGuidance({
|
|
278
|
+
getIdentityGuidelines: runtimeAgentContext.getIdentityGuidelines,
|
|
279
|
+
getAgentName: () => agentName,
|
|
280
|
+
getAgentEmoji: () => agentEmoji,
|
|
281
|
+
getActiveSkinTheme: () => activeSkinTheme,
|
|
282
|
+
getAgentPersonality: () => agentPersonality,
|
|
283
|
+
getBrokerRole: () => brokerRole,
|
|
284
|
+
getBrokerPromptSetting: () => settings.brokerPrompt,
|
|
285
|
+
});
|
|
286
|
+
let isSinglePlayerShuttingDown = () => false;
|
|
287
|
+
let isSinglePlayerConnected = () => false;
|
|
288
|
+
const slackThreadStatuses = new SlackThreadStatusManager({
|
|
289
|
+
slack,
|
|
290
|
+
getBotToken: () => botToken,
|
|
291
|
+
formatError: msg,
|
|
292
|
+
logger: console,
|
|
293
|
+
});
|
|
294
|
+
const slackRuntimeAccess = createSlackRuntimeAccess({
|
|
295
|
+
slack,
|
|
296
|
+
getBotToken: () => botToken,
|
|
297
|
+
userNames,
|
|
298
|
+
channelCache,
|
|
299
|
+
persistState,
|
|
300
|
+
isSinglePlayerShuttingDown: () => isSinglePlayerShuttingDown(),
|
|
301
|
+
getSuggestedPrompts: () => settings.suggestedPrompts,
|
|
302
|
+
getAgentName: () => agentName,
|
|
303
|
+
getThreads: () => threads,
|
|
304
|
+
getBrokerRole: () => brokerRole,
|
|
305
|
+
resolveBrokerThreadChannel: (threadTs) => brokerRuntime.getBroker()?.db.getThread(threadTs)?.channel ?? null,
|
|
306
|
+
resolveFollowerThreadChannel: async (threadTs) => (await brokerClient?.client.resolveThread(threadTs)) ?? null,
|
|
307
|
+
});
|
|
308
|
+
const { addReaction, removeReaction, resolveUser, rememberChannel, resolveChannel, resolveFollowerReplyChannel, clearThreadStatus, setSuggestedPrompts, fetchSlackMessageByTs, } = slackRuntimeAccess;
|
|
309
|
+
const pinetHomeTabs = createPinetHomeTabs({
|
|
310
|
+
slack,
|
|
311
|
+
getBotToken: () => botToken,
|
|
312
|
+
formatError: msg,
|
|
313
|
+
getAgentName: () => agentName,
|
|
314
|
+
getAgentEmoji: () => agentEmoji,
|
|
315
|
+
getBrokerRole: () => brokerRole,
|
|
316
|
+
getRuntimeMode: () => currentRuntimeMode,
|
|
317
|
+
isFollowerConnected: () => brokerClient != null,
|
|
318
|
+
isSinglePlayerConnected: () => isSinglePlayerConnected(),
|
|
319
|
+
getActiveThreads: () => threads.size,
|
|
320
|
+
getPendingInboxCount: () => inbox.length,
|
|
321
|
+
getDefaultChannel: () => settings.defaultChannel ?? null,
|
|
322
|
+
getCurrentBranch: async () => (await probeGitBranch(process.cwd())) ?? null,
|
|
323
|
+
getBrokerHomeTabs: () => brokerRuntime,
|
|
324
|
+
});
|
|
325
|
+
const brokerRuntimeAccess = createBrokerRuntimeAccess({
|
|
326
|
+
getBroker: () => brokerRuntime.getBroker(),
|
|
327
|
+
getSelfId: () => brokerRuntime.getSelfId(),
|
|
328
|
+
getHomeTabViewerIds: () => brokerRuntime.getHomeTabViewerIds(),
|
|
329
|
+
});
|
|
330
|
+
const { getActiveBroker, getActiveBrokerDb, getActiveBrokerSelfId, getBrokerControlPlaneHomeTabViewerIds, } = brokerRuntimeAccess;
|
|
331
|
+
const pinetAgentStatus = createPinetAgentStatus({
|
|
332
|
+
getPinetEnabled: () => pinetEnabled,
|
|
333
|
+
getBrokerRole: () => brokerRole,
|
|
334
|
+
getDesiredAgentStatus: () => desiredAgentStatus,
|
|
335
|
+
setDesiredAgentStatus: (status) => {
|
|
336
|
+
desiredAgentStatus = status;
|
|
337
|
+
},
|
|
338
|
+
getActiveBrokerDb,
|
|
339
|
+
getActiveBrokerSelfId,
|
|
340
|
+
hasFollowerClient: () => brokerClient != null,
|
|
341
|
+
syncFollowerDesiredStatus: (status, options) => followerRuntime.syncDesiredStatus(status, options),
|
|
342
|
+
runBrokerMaintenance: (ctx) => {
|
|
343
|
+
brokerRuntime.runMaintenance(ctx);
|
|
344
|
+
},
|
|
345
|
+
getInboxLength: () => inbox.length,
|
|
346
|
+
getCurrentRuntimeMode: () => currentRuntimeMode,
|
|
347
|
+
maybeDrainInboxIfIdle,
|
|
348
|
+
getExtensionContext: () => sessionUiRuntime.getExtensionContext() ?? undefined,
|
|
349
|
+
});
|
|
350
|
+
const { reportStatus, signalAgentFree } = pinetAgentStatus;
|
|
351
|
+
reportAgentStatus = reportStatus;
|
|
352
|
+
const agentCompletionRuntime = createAgentCompletionRuntime({
|
|
353
|
+
getThreads: () => threads,
|
|
354
|
+
clearThreadStatus,
|
|
355
|
+
clearFollowUpPending: () => {
|
|
356
|
+
brokerRuntime.clearFollowUpPending();
|
|
357
|
+
},
|
|
358
|
+
signalAgentFree: (ctx) => signalAgentFree(ctx),
|
|
359
|
+
formatError: msg,
|
|
360
|
+
});
|
|
361
|
+
const agentEventRuntime = createAgentEventRuntime({
|
|
362
|
+
getBrokerRole: () => brokerRole,
|
|
363
|
+
getGuardrails: () => guardrails,
|
|
364
|
+
requireToolPolicy,
|
|
365
|
+
formatAction: formatConfirmationAction,
|
|
366
|
+
formatError: msg,
|
|
367
|
+
deliverFollowUpMessage,
|
|
368
|
+
beginThreadStatus: (channel, threadTs, status) => slackThreadStatuses.begin(channel, threadTs, status),
|
|
369
|
+
updateThreadStatus: (channel, threadTs, status) => slackThreadStatuses.update(channel, threadTs, status),
|
|
370
|
+
clearThreadStatus: (channel, threadTs) => slackThreadStatuses.clear(channel, threadTs),
|
|
371
|
+
beforeAgentStart: agentPromptGuidance.beforeAgentStart,
|
|
372
|
+
onCompletionAgentEnd: agentCompletionRuntime.onAgentEnd,
|
|
373
|
+
setDeliverTrackedSlackFollowUpMessage: (deliver) => {
|
|
374
|
+
deliverTrackedSlackFollowUpMessage = deliver;
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
const pinetMaintenanceDelivery = createPinetMaintenanceDelivery({
|
|
378
|
+
getActiveBrokerDb,
|
|
379
|
+
getActiveBrokerSelfId,
|
|
380
|
+
isIdle: () => sessionUiRuntime.getExtensionContext()?.isIdle?.() ?? true,
|
|
381
|
+
sendUserMessage: (body) => {
|
|
382
|
+
pi.sendUserMessage(body);
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
const { sendBrokerMaintenanceMessage, trySendBrokerFollowUp } = pinetMaintenanceDelivery;
|
|
386
|
+
const pinetRemoteControlAcks = createPinetRemoteControlAcks({
|
|
387
|
+
queueBrokerInboxIds: (inboxIds) => {
|
|
388
|
+
queueBrokerInboxIds(brokerDeliveryState, inboxIds);
|
|
389
|
+
},
|
|
390
|
+
isBrokerConnected: () => brokerRuntime.isConnected(),
|
|
391
|
+
markBrokerInboxIdsDelivered: (inboxIds) => {
|
|
392
|
+
brokerRuntime.markDelivered(inboxIds);
|
|
393
|
+
},
|
|
394
|
+
queueFollowerInboxIds: (inboxIds) => {
|
|
395
|
+
queueFollowerInboxIds(followerDeliveryState, inboxIds);
|
|
396
|
+
},
|
|
397
|
+
markFollowerInboxIdsDelivered: (inboxIds) => {
|
|
398
|
+
markFollowerInboxIdsDelivered(followerDeliveryState, inboxIds);
|
|
399
|
+
},
|
|
400
|
+
flushDeliveredFollowerAcks,
|
|
401
|
+
});
|
|
402
|
+
const { resetPendingRemoteControlAcks, deferBrokerControlAck, deferFollowerControlAck, flushDeferredRemoteControlAcks, } = pinetRemoteControlAcks;
|
|
403
|
+
const pinetRemoteControl = createPinetRemoteControl({
|
|
404
|
+
flushDeferredRemoteControlAcks,
|
|
405
|
+
reloadPinetRuntime,
|
|
406
|
+
formatError: msg,
|
|
407
|
+
});
|
|
408
|
+
const { requestRemoteControl, runRemoteControl, resetRemoteControlState } = pinetRemoteControl;
|
|
409
|
+
const pinetActivityFormatting = createPinetActivityFormatting({
|
|
410
|
+
getActiveBrokerDb,
|
|
411
|
+
});
|
|
412
|
+
const { formatTrackedAgent, summarizeTrackedAssignmentStatus } = pinetActivityFormatting;
|
|
413
|
+
const pinetControlPlaneDashboard = createPinetControlPlaneDashboard({
|
|
414
|
+
getActiveBrokerDb,
|
|
415
|
+
getActiveBrokerSelfId,
|
|
416
|
+
heartbeatTimerActive: () => brokerRuntime.heartbeatTimerActive(),
|
|
417
|
+
maintenanceTimerActive: () => brokerRuntime.maintenanceTimerActive(),
|
|
418
|
+
getLastMaintenance: () => brokerRuntime.getLastMaintenance(),
|
|
419
|
+
getRalphSnoozeStatus: () => brokerRuntime.getRalphSnoozeStatus(),
|
|
420
|
+
});
|
|
421
|
+
const { buildCurrentBrokerControlPlaneDashboardSnapshot } = pinetControlPlaneDashboard;
|
|
422
|
+
// ─── Socket Mode (native WebSocket) ─────────────────
|
|
423
|
+
const singlePlayerRuntime = createSinglePlayerRuntime({
|
|
424
|
+
slack,
|
|
425
|
+
getBotToken: () => botToken,
|
|
426
|
+
getAppToken: () => appToken,
|
|
427
|
+
dedup: processedSlackSocketDeliveries,
|
|
428
|
+
abortSlackRequests: slackRequestRuntime.abortAndWait,
|
|
429
|
+
isSingleRuntimeActive: () => currentRuntimeMode === "single",
|
|
430
|
+
setExtStatus,
|
|
431
|
+
formatError: msg,
|
|
432
|
+
getAgentName: () => agentName,
|
|
433
|
+
getAgentAliases: () => agentAliases,
|
|
434
|
+
getAgentOwnerToken: () => agentOwnerToken,
|
|
435
|
+
getBotUserId: () => botUserId,
|
|
436
|
+
getThreads: () => threads,
|
|
437
|
+
getPendingEyes: () => pendingEyes,
|
|
438
|
+
getUnclaimedThreads: () => unclaimedThreads,
|
|
439
|
+
pushInboxMessage: (message) => {
|
|
440
|
+
inbox.push(message);
|
|
441
|
+
},
|
|
442
|
+
setLastDmChannel: (channelId) => {
|
|
443
|
+
lastDmChannel = channelId;
|
|
444
|
+
},
|
|
445
|
+
persistState,
|
|
446
|
+
updateBadge,
|
|
447
|
+
maybeDrainInboxIfIdle,
|
|
448
|
+
resolveThreadChannel: resolveFollowerReplyChannel,
|
|
449
|
+
setSuggestedPrompts,
|
|
450
|
+
publishCurrentPinetHomeTab: (userId, ctx) => pinetHomeTabs.publishCurrentPinetHomeTabSafely(userId, ctx),
|
|
451
|
+
fetchSlackMessageByTs,
|
|
452
|
+
addReaction,
|
|
453
|
+
removeReaction,
|
|
454
|
+
resolveUser,
|
|
455
|
+
isUserAllowed,
|
|
456
|
+
getReactionCommand: (reactionName) => reactionCommands.get(reactionName),
|
|
457
|
+
consumeConfirmationReply,
|
|
458
|
+
beginThreadStatus: (channelId, threadTs, status) => slackThreadStatuses.begin(channelId, threadTs, status),
|
|
459
|
+
claimOwnedThread: (threadTs, channelId, source = "slack") => {
|
|
460
|
+
if (brokerRole === "broker") {
|
|
461
|
+
brokerRuntime.claimThread(threadTs, channelId, source);
|
|
462
|
+
}
|
|
463
|
+
else if (brokerRole === "follower" && brokerClient?.client) {
|
|
464
|
+
void brokerClient.client.claimThread(threadTs, channelId, source).catch(() => {
|
|
465
|
+
/* broker gone, best effort */
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
isSinglePlayerShuttingDown = () => singlePlayerRuntime.isShuttingDown();
|
|
471
|
+
isSinglePlayerConnected = () => singlePlayerRuntime.isConnected();
|
|
472
|
+
// ─── Reconnect / status ─────────────────────────────
|
|
473
|
+
// ─── Agent-to-agent messaging tools ──────────────────
|
|
474
|
+
// These are registered unconditionally but only work when pinet is active.
|
|
475
|
+
// The variables they reference (pinetEnabled, brokerRole, brokerRuntime,
|
|
476
|
+
// brokerClient) are declared in the Commands section just below.
|
|
477
|
+
// Forward-declared — assigned in the Commands section below.
|
|
478
|
+
let pinetEnabled = false;
|
|
479
|
+
let currentRuntimeMode = "off";
|
|
480
|
+
let brokerRole = null;
|
|
481
|
+
let brokerClient = null;
|
|
482
|
+
let followerRuntimeDiagnostic = null;
|
|
483
|
+
const followerDeliveryState = createFollowerDeliveryState();
|
|
484
|
+
let desiredAgentStatus = "idle";
|
|
485
|
+
const pinetRegistrationGate = createPinetRegistrationGate();
|
|
486
|
+
const brokerThreadOwnerHints = createBrokerThreadOwnerHints({
|
|
487
|
+
slack,
|
|
488
|
+
getBotToken: () => botToken,
|
|
489
|
+
});
|
|
490
|
+
const { resolveBrokerThreadOwnerHint } = brokerThreadOwnerHints;
|
|
491
|
+
const handleBrokerAppHomeOpened = async (userId, ctx) => {
|
|
492
|
+
await pinetHomeTabs.publishCurrentPinetHomeTabSafely(userId, ctx, new Date().toISOString());
|
|
493
|
+
};
|
|
494
|
+
const slackPinetAdapterFactory = createSlackPinetRuntimeAdapterFactory({
|
|
495
|
+
getSettings: () => settings,
|
|
496
|
+
getBotToken: () => botToken,
|
|
497
|
+
getAppToken: () => appToken,
|
|
498
|
+
getAllowedUsers: () => allowedUsers,
|
|
499
|
+
shouldAllowAllWorkspaceUsers: () => resolveAllowAllWorkspaceUsers(settings, process.env.SLACK_ALLOW_ALL_WORKSPACE_USERS),
|
|
500
|
+
onAppHomeOpened: handleBrokerAppHomeOpened,
|
|
501
|
+
});
|
|
502
|
+
const brokerRuntime = createBrokerRuntime({
|
|
503
|
+
getSettings: () => settings,
|
|
504
|
+
getAllowedUsers: () => allowedUsers,
|
|
505
|
+
getBrokerStableId: () => brokerStableId,
|
|
506
|
+
setBrokerStableId: (stableId) => {
|
|
507
|
+
brokerStableId = stableId;
|
|
508
|
+
},
|
|
509
|
+
getActiveSkinTheme: () => activeSkinTheme,
|
|
510
|
+
setActiveSkinTheme: (theme) => {
|
|
511
|
+
activeSkinTheme = theme;
|
|
512
|
+
},
|
|
513
|
+
setAgentOwnerToken: (ownerToken) => {
|
|
514
|
+
agentOwnerToken = ownerToken;
|
|
515
|
+
},
|
|
516
|
+
getAgentMetadata,
|
|
517
|
+
applyLocalAgentIdentity,
|
|
518
|
+
buildSkinMetadata: (metadata, personality, statusVocabulary) => buildSkinMetadata(metadata ?? undefined, personality, statusVocabulary),
|
|
519
|
+
getMeshRoleFromMetadata: (metadata, fallbackRole) => getMeshRoleFromMetadata(metadata ?? undefined, fallbackRole),
|
|
520
|
+
handleInboundMessage: async ({ message, broker, router, selfId, ctx }) => {
|
|
521
|
+
try {
|
|
522
|
+
const ownerHint = message.source === "slack" && message.threadId && message.channel
|
|
523
|
+
? await resolveBrokerThreadOwnerHint(message.channel, message.threadId)
|
|
524
|
+
: null;
|
|
525
|
+
const routedMessage = ownerHint && (ownerHint.agentOwner || ownerHint.agentName)
|
|
526
|
+
? {
|
|
527
|
+
...message,
|
|
528
|
+
metadata: {
|
|
529
|
+
...(message.metadata ?? {}),
|
|
530
|
+
threadOwnerHint: ownerHint,
|
|
531
|
+
...(ownerHint.agentOwner ? { threadOwnerAgentOwner: ownerHint.agentOwner } : {}),
|
|
532
|
+
...(ownerHint.agentName ? { threadOwnerAgentName: ownerHint.agentName } : {}),
|
|
533
|
+
},
|
|
534
|
+
}
|
|
535
|
+
: message;
|
|
536
|
+
trackBrokerInboundThread(threads, routedMessage);
|
|
537
|
+
const decision = router.route(routedMessage);
|
|
538
|
+
if (routedMessage.threadId && routedMessage.channel) {
|
|
539
|
+
broker.db.updateThread(routedMessage.threadId, {
|
|
540
|
+
source: routedMessage.source,
|
|
541
|
+
channel: routedMessage.channel,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
if (decision.action === "deliver" && decision.agentId !== selfId) {
|
|
545
|
+
broker.db.queueMessage(decision.agentId, routedMessage);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (decision.action === "deliver" || decision.action === "unrouted") {
|
|
549
|
+
const persisted = routedMessage.source === "slack" && routedMessage.threadId
|
|
550
|
+
? persistDeliveredInboundMessage(broker.db, selfId, routedMessage)
|
|
551
|
+
: null;
|
|
552
|
+
if (persisted && !persisted.result.freshDelivery) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
inbox.push({
|
|
556
|
+
channel: routedMessage.channel,
|
|
557
|
+
threadTs: routedMessage.threadId,
|
|
558
|
+
userId: routedMessage.userId,
|
|
559
|
+
text: persisted?.notificationText ?? routedMessage.text,
|
|
560
|
+
timestamp: routedMessage.timestamp,
|
|
561
|
+
metadata: routedMessage.metadata ?? null,
|
|
562
|
+
...(persisted ? { brokerInboxId: persisted.result.entry.id } : {}),
|
|
563
|
+
...(routedMessage.scope ? { scope: routedMessage.scope } : {}),
|
|
564
|
+
});
|
|
565
|
+
updateBadge();
|
|
566
|
+
maybeDrainInboxIfIdle(ctx);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
catch (err) {
|
|
570
|
+
console.error(`[slack-bridge] broker inbound routing failed: ${msg(err)}`);
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
pushInboxMessages: (messages) => {
|
|
574
|
+
inbox.push(...messages);
|
|
575
|
+
},
|
|
576
|
+
updateBadge,
|
|
577
|
+
maybeDrainInboxIfIdle,
|
|
578
|
+
requestRemoteControl,
|
|
579
|
+
deferControlAck: deferBrokerControlAck,
|
|
580
|
+
runRemoteControl,
|
|
581
|
+
formatError: msg,
|
|
582
|
+
deliveryState: brokerDeliveryState,
|
|
583
|
+
createActivityLogger: (onError) => new SlackActivityLogger({
|
|
584
|
+
getBotToken: () => botToken,
|
|
585
|
+
getLogChannel: () => settings.logChannel,
|
|
586
|
+
getLogLevel: () => settings.logLevel,
|
|
587
|
+
getAgentName: () => agentName,
|
|
588
|
+
getAgentEmoji: () => agentEmoji,
|
|
589
|
+
resolveChannel,
|
|
590
|
+
slack,
|
|
591
|
+
onError,
|
|
592
|
+
}),
|
|
593
|
+
formatTrackedAgent,
|
|
594
|
+
summarizeTrackedAssignmentStatus,
|
|
595
|
+
sendMaintenanceMessage: (targetAgentId, body) => {
|
|
596
|
+
sendBrokerMaintenanceMessage(targetAgentId, body);
|
|
597
|
+
},
|
|
598
|
+
trySendFollowUp: (body, onDelivered) => {
|
|
599
|
+
trySendBrokerFollowUp(body, onDelivered);
|
|
600
|
+
},
|
|
601
|
+
refreshHomeTabs: async (ctx, snapshot, refreshedAt, userIds) => {
|
|
602
|
+
await pinetHomeTabs.refreshBrokerControlPlaneHomeTabs(ctx, snapshot, refreshedAt, userIds);
|
|
603
|
+
},
|
|
604
|
+
buildControlPlaneDashboardSnapshot: (input) => buildBrokerControlPlaneDashboardSnapshot(input),
|
|
605
|
+
buildCurrentDashboardSnapshot: async (openedAt) => buildCurrentBrokerControlPlaneDashboardSnapshot(openedAt),
|
|
606
|
+
createAdapterBindings: [slackPinetAdapterFactory],
|
|
607
|
+
onMaintenanceResult: (ctx, { result, previousSignature, signature }) => {
|
|
608
|
+
if (signature && signature !== previousSignature) {
|
|
609
|
+
ctx.ui.notify(`Pinet broker: ${result.anomalies.join("; ")}`, "warning");
|
|
610
|
+
}
|
|
611
|
+
else if (!signature && previousSignature) {
|
|
612
|
+
ctx.ui.notify("Pinet broker health recovered", "info");
|
|
613
|
+
}
|
|
614
|
+
const maintenanceDetails = [];
|
|
615
|
+
if (result.assignedBacklogCount > 0) {
|
|
616
|
+
maintenanceDetails.push(`assigned ${result.assignedBacklogCount} backlog item${result.assignedBacklogCount === 1 ? "" : "s"}`);
|
|
617
|
+
}
|
|
618
|
+
if (result.reapedAgentIds.length > 0) {
|
|
619
|
+
maintenanceDetails.push(`reaped stale agents: ${result.reapedAgentIds.map((agentId) => formatTrackedAgent(agentId)).join(", ")}`);
|
|
620
|
+
}
|
|
621
|
+
if (result.repairedThreadClaims > 0) {
|
|
622
|
+
maintenanceDetails.push(`released ${result.repairedThreadClaims} orphaned thread claim${result.repairedThreadClaims === 1 ? "" : "s"}`);
|
|
623
|
+
}
|
|
624
|
+
maintenanceDetails.push(...result.anomalies);
|
|
625
|
+
const hasMaintenanceActions = result.assignedBacklogCount > 0 ||
|
|
626
|
+
result.reapedAgentIds.length > 0 ||
|
|
627
|
+
result.repairedThreadClaims > 0;
|
|
628
|
+
const shouldLogMaintenance = hasMaintenanceActions || previousSignature !== signature;
|
|
629
|
+
if (shouldLogMaintenance) {
|
|
630
|
+
brokerRuntime.logActivity({
|
|
631
|
+
kind: "broker_maintenance",
|
|
632
|
+
level: hasMaintenanceActions ? "actions" : "verbose",
|
|
633
|
+
title: signature ? "Broker maintenance anomaly" : "Broker maintenance recovery",
|
|
634
|
+
summary: signature
|
|
635
|
+
? `Broker maintenance recorded ${maintenanceDetails.length} noteworthy event${maintenanceDetails.length === 1 ? "" : "s"}.`
|
|
636
|
+
: "Broker maintenance is healthy again.",
|
|
637
|
+
details: signature && maintenanceDetails.length > 0
|
|
638
|
+
? maintenanceDetails
|
|
639
|
+
: previousSignature
|
|
640
|
+
? ["Previous anomalies cleared."]
|
|
641
|
+
: undefined,
|
|
642
|
+
fields: [
|
|
643
|
+
{ label: "Backlog", value: result.pendingBacklogCount },
|
|
644
|
+
{ label: "Assigned", value: result.assignedBacklogCount },
|
|
645
|
+
{ label: "Reaped", value: result.reapedAgentIds.length },
|
|
646
|
+
{ label: "Repaired", value: result.repairedThreadClaims },
|
|
647
|
+
],
|
|
648
|
+
tone: signature ? "warning" : "success",
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
onMaintenanceError: (ctx, error) => {
|
|
653
|
+
ctx.ui.notify(`Pinet maintenance failed: ${msg(error)}`, "error");
|
|
654
|
+
brokerRuntime.logActivity({
|
|
655
|
+
kind: "broker_maintenance_error",
|
|
656
|
+
level: "errors",
|
|
657
|
+
title: "Broker maintenance failed",
|
|
658
|
+
summary: msg(error),
|
|
659
|
+
tone: "error",
|
|
660
|
+
});
|
|
661
|
+
},
|
|
662
|
+
onScheduledWakeupError: (ctx, error) => {
|
|
663
|
+
ctx.ui.notify(`Pinet scheduled wake-ups failed: ${msg(error)}`, "error");
|
|
664
|
+
brokerRuntime.logActivity({
|
|
665
|
+
kind: "scheduled_wakeup_error",
|
|
666
|
+
level: "errors",
|
|
667
|
+
title: "Scheduled wake-up delivery failed",
|
|
668
|
+
summary: msg(error),
|
|
669
|
+
tone: "error",
|
|
670
|
+
});
|
|
671
|
+
},
|
|
672
|
+
onAgentStatusChange: (_ctx, changedAgentId, status) => {
|
|
673
|
+
brokerRuntime.logActivity({
|
|
674
|
+
kind: "agent_status",
|
|
675
|
+
level: "verbose",
|
|
676
|
+
title: status === "idle" ? "Worker available" : "Worker busy",
|
|
677
|
+
summary: `${formatTrackedAgent(changedAgentId)} marked itself ${status}.`,
|
|
678
|
+
fields: [{ label: "Agent", value: formatTrackedAgent(changedAgentId) }],
|
|
679
|
+
tone: status === "idle" ? "success" : "info",
|
|
680
|
+
});
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
const pinetMeshOps = createPinetMeshOps({
|
|
684
|
+
getPinetEnabled: () => pinetEnabled,
|
|
685
|
+
getBrokerRole: () => brokerRole,
|
|
686
|
+
getActiveBrokerDb,
|
|
687
|
+
getActiveBrokerSelfId,
|
|
688
|
+
getAgentName: () => agentName,
|
|
689
|
+
getFollowerClient: () => brokerClient?.client ?? null,
|
|
690
|
+
formatTrackedAgent,
|
|
691
|
+
logActivity: (entry) => {
|
|
692
|
+
brokerRuntime.logActivity(entry);
|
|
693
|
+
},
|
|
694
|
+
});
|
|
695
|
+
const { sendPinetAgentMessage, sendPinetBroadcastMessage, scheduleBrokerWakeup, scheduleFollowerWakeup, listBrokerAgents, listFollowerAgents, } = pinetMeshOps;
|
|
696
|
+
async function listPinetLanes(options) {
|
|
697
|
+
if (brokerRole === "broker") {
|
|
698
|
+
const db = getActiveBrokerDb();
|
|
699
|
+
if (!db)
|
|
700
|
+
throw new Error("Broker database is unavailable.");
|
|
701
|
+
return db.listPinetLanes(options);
|
|
702
|
+
}
|
|
703
|
+
if (brokerRole === "follower" && brokerClient?.client) {
|
|
704
|
+
return await brokerClient.client.listLanes(options);
|
|
705
|
+
}
|
|
706
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
707
|
+
}
|
|
708
|
+
async function upsertPinetLane(input) {
|
|
709
|
+
if (brokerRole === "broker") {
|
|
710
|
+
const db = getActiveBrokerDb();
|
|
711
|
+
if (!db)
|
|
712
|
+
throw new Error("Broker database is unavailable.");
|
|
713
|
+
return db.upsertPinetLane(input);
|
|
714
|
+
}
|
|
715
|
+
if (brokerRole === "follower" && brokerClient?.client) {
|
|
716
|
+
return await brokerClient.client.upsertLane(input);
|
|
717
|
+
}
|
|
718
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
719
|
+
}
|
|
720
|
+
async function setPinetLaneParticipant(input) {
|
|
721
|
+
if (brokerRole === "broker") {
|
|
722
|
+
const db = getActiveBrokerDb();
|
|
723
|
+
const selfId = getActiveBrokerSelfId();
|
|
724
|
+
if (!db)
|
|
725
|
+
throw new Error("Broker database is unavailable.");
|
|
726
|
+
return db.setPinetLaneParticipant({
|
|
727
|
+
...input,
|
|
728
|
+
agentId: input.agentId.trim().length > 0 ? input.agentId : (selfId ?? input.agentId),
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
if (brokerRole === "follower" && brokerClient?.client) {
|
|
732
|
+
return await brokerClient.client.setLaneParticipant(input);
|
|
733
|
+
}
|
|
734
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
735
|
+
}
|
|
736
|
+
async function acquirePortLease(input) {
|
|
737
|
+
if (brokerRole === "broker") {
|
|
738
|
+
const db = getActiveBrokerDb();
|
|
739
|
+
const selfId = getActiveBrokerSelfId();
|
|
740
|
+
if (!db)
|
|
741
|
+
throw new Error("Broker database is unavailable.");
|
|
742
|
+
return db.acquirePortLease({ ...input, ownerAgentId: input.ownerAgentId ?? selfId ?? null });
|
|
743
|
+
}
|
|
744
|
+
if (brokerRole === "follower" && brokerClient?.client) {
|
|
745
|
+
return await brokerClient.client.acquirePortLease(input);
|
|
746
|
+
}
|
|
747
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
748
|
+
}
|
|
749
|
+
async function renewPortLease(input) {
|
|
750
|
+
if (brokerRole === "broker") {
|
|
751
|
+
const db = getActiveBrokerDb();
|
|
752
|
+
const selfId = getActiveBrokerSelfId();
|
|
753
|
+
if (!db)
|
|
754
|
+
throw new Error("Broker database is unavailable.");
|
|
755
|
+
return db.renewPortLease({ ...input, ownerAgentId: input.ownerAgentId ?? selfId ?? null });
|
|
756
|
+
}
|
|
757
|
+
if (brokerRole === "follower" && brokerClient?.client) {
|
|
758
|
+
return await brokerClient.client.renewPortLease(input);
|
|
759
|
+
}
|
|
760
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
761
|
+
}
|
|
762
|
+
async function releasePortLease(input) {
|
|
763
|
+
if (brokerRole === "broker") {
|
|
764
|
+
const db = getActiveBrokerDb();
|
|
765
|
+
const selfId = getActiveBrokerSelfId();
|
|
766
|
+
if (!db)
|
|
767
|
+
throw new Error("Broker database is unavailable.");
|
|
768
|
+
return db.releasePortLease({ ...input, ownerAgentId: input.ownerAgentId ?? selfId ?? null });
|
|
769
|
+
}
|
|
770
|
+
if (brokerRole === "follower" && brokerClient?.client) {
|
|
771
|
+
return await brokerClient.client.releasePortLease(input);
|
|
772
|
+
}
|
|
773
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
774
|
+
}
|
|
775
|
+
async function getPortLease(leaseId) {
|
|
776
|
+
if (brokerRole === "broker") {
|
|
777
|
+
const db = getActiveBrokerDb();
|
|
778
|
+
if (!db)
|
|
779
|
+
throw new Error("Broker database is unavailable.");
|
|
780
|
+
return db.getPortLease(leaseId);
|
|
781
|
+
}
|
|
782
|
+
if (brokerRole === "follower" && brokerClient?.client) {
|
|
783
|
+
return await brokerClient.client.getPortLease(leaseId);
|
|
784
|
+
}
|
|
785
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
786
|
+
}
|
|
787
|
+
async function listPortLeases(options) {
|
|
788
|
+
if (brokerRole === "broker") {
|
|
789
|
+
const db = getActiveBrokerDb();
|
|
790
|
+
if (!db)
|
|
791
|
+
throw new Error("Broker database is unavailable.");
|
|
792
|
+
return db.listPortLeases(options);
|
|
793
|
+
}
|
|
794
|
+
if (brokerRole === "follower" && brokerClient?.client) {
|
|
795
|
+
return await brokerClient.client.listPortLeases(options);
|
|
796
|
+
}
|
|
797
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
798
|
+
}
|
|
799
|
+
async function expirePortLeases() {
|
|
800
|
+
if (brokerRole === "broker") {
|
|
801
|
+
const db = getActiveBrokerDb();
|
|
802
|
+
if (!db)
|
|
803
|
+
throw new Error("Broker database is unavailable.");
|
|
804
|
+
return db.expirePortLeases();
|
|
805
|
+
}
|
|
806
|
+
if (brokerRole === "follower" && brokerClient?.client) {
|
|
807
|
+
return await brokerClient.client.expirePortLeases();
|
|
808
|
+
}
|
|
809
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
810
|
+
}
|
|
811
|
+
async function readPinetInbox(options) {
|
|
812
|
+
if (brokerRole === "broker") {
|
|
813
|
+
const db = getActiveBrokerDb();
|
|
814
|
+
const selfId = getActiveBrokerSelfId();
|
|
815
|
+
if (!db || !selfId) {
|
|
816
|
+
throw new Error("Broker agent identity is unavailable.");
|
|
817
|
+
}
|
|
818
|
+
const result = db.readInbox(selfId, options);
|
|
819
|
+
return consumePinetReadConfirmationReplies({
|
|
820
|
+
messages: result.messages.map((item) => ({
|
|
821
|
+
inboxId: item.entry.id,
|
|
822
|
+
delivered: item.entry.delivered,
|
|
823
|
+
readAt: item.entry.readAt,
|
|
824
|
+
message: item.message,
|
|
825
|
+
})),
|
|
826
|
+
unreadCountBefore: result.unreadCountBefore,
|
|
827
|
+
unreadCountAfter: result.unreadCountAfter,
|
|
828
|
+
unreadThreads: result.unreadThreads,
|
|
829
|
+
markedReadIds: result.markedReadIds,
|
|
830
|
+
}, consumeConfirmationReply);
|
|
831
|
+
}
|
|
832
|
+
if (brokerRole === "follower" && brokerClient?.client) {
|
|
833
|
+
const result = await brokerClient.client.readInbox(options);
|
|
834
|
+
return consumePinetReadConfirmationReplies(result, consumeConfirmationReply);
|
|
835
|
+
}
|
|
836
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
837
|
+
}
|
|
838
|
+
async function transitionToRuntimeMode(ctx, mode) {
|
|
839
|
+
if (currentRuntimeMode === mode) {
|
|
840
|
+
if (mode === "off") {
|
|
841
|
+
setExtStatus(ctx, "off");
|
|
842
|
+
}
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (currentRuntimeMode !== "off") {
|
|
846
|
+
await stopPinetRuntime(ctx, { releaseIdentity: true });
|
|
847
|
+
// Runtime transitions keep the extension alive in-process, so restore a
|
|
848
|
+
// fresh top-level Slack request tracker after tearing the prior runtime down.
|
|
849
|
+
slackRequestRuntime.reset();
|
|
850
|
+
singlePlayerRuntime.resetShutdownState();
|
|
851
|
+
}
|
|
852
|
+
if (mode === "off") {
|
|
853
|
+
currentRuntimeMode = "off";
|
|
854
|
+
setExtStatus(ctx, "off");
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
if (mode === "single") {
|
|
858
|
+
currentRuntimeMode = "single";
|
|
859
|
+
setExtStatus(ctx, "reconnecting");
|
|
860
|
+
await singlePlayerRuntime.connect(ctx);
|
|
861
|
+
botUserId = singlePlayerRuntime.getBotUserId() ?? botUserId;
|
|
862
|
+
void ensureSlackScopeDiagnostics(ctx);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (mode === "broker") {
|
|
866
|
+
await connectAsBroker(ctx);
|
|
867
|
+
void ensureSlackScopeDiagnostics(ctx);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
await connectAsFollower(ctx);
|
|
871
|
+
void ensureSlackScopeDiagnostics(ctx);
|
|
872
|
+
}
|
|
873
|
+
async function stopPinetRuntime(ctx, options) {
|
|
874
|
+
flushPersist();
|
|
875
|
+
await brokerRuntime.disconnect({ releaseIdentity: options.releaseIdentity });
|
|
876
|
+
if (brokerClient) {
|
|
877
|
+
if (options.releaseIdentity) {
|
|
878
|
+
await disconnectFollower(ctx).catch(() => {
|
|
879
|
+
/* best effort */
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
await followerRuntime.disconnect(ctx, { releaseIdentity: false }).catch(() => {
|
|
884
|
+
/* best effort */
|
|
885
|
+
});
|
|
886
|
+
brokerClient = null;
|
|
887
|
+
desiredAgentStatus = "idle";
|
|
888
|
+
brokerRole = null;
|
|
889
|
+
pinetEnabled = false;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
await singlePlayerRuntime.disconnect();
|
|
893
|
+
brokerRole = null;
|
|
894
|
+
pinetEnabled = false;
|
|
895
|
+
desiredAgentStatus = "idle";
|
|
896
|
+
currentRuntimeMode = "off";
|
|
897
|
+
setExtStatus(ctx, "off");
|
|
898
|
+
}
|
|
899
|
+
async function reloadPinetRuntime(ctx) {
|
|
900
|
+
await reloadPinetRuntimeSafely({
|
|
901
|
+
getCurrentRole: () => brokerRole,
|
|
902
|
+
snapshotState: () => snapshotReloadableRuntime(),
|
|
903
|
+
restoreState: (snapshot) => {
|
|
904
|
+
restoreReloadableRuntime(snapshot);
|
|
905
|
+
},
|
|
906
|
+
refreshState: () => {
|
|
907
|
+
refreshSettings();
|
|
908
|
+
},
|
|
909
|
+
validateRefreshedState: () => {
|
|
910
|
+
if (!botToken || !appToken) {
|
|
911
|
+
throw new Error("Slack tokens are not configured after reload.");
|
|
912
|
+
}
|
|
913
|
+
},
|
|
914
|
+
stopRuntime: async () => {
|
|
915
|
+
await stopPinetRuntime(ctx, { releaseIdentity: false });
|
|
916
|
+
// Reload intentionally keeps the extension alive in-process, so restore a
|
|
917
|
+
// fresh top-level Slack request tracker after aborting the previous
|
|
918
|
+
// generation. This preserves shutdown abort semantics without leaving
|
|
919
|
+
// top-level Slack tools permanently stuck in "shutdown in progress".
|
|
920
|
+
slackRequestRuntime.reset();
|
|
921
|
+
singlePlayerRuntime.resetShutdownState();
|
|
922
|
+
setExtStatus(ctx, "reconnecting");
|
|
923
|
+
},
|
|
924
|
+
startRuntime: async (role) => {
|
|
925
|
+
if (role === "broker") {
|
|
926
|
+
await connectAsBroker(ctx);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
await connectAsFollower(ctx);
|
|
930
|
+
},
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
// ─── Tools ──────────────────────────────────────────
|
|
934
|
+
const toolRegistrationRuntime = createToolRegistrationRuntime({
|
|
935
|
+
slackTools: {
|
|
936
|
+
getBotToken: () => {
|
|
937
|
+
if (!botToken) {
|
|
938
|
+
throw new Error("Slack bot token is not configured.");
|
|
939
|
+
}
|
|
940
|
+
return botToken;
|
|
941
|
+
},
|
|
942
|
+
getDefaultChannel: () => settings.defaultChannel,
|
|
943
|
+
getSecurityPrompt: () => securityPrompt,
|
|
944
|
+
inbox,
|
|
945
|
+
slack,
|
|
946
|
+
getAgentName: () => agentName,
|
|
947
|
+
getAgentEmoji: () => agentEmoji,
|
|
948
|
+
getAgentOwnerToken: () => agentOwnerToken,
|
|
949
|
+
getLastDmChannel: () => lastDmChannel,
|
|
950
|
+
updateBadge,
|
|
951
|
+
resolveUser,
|
|
952
|
+
threadContext: singlePlayerRuntime.getThreadContextPort(),
|
|
953
|
+
resolveChannel,
|
|
954
|
+
rememberChannel,
|
|
955
|
+
requireToolPolicy,
|
|
956
|
+
getBotUserId: () => botUserId,
|
|
957
|
+
registerConfirmationRequest,
|
|
958
|
+
pinetDelivery: {
|
|
959
|
+
isAvailable: () => pinetEnabled && brokerRole !== null,
|
|
960
|
+
sendSlackMessage: async (input) => {
|
|
961
|
+
const content = {
|
|
962
|
+
text: input.text,
|
|
963
|
+
markdown: input.text,
|
|
964
|
+
...(input.blocks && input.blocks.length > 0 ? { slackBlocks: input.blocks } : {}),
|
|
965
|
+
};
|
|
966
|
+
if (brokerRole === "broker") {
|
|
967
|
+
const broker = getActiveBroker();
|
|
968
|
+
const selfId = getActiveBrokerSelfId();
|
|
969
|
+
if (!broker || !selfId) {
|
|
970
|
+
throw new Error("Broker agent identity is unavailable.");
|
|
971
|
+
}
|
|
972
|
+
const result = await sendBrokerMessage({
|
|
973
|
+
db: broker.db,
|
|
974
|
+
adapters: broker.adapters,
|
|
975
|
+
}, {
|
|
976
|
+
threadId: input.threadId,
|
|
977
|
+
body: input.text,
|
|
978
|
+
senderAgentId: selfId,
|
|
979
|
+
source: "slack",
|
|
980
|
+
channel: input.channel,
|
|
981
|
+
content,
|
|
982
|
+
...(input.blocks && input.blocks.length > 0 ? { blocks: input.blocks } : {}),
|
|
983
|
+
agentName,
|
|
984
|
+
agentEmoji,
|
|
985
|
+
agentOwnerToken,
|
|
986
|
+
});
|
|
987
|
+
return {
|
|
988
|
+
adapter: result.adapter,
|
|
989
|
+
messageId: result.message.id,
|
|
990
|
+
threadId: result.thread.threadId,
|
|
991
|
+
channel: result.thread.channel,
|
|
992
|
+
source: result.thread.source,
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
if (brokerRole === "follower") {
|
|
996
|
+
if (!brokerClient?.client) {
|
|
997
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
998
|
+
}
|
|
999
|
+
return brokerClient.client.sendMessage({
|
|
1000
|
+
threadId: input.threadId,
|
|
1001
|
+
body: input.text,
|
|
1002
|
+
source: "slack",
|
|
1003
|
+
channel: input.channel,
|
|
1004
|
+
content,
|
|
1005
|
+
...(input.blocks && input.blocks.length > 0 ? { blocks: input.blocks } : {}),
|
|
1006
|
+
agentName,
|
|
1007
|
+
agentEmoji,
|
|
1008
|
+
agentOwnerToken,
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
1012
|
+
},
|
|
1013
|
+
},
|
|
1014
|
+
},
|
|
1015
|
+
pinetTools: {
|
|
1016
|
+
pinetEnabled: () => pinetEnabled,
|
|
1017
|
+
brokerRole: () => brokerRole,
|
|
1018
|
+
requireToolPolicy,
|
|
1019
|
+
sendPinetAgentMessage,
|
|
1020
|
+
sendPinetBroadcastMessage,
|
|
1021
|
+
signalAgentFree,
|
|
1022
|
+
scheduleBrokerWakeup,
|
|
1023
|
+
scheduleFollowerWakeup,
|
|
1024
|
+
readPinetInbox,
|
|
1025
|
+
listBrokerAgents,
|
|
1026
|
+
listFollowerAgents,
|
|
1027
|
+
listPinetLanes,
|
|
1028
|
+
upsertPinetLane,
|
|
1029
|
+
setPinetLaneParticipant,
|
|
1030
|
+
acquirePortLease,
|
|
1031
|
+
renewPortLease,
|
|
1032
|
+
releasePortLease,
|
|
1033
|
+
getPortLease,
|
|
1034
|
+
listPortLeases,
|
|
1035
|
+
expirePortLeases,
|
|
1036
|
+
ralphSnoozeStatus: () => currentRuntimeMode === "broker" ? brokerRuntime.getRalphSnoozeStatus() : null,
|
|
1037
|
+
snoozeRalphLoop: (input) => brokerRuntime.snoozeRalphLoop(input),
|
|
1038
|
+
clearRalphSnooze: () => brokerRuntime.clearRalphSnooze(),
|
|
1039
|
+
},
|
|
1040
|
+
iMessageTools: {
|
|
1041
|
+
pinetEnabled: () => pinetEnabled,
|
|
1042
|
+
brokerRole: () => brokerRole,
|
|
1043
|
+
requireToolPolicy,
|
|
1044
|
+
getActiveBroker,
|
|
1045
|
+
getActiveBrokerSelfId,
|
|
1046
|
+
sendFollowerIMessage: async (input) => {
|
|
1047
|
+
if (!brokerClient?.client) {
|
|
1048
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
1049
|
+
}
|
|
1050
|
+
const result = await brokerClient.client.sendMessage(input);
|
|
1051
|
+
return {
|
|
1052
|
+
adapter: result.adapter,
|
|
1053
|
+
messageId: result.messageId,
|
|
1054
|
+
};
|
|
1055
|
+
},
|
|
1056
|
+
getAgentIdentity: () => ({
|
|
1057
|
+
name: agentName,
|
|
1058
|
+
emoji: agentEmoji,
|
|
1059
|
+
ownerToken: agentOwnerToken,
|
|
1060
|
+
}),
|
|
1061
|
+
trackOwnedThread: (threadId, channel, source) => {
|
|
1062
|
+
singlePlayerRuntime.trackOwnedThread(threadId, channel, source);
|
|
1063
|
+
},
|
|
1064
|
+
},
|
|
1065
|
+
});
|
|
1066
|
+
toolRegistrationRuntime.register(pi);
|
|
1067
|
+
// ─── Commands ───────────────────────────────────────
|
|
1068
|
+
async function connectAsBroker(ctx) {
|
|
1069
|
+
refreshSettings();
|
|
1070
|
+
maybeWarnSlackUserAccess(ctx);
|
|
1071
|
+
maybeWarnSlackGuardrailPosture(ctx);
|
|
1072
|
+
const { botUserId: brokerBotUserId, recoveredBrokerMessages, recoveredTargetedBacklogCount, releasedBrokerClaims, } = await brokerRuntime.connect(ctx);
|
|
1073
|
+
const broker = brokerRuntime.getBroker();
|
|
1074
|
+
if (!broker) {
|
|
1075
|
+
throw new Error("Broker runtime failed to initialize.");
|
|
1076
|
+
}
|
|
1077
|
+
botUserId = brokerBotUserId;
|
|
1078
|
+
if (settings.imessage?.enabled) {
|
|
1079
|
+
const environment = detectIMessageMvpEnvironment();
|
|
1080
|
+
const readinessSummary = formatIMessageMvpReadiness(environment).join(" | ");
|
|
1081
|
+
if (!environment.canAttemptSend) {
|
|
1082
|
+
ctx.ui.notify(`iMessage adapter unavailable — ${readinessSummary}`, "warning");
|
|
1083
|
+
brokerRuntime.logActivity({
|
|
1084
|
+
kind: "transport_readiness",
|
|
1085
|
+
level: "actions",
|
|
1086
|
+
title: "iMessage adapter unavailable",
|
|
1087
|
+
summary: readinessSummary,
|
|
1088
|
+
tone: "warning",
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
else {
|
|
1092
|
+
try {
|
|
1093
|
+
const imessageAdapter = createIMessageAdapter();
|
|
1094
|
+
await imessageAdapter.connect();
|
|
1095
|
+
broker.addAdapter(imessageAdapter);
|
|
1096
|
+
if (environment.blockers.length > 0) {
|
|
1097
|
+
ctx.ui.notify(`iMessage send-first mode enabled — ${readinessSummary}`, "warning");
|
|
1098
|
+
brokerRuntime.logActivity({
|
|
1099
|
+
kind: "transport_readiness",
|
|
1100
|
+
level: "actions",
|
|
1101
|
+
title: "iMessage send-first mode enabled",
|
|
1102
|
+
summary: readinessSummary,
|
|
1103
|
+
tone: "warning",
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
catch (err) {
|
|
1108
|
+
ctx.ui.notify(`iMessage adapter failed to start: ${msg(err)}`, "warning");
|
|
1109
|
+
brokerRuntime.logActivity({
|
|
1110
|
+
kind: "transport_readiness",
|
|
1111
|
+
level: "errors",
|
|
1112
|
+
title: "iMessage adapter failed to start",
|
|
1113
|
+
summary: msg(err),
|
|
1114
|
+
tone: "error",
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
broker.server.setOutboundMessageAdapters?.(broker.adapters);
|
|
1120
|
+
brokerRole = "broker";
|
|
1121
|
+
pinetEnabled = true;
|
|
1122
|
+
desiredAgentStatus = "idle";
|
|
1123
|
+
currentRuntimeMode = "broker";
|
|
1124
|
+
if (recoveredBrokerMessages > 0 || releasedBrokerClaims > 0) {
|
|
1125
|
+
const recoveredTargetedDetail = recoveredTargetedBacklogCount > 0
|
|
1126
|
+
? ` including ${recoveredTargetedBacklogCount} recovered targeted backlog item${recoveredTargetedBacklogCount === 1 ? "" : "s"}`
|
|
1127
|
+
: "";
|
|
1128
|
+
ctx.ui.notify(`Pinet broker recovered ${recoveredBrokerMessages} pending message${recoveredBrokerMessages === 1 ? "" : "s"}${recoveredTargetedDetail} and released ${releasedBrokerClaims} broker-owned thread claim${releasedBrokerClaims === 1 ? "" : "s"}`, "info");
|
|
1129
|
+
}
|
|
1130
|
+
brokerRuntime.startObservability(ctx);
|
|
1131
|
+
setExtStatus(ctx, "ok");
|
|
1132
|
+
brokerRuntime.logActivity({
|
|
1133
|
+
kind: "broker_started",
|
|
1134
|
+
level: "actions",
|
|
1135
|
+
title: "Broker started",
|
|
1136
|
+
summary: `${agentEmoji} ${agentName} is online and coordinating the mesh.`,
|
|
1137
|
+
details: recoveredBrokerMessages > 0 || releasedBrokerClaims > 0
|
|
1138
|
+
? [
|
|
1139
|
+
`Recovered ${recoveredBrokerMessages} pending broker inbox item${recoveredBrokerMessages === 1 ? "" : "s"}.`,
|
|
1140
|
+
...(recoveredTargetedBacklogCount > 0
|
|
1141
|
+
? [
|
|
1142
|
+
`Recovered ${recoveredTargetedBacklogCount} targeted backlog item${recoveredTargetedBacklogCount === 1 ? "" : "s"} during startup.`,
|
|
1143
|
+
]
|
|
1144
|
+
: []),
|
|
1145
|
+
`Released ${releasedBrokerClaims} stale broker-owned thread claim${releasedBrokerClaims === 1 ? "" : "s"}.`,
|
|
1146
|
+
]
|
|
1147
|
+
: undefined,
|
|
1148
|
+
fields: [
|
|
1149
|
+
{ label: "Bot", value: botUserId ?? "unknown" },
|
|
1150
|
+
{ label: "Log channel", value: settings.logChannel ?? "disabled" },
|
|
1151
|
+
{ label: "Log level", value: settings.logLevel ?? "actions" },
|
|
1152
|
+
],
|
|
1153
|
+
tone: "success",
|
|
1154
|
+
});
|
|
1155
|
+
ctx.ui.notify(`${agentEmoji} ${agentName} — broker started (${botUserId})`, "info");
|
|
1156
|
+
}
|
|
1157
|
+
const followerRuntime = createFollowerRuntime({
|
|
1158
|
+
getSettings: () => settings,
|
|
1159
|
+
refreshSettings,
|
|
1160
|
+
getPinetEnabled: () => pinetEnabled,
|
|
1161
|
+
getAgentIdentity: () => ({ name: agentName, emoji: agentEmoji }),
|
|
1162
|
+
getAgentStableId: () => agentStableId,
|
|
1163
|
+
getAgentOwnerToken: () => agentOwnerToken,
|
|
1164
|
+
setAgentOwnerToken: (ownerToken) => {
|
|
1165
|
+
agentOwnerToken = ownerToken;
|
|
1166
|
+
},
|
|
1167
|
+
getDesiredAgentStatus: () => desiredAgentStatus,
|
|
1168
|
+
getAgentAliases: () => agentAliases,
|
|
1169
|
+
getThreads: () => threads,
|
|
1170
|
+
getLastDmChannel: () => lastDmChannel,
|
|
1171
|
+
setLastDmChannel: (channelId) => {
|
|
1172
|
+
lastDmChannel = channelId;
|
|
1173
|
+
},
|
|
1174
|
+
pushInboxMessages: (messages) => {
|
|
1175
|
+
inbox.push(...messages);
|
|
1176
|
+
},
|
|
1177
|
+
getAgentMetadata,
|
|
1178
|
+
applyRegistrationIdentity,
|
|
1179
|
+
persistState,
|
|
1180
|
+
updateBadge,
|
|
1181
|
+
maybeDrainInboxIfIdle,
|
|
1182
|
+
requestRemoteControl,
|
|
1183
|
+
deferControlAck: deferFollowerControlAck,
|
|
1184
|
+
runRemoteControl,
|
|
1185
|
+
deliverFollowUpMessage,
|
|
1186
|
+
setExtStatus,
|
|
1187
|
+
getRuntimeDiagnostic: () => followerRuntimeDiagnostic,
|
|
1188
|
+
setRuntimeDiagnostic: (diagnostic) => {
|
|
1189
|
+
followerRuntimeDiagnostic = diagnostic;
|
|
1190
|
+
},
|
|
1191
|
+
handleTerminalReconnectFailure: async (ctx, error) => {
|
|
1192
|
+
console.error(`[slack-bridge] follower reconnect failed: ${msg(error)}`);
|
|
1193
|
+
await disconnectFollower(ctx, { preserveErrorState: true }).catch(() => {
|
|
1194
|
+
/* best effort */
|
|
1195
|
+
});
|
|
1196
|
+
setExtStatus(ctx, "error");
|
|
1197
|
+
ctx.ui.notify(`Pinet reconnect stopped: ${msg(error)} Update slack-bridge.agentName/agentEmoji or PI_NICKNAME, or clear the explicit identity request, then run /pinet follow to retry.`, "error");
|
|
1198
|
+
},
|
|
1199
|
+
formatError: msg,
|
|
1200
|
+
deliveryState: followerDeliveryState,
|
|
1201
|
+
});
|
|
1202
|
+
const commandRegistrationRuntime = createCommandRegistrationRuntime({
|
|
1203
|
+
pinetCommands: {
|
|
1204
|
+
pinetEnabled: () => pinetEnabled,
|
|
1205
|
+
pinetRegistrationBlocked: pinetRegistrationGate.isBlocked,
|
|
1206
|
+
runtimeMode: () => currentRuntimeMode,
|
|
1207
|
+
runtimeConnected: () => currentRuntimeMode === "broker"
|
|
1208
|
+
? brokerRuntime.isConnected()
|
|
1209
|
+
: currentRuntimeMode === "follower"
|
|
1210
|
+
? (brokerClient?.client.isConnected() ?? false)
|
|
1211
|
+
: currentRuntimeMode === "single"
|
|
1212
|
+
? singlePlayerRuntime.isConnected()
|
|
1213
|
+
: false,
|
|
1214
|
+
brokerRole: () => brokerRole,
|
|
1215
|
+
agentName: () => agentName,
|
|
1216
|
+
agentEmoji: () => agentEmoji,
|
|
1217
|
+
agentOwnerToken: () => agentOwnerToken,
|
|
1218
|
+
agentPersonality: () => agentPersonality,
|
|
1219
|
+
agentAliases: () => agentAliases,
|
|
1220
|
+
botUserId: () => botUserId,
|
|
1221
|
+
activeSkinTheme: () => activeSkinTheme,
|
|
1222
|
+
lastDmChannel: () => lastDmChannel,
|
|
1223
|
+
followerRuntimeDiagnostic: () => followerRuntimeDiagnostic,
|
|
1224
|
+
threads: () => threads,
|
|
1225
|
+
allowedUsers: () => allowedUsers,
|
|
1226
|
+
inboxLength: () => inbox.length,
|
|
1227
|
+
recentActivityLogEntries: (limit) => brokerRuntime.getRecentActivityEntries(limit),
|
|
1228
|
+
slackScopeDiagnostics: () => slackScopeDiagnostics,
|
|
1229
|
+
settings: () => settings,
|
|
1230
|
+
lastBrokerMaintenance: () => brokerRuntime.getLastMaintenance(),
|
|
1231
|
+
ralphSnoozeStatus: () => currentRuntimeMode === "broker" ? brokerRuntime.getRalphSnoozeStatus() : null,
|
|
1232
|
+
snoozeRalphLoop: (input) => brokerRuntime.snoozeRalphLoop(input),
|
|
1233
|
+
clearRalphSnooze: () => brokerRuntime.clearRalphSnooze(),
|
|
1234
|
+
getBrokerControlPlaneHomeTabViewerIds,
|
|
1235
|
+
lastBrokerControlPlaneHomeTabRefreshAt: () => brokerRuntime.getLastHomeTabRefreshAt(),
|
|
1236
|
+
lastBrokerControlPlaneHomeTabError: () => brokerRuntime.getLastHomeTabError(),
|
|
1237
|
+
getPinetRegistrationBlockReason: pinetRegistrationGate.getBlockReason,
|
|
1238
|
+
connectAsBroker: (ctx) => transitionToRuntimeMode(ctx, "broker"),
|
|
1239
|
+
connectAsFollower: (ctx) => transitionToRuntimeMode(ctx, "follower"),
|
|
1240
|
+
reloadPinetRuntime,
|
|
1241
|
+
disconnectFollower,
|
|
1242
|
+
sendPinetAgentMessage,
|
|
1243
|
+
signalAgentFree,
|
|
1244
|
+
applyLocalAgentIdentity,
|
|
1245
|
+
setExtStatus,
|
|
1246
|
+
setExtCtx: sessionUiRuntime.setExtCtx,
|
|
1247
|
+
},
|
|
1248
|
+
});
|
|
1249
|
+
commandRegistrationRuntime.register(pi);
|
|
1250
|
+
async function connectAsFollower(ctx) {
|
|
1251
|
+
pinetRegistrationGate.assertCanRegister();
|
|
1252
|
+
const clientRef = await followerRuntime.connect(ctx);
|
|
1253
|
+
brokerClient = clientRef;
|
|
1254
|
+
followerRuntimeDiagnostic = null;
|
|
1255
|
+
brokerRole = "follower";
|
|
1256
|
+
pinetEnabled = true;
|
|
1257
|
+
desiredAgentStatus = "idle";
|
|
1258
|
+
currentRuntimeMode = "follower";
|
|
1259
|
+
setExtStatus(ctx, "ok");
|
|
1260
|
+
}
|
|
1261
|
+
async function disconnectFollower(ctx, options = {}) {
|
|
1262
|
+
const result = await followerRuntime.disconnect(ctx);
|
|
1263
|
+
brokerClient = null;
|
|
1264
|
+
desiredAgentStatus = "idle";
|
|
1265
|
+
brokerRole = null;
|
|
1266
|
+
pinetEnabled = false;
|
|
1267
|
+
currentRuntimeMode = "off";
|
|
1268
|
+
if (!options.preserveErrorState) {
|
|
1269
|
+
followerRuntimeDiagnostic = null;
|
|
1270
|
+
setExtStatus(ctx, "off");
|
|
1271
|
+
}
|
|
1272
|
+
return result;
|
|
1273
|
+
}
|
|
1274
|
+
// ─── Lifecycle ──────────────────────────────────────
|
|
1275
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
1276
|
+
singlePlayerRuntime.resetShutdownState();
|
|
1277
|
+
slackRequestRuntime.reset();
|
|
1278
|
+
resetRemoteControlState();
|
|
1279
|
+
resetPendingRemoteControlAcks();
|
|
1280
|
+
sessionUiRuntime.prepareForSessionStart(ctx);
|
|
1281
|
+
const pinetRegistrationBlocked = pinetRegistrationGate.evaluateSessionStart(ctx);
|
|
1282
|
+
// Restore persisted thread state (always restore, even before /pinet)
|
|
1283
|
+
restorePersistedRuntimeState(ctx);
|
|
1284
|
+
if (pinetRegistrationBlocked) {
|
|
1285
|
+
console.log("[slack-bridge] detected local subagent context; skipping Pinet registration");
|
|
1286
|
+
currentRuntimeMode = "off";
|
|
1287
|
+
setExtStatus(ctx, "off");
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
refreshSettings();
|
|
1291
|
+
maybeWarnSlackUserAccess(ctx);
|
|
1292
|
+
const startupMode = resolveSlackBridgeStartupRuntimeMode(settings, {
|
|
1293
|
+
brokerSocketExists: fs.existsSync(DEFAULT_SOCKET_PATH),
|
|
1294
|
+
brokerManagedFollowerLaunch: isBrokerManagedFollowerLaunch(),
|
|
1295
|
+
});
|
|
1296
|
+
try {
|
|
1297
|
+
await transitionToRuntimeMode(ctx, startupMode);
|
|
1298
|
+
if (startupMode === "single") {
|
|
1299
|
+
maybeWarnSlackGuardrailPosture(ctx);
|
|
1300
|
+
console.log("[slack-bridge] runtime mode: single");
|
|
1301
|
+
}
|
|
1302
|
+
else if (startupMode === "follower") {
|
|
1303
|
+
console.log("[slack-bridge] runtime mode: follower");
|
|
1304
|
+
}
|
|
1305
|
+
else if (startupMode === "broker") {
|
|
1306
|
+
console.log("[slack-bridge] runtime mode: broker");
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
catch (err) {
|
|
1310
|
+
console.error(`[slack-bridge] runtime start (${startupMode}) failed: ${msg(err)}`);
|
|
1311
|
+
currentRuntimeMode = "off";
|
|
1312
|
+
setExtStatus(ctx, "off");
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
// ─── Agent event wiring ──────────────────────────────
|
|
1316
|
+
agentEventRuntime.register(pi);
|
|
1317
|
+
pi.on("session_compact", async (_event, ctx) => {
|
|
1318
|
+
maybeDrainInboxIfIdle(ctx);
|
|
1319
|
+
});
|
|
1320
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
1321
|
+
resetRemoteControlState();
|
|
1322
|
+
resetPendingRemoteControlAcks();
|
|
1323
|
+
sessionUiRuntime.cleanupForSessionShutdown();
|
|
1324
|
+
await stopPinetRuntime(ctx, { releaseIdentity: true });
|
|
1325
|
+
pinetRegistrationGate.reset();
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
function msg(err) {
|
|
1329
|
+
return err instanceof Error ? err.message : String(err);
|
|
1330
|
+
}
|