@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,533 @@
1
+ import { buildPinetOwnerToken, buildPinetSkinAssignment, DEFAULT_PINET_SKIN_THEME, normalizePinetSkinTheme, resolvePinetMeshAuth, syncBrokerInboxEntries, } from "./helpers.js";
2
+ import { startBroker } from "./broker/index.js";
3
+ import { DEFAULT_HEARTBEAT_TIMEOUT_MS } from "./broker/socket-server.js";
4
+ import { MessageRouter } from "./broker/router.js";
5
+ import { DEFAULT_BROKER_MAINTENANCE_INTERVAL_MS, DEFAULT_BUSY_ASSIGNMENT_AGE_MS, runBrokerMaintenancePass, } from "./broker/maintenance.js";
6
+ import { HEARTBEAT_INTERVAL_MS } from "./broker/client.js";
7
+ import { isBrokerInboxIdTracked, markBrokerInboxIdsHandled, queueBrokerInboxIds, resetBrokerDeliveryState, } from "./broker-delivery.js";
8
+ import {} from "./activity-log.js";
9
+ import { clearRalphLoopSnooze, createRalphLoopState, getRalphLoopSnoozeStatus, setRalphLoopSnooze, startRalphLoop, stopRalphLoop, } from "./ralph-loop.js";
10
+ import { TtlCache } from "./ttl-cache.js";
11
+ import { createBrokerGhostReaper } from "./broker/ghost-reaper.js";
12
+ import { buildPinetRuntimeAdapterBindings, connectPinetRuntimeAdapters, } from "./pinet-runtime-composition.js";
13
+ function normalizeOptionalSetting(value) {
14
+ const trimmed = value?.trim();
15
+ return trimmed && trimmed.length > 0 ? trimmed : null;
16
+ }
17
+ export function resolveConfiguredBrokerSkinTheme(settings) {
18
+ return normalizePinetSkinTheme(settings.skinTheme) ?? DEFAULT_PINET_SKIN_THEME;
19
+ }
20
+ export function createBrokerRuntime(deps) {
21
+ let activeBroker = null;
22
+ let activeRouter = null;
23
+ let activeSelfId = null;
24
+ let brokerHeartbeatTimer = null;
25
+ let brokerMaintenanceTimer = null;
26
+ let brokerScheduledWakeupTimer = null;
27
+ let brokerMaintenanceRunning = false;
28
+ let brokerScheduledWakeupRunning = false;
29
+ let lastBrokerMaintenance = null;
30
+ let lastBrokerMaintenanceSignature = "";
31
+ let activityLogger = null;
32
+ let activityLogContext = null;
33
+ let lastActivityLogFailureAt = 0;
34
+ const brokerControlPlaneHomeTabViewers = new TtlCache({
35
+ maxSize: 100,
36
+ ttlMs: 12 * 60 * 60 * 1000,
37
+ });
38
+ let lastBrokerControlPlaneHomeTabSnapshot = null;
39
+ let lastBrokerControlPlaneHomeTabRefreshAt = null;
40
+ let lastBrokerControlPlaneHomeTabError = null;
41
+ const ghostReaper = createBrokerGhostReaper({
42
+ brokerAgentId: () => activeSelfId,
43
+ getAgentById: (agentId) => activeBroker?.db.getAgentById(agentId) ?? null,
44
+ });
45
+ const ralphLoopState = createRalphLoopState();
46
+ function ensureActivityLogger() {
47
+ if (activityLogger) {
48
+ return activityLogger;
49
+ }
50
+ activityLogger = deps.createActivityLogger((error) => {
51
+ const formatted = deps.formatError(error);
52
+ console.error(`[slack-bridge] activity log failed: ${formatted}`);
53
+ const now = Date.now();
54
+ if (!activityLogContext?.hasUI || now - lastActivityLogFailureAt < 60_000) {
55
+ return;
56
+ }
57
+ lastActivityLogFailureAt = now;
58
+ activityLogContext.ui.notify(`Pinet activity log failed: ${formatted}`, "warning");
59
+ });
60
+ return activityLogger;
61
+ }
62
+ function stopBrokerHeartbeat() {
63
+ if (!brokerHeartbeatTimer)
64
+ return;
65
+ clearInterval(brokerHeartbeatTimer);
66
+ brokerHeartbeatTimer = null;
67
+ }
68
+ function startBrokerHeartbeat() {
69
+ stopBrokerHeartbeat();
70
+ if (!activeBroker || !activeSelfId)
71
+ return;
72
+ const broker = activeBroker;
73
+ const selfId = activeSelfId;
74
+ brokerHeartbeatTimer = setInterval(() => {
75
+ try {
76
+ broker.db.heartbeatAgent(selfId);
77
+ }
78
+ catch {
79
+ /* best effort */
80
+ }
81
+ }, HEARTBEAT_INTERVAL_MS);
82
+ brokerHeartbeatTimer.unref?.();
83
+ }
84
+ function stopBrokerMaintenance() {
85
+ if (!brokerMaintenanceTimer)
86
+ return;
87
+ clearInterval(brokerMaintenanceTimer);
88
+ brokerMaintenanceTimer = null;
89
+ }
90
+ function syncBrokerDbInbox(agentId, db, ctx) {
91
+ db.recoverPendingTargetedBacklog(agentId);
92
+ const pending = db
93
+ .getInbox(agentId)
94
+ .filter((item) => !isBrokerInboxIdTracked(deps.deliveryState, item.entry.id));
95
+ if (pending.length === 0) {
96
+ return;
97
+ }
98
+ const synced = syncBrokerInboxEntries(pending.map((item) => ({
99
+ inboxId: item.entry.id,
100
+ message: {
101
+ threadId: item.message.threadId,
102
+ sender: typeof item.message.metadata?.senderAgent === "string"
103
+ ? item.message.metadata.senderAgent
104
+ : item.message.sender,
105
+ body: item.message.body,
106
+ createdAt: item.message.createdAt,
107
+ metadata: item.message.metadata,
108
+ },
109
+ })));
110
+ const handledInboxIds = new Set();
111
+ const commandsToStart = [];
112
+ for (const entry of synced.controlEntries) {
113
+ try {
114
+ const queued = deps.requestRemoteControl(entry.command, ctx);
115
+ if (queued.ackDisposition === "immediate") {
116
+ handledInboxIds.add(entry.inboxId);
117
+ }
118
+ else {
119
+ deps.deferControlAck(queued.scheduledCommand, entry.inboxId);
120
+ }
121
+ if (queued.shouldStartNow) {
122
+ commandsToStart.push(entry.command);
123
+ }
124
+ }
125
+ catch (error) {
126
+ ctx.ui.notify(`Pinet remote control failed: ${deps.formatError(error)}`, "error");
127
+ }
128
+ }
129
+ if (handledInboxIds.size > 0) {
130
+ db.markDelivered([...handledInboxIds], agentId);
131
+ }
132
+ for (const command of commandsToStart) {
133
+ deps.runRemoteControl(command, ctx);
134
+ }
135
+ if (synced.inboxMessages.length === 0) {
136
+ return;
137
+ }
138
+ queueBrokerInboxIds(deps.deliveryState, synced.inboxMessages.flatMap((message) => message.brokerInboxId != null ? [message.brokerInboxId] : []));
139
+ deps.pushInboxMessages(synced.inboxMessages);
140
+ deps.updateBadge();
141
+ deps.maybeDrainInboxIfIdle(ctx);
142
+ }
143
+ function runMaintenance(ctx) {
144
+ if (!activeBroker || !activeSelfId || brokerMaintenanceRunning)
145
+ return;
146
+ brokerMaintenanceRunning = true;
147
+ try {
148
+ const result = runBrokerMaintenancePass(activeBroker.db, {
149
+ brokerAgentId: activeSelfId,
150
+ staleAfterMs: DEFAULT_HEARTBEAT_TIMEOUT_MS,
151
+ busyAssignmentAgeMs: DEFAULT_BUSY_ASSIGNMENT_AGE_MS,
152
+ });
153
+ const staleAgents = result.reapedAgentIds
154
+ .map((agentId) => activeBroker?.db.getAgentById(agentId) ?? null)
155
+ .filter((agent) => agent !== null);
156
+ const processReap = ghostReaper.reapGhosts(staleAgents);
157
+ if (processReap.signaledAgentIds.length > 0) {
158
+ result.anomalies.push(`signaled ${processReap.signaledAgentIds.length} broker-managed ghost process${processReap.signaledAgentIds.length === 1 ? "" : "es"}`);
159
+ }
160
+ syncBrokerDbInbox(activeSelfId, activeBroker.db, ctx);
161
+ lastBrokerMaintenance = result;
162
+ const previousSignature = lastBrokerMaintenanceSignature;
163
+ const signature = result.anomalies.join("|");
164
+ deps.onMaintenanceResult(ctx, { result, previousSignature, signature });
165
+ lastBrokerMaintenanceSignature = signature;
166
+ }
167
+ catch (error) {
168
+ deps.onMaintenanceError(ctx, error);
169
+ }
170
+ finally {
171
+ brokerMaintenanceRunning = false;
172
+ }
173
+ }
174
+ function startBrokerMaintenance(ctx) {
175
+ stopBrokerMaintenance();
176
+ brokerMaintenanceTimer = setInterval(() => {
177
+ runMaintenance(ctx);
178
+ }, DEFAULT_BROKER_MAINTENANCE_INTERVAL_MS);
179
+ brokerMaintenanceTimer.unref?.();
180
+ runMaintenance(ctx);
181
+ }
182
+ function runBrokerScheduledWakeups(ctx) {
183
+ if (!activeBroker || brokerScheduledWakeupRunning)
184
+ return;
185
+ brokerScheduledWakeupRunning = true;
186
+ try {
187
+ const deliveries = activeBroker.db.deliverDueScheduledWakeups();
188
+ if (deliveries.length > 0 && activeSelfId) {
189
+ syncBrokerDbInbox(activeSelfId, activeBroker.db, ctx);
190
+ }
191
+ }
192
+ catch (error) {
193
+ deps.onScheduledWakeupError(ctx, error);
194
+ }
195
+ finally {
196
+ brokerScheduledWakeupRunning = false;
197
+ }
198
+ }
199
+ function stopBrokerScheduledWakeups() {
200
+ brokerScheduledWakeupRunning = false;
201
+ if (!brokerScheduledWakeupTimer)
202
+ return;
203
+ clearInterval(brokerScheduledWakeupTimer);
204
+ brokerScheduledWakeupTimer = null;
205
+ }
206
+ function startBrokerScheduledWakeups(ctx) {
207
+ stopBrokerScheduledWakeups();
208
+ brokerScheduledWakeupTimer = setInterval(() => {
209
+ runBrokerScheduledWakeups(ctx);
210
+ }, 1000);
211
+ brokerScheduledWakeupTimer.unref?.();
212
+ runBrokerScheduledWakeups(ctx);
213
+ }
214
+ function getRalphLoopDeps() {
215
+ return {
216
+ getBrokerDb: () => activeBroker?.db ?? null,
217
+ getBrokerAgentId: () => activeSelfId,
218
+ heartbeatTimerActive: () => brokerHeartbeatTimer != null,
219
+ maintenanceTimerActive: () => brokerMaintenanceTimer != null,
220
+ runMaintenance: (ctx) => runMaintenance(ctx),
221
+ sendMaintenanceMessage: (targetAgentId, body) => deps.sendMaintenanceMessage(targetAgentId, body),
222
+ trySendFollowUp: (body, onDelivered) => deps.trySendFollowUp(body, onDelivered),
223
+ logActivity: (entry) => {
224
+ if (!activeBroker) {
225
+ return;
226
+ }
227
+ ensureActivityLogger().log(entry);
228
+ },
229
+ formatTrackedAgent: (agentId) => deps.formatTrackedAgent(agentId),
230
+ summarizeTrackedAssignmentStatus: (status, prNumber, branch) => deps.summarizeTrackedAssignmentStatus(status, prNumber, branch),
231
+ refreshHomeTabs: (ctx, snapshot, refreshedAt) => deps.refreshHomeTabs(ctx, snapshot, refreshedAt),
232
+ getLastMaintenance: () => lastBrokerMaintenance,
233
+ getSettings: () => deps.getSettings(),
234
+ buildControlPlaneDashboardSnapshot: (input) => deps.buildControlPlaneDashboardSnapshot(input),
235
+ setLastHomeTabSnapshot: (snapshot) => {
236
+ lastBrokerControlPlaneHomeTabSnapshot = snapshot;
237
+ },
238
+ getLastHomeTabError: () => lastBrokerControlPlaneHomeTabError,
239
+ setLastHomeTabError: (error) => {
240
+ lastBrokerControlPlaneHomeTabError = error;
241
+ },
242
+ };
243
+ }
244
+ function startObservability(ctx) {
245
+ if (!activeBroker) {
246
+ return;
247
+ }
248
+ activityLogContext = ctx;
249
+ startRalphLoop(ctx, ralphLoopState, getRalphLoopDeps());
250
+ }
251
+ function stopObservability() {
252
+ stopRalphLoop(ralphLoopState);
253
+ brokerControlPlaneHomeTabViewers.clear();
254
+ lastBrokerControlPlaneHomeTabSnapshot = null;
255
+ lastBrokerControlPlaneHomeTabRefreshAt = null;
256
+ lastBrokerControlPlaneHomeTabError = null;
257
+ activityLogger?.clearPending();
258
+ activityLogContext = null;
259
+ }
260
+ async function publishCurrentHomeTabSafely(userId, ctx, openedAt = new Date().toISOString()) {
261
+ if (!activeBroker) {
262
+ return false;
263
+ }
264
+ activityLogContext = ctx;
265
+ brokerControlPlaneHomeTabViewers.set(userId, { openedAt });
266
+ try {
267
+ const snapshot = (await deps.buildCurrentDashboardSnapshot(openedAt)) ??
268
+ lastBrokerControlPlaneHomeTabSnapshot;
269
+ if (!snapshot) {
270
+ return false;
271
+ }
272
+ await deps.refreshHomeTabs(ctx, snapshot, openedAt, [userId]);
273
+ return true;
274
+ }
275
+ catch (error) {
276
+ const homeTabMessage = `Pinet Home tab publish failed: ${deps.formatError(error)}`;
277
+ if (homeTabMessage !== lastBrokerControlPlaneHomeTabError) {
278
+ ctx.ui.notify(homeTabMessage, "warning");
279
+ }
280
+ lastBrokerControlPlaneHomeTabError = homeTabMessage;
281
+ return false;
282
+ }
283
+ }
284
+ async function disconnect(options = {}) {
285
+ stopObservability();
286
+ stopBrokerHeartbeat();
287
+ stopBrokerMaintenance();
288
+ stopBrokerScheduledWakeups();
289
+ ghostReaper.dispose();
290
+ brokerMaintenanceRunning = false;
291
+ brokerScheduledWakeupRunning = false;
292
+ if (activeBroker) {
293
+ try {
294
+ if (options.releaseIdentity && activeSelfId) {
295
+ activeBroker.db.unregisterAgent(activeSelfId);
296
+ }
297
+ await activeBroker.stop();
298
+ }
299
+ catch {
300
+ /* best effort */
301
+ }
302
+ }
303
+ activeBroker = null;
304
+ activeRouter = null;
305
+ activeSelfId = null;
306
+ lastBrokerMaintenance = null;
307
+ lastBrokerMaintenanceSignature = "";
308
+ resetBrokerDeliveryState(deps.deliveryState);
309
+ }
310
+ return {
311
+ async connect(ctx) {
312
+ const settings = deps.getSettings();
313
+ const meshAuth = resolvePinetMeshAuth(settings);
314
+ const allowedUsers = deps.getAllowedUsers();
315
+ const broker = await startBroker({
316
+ ...(meshAuth.meshSecret ? { meshSecret: meshAuth.meshSecret } : {}),
317
+ ...(meshAuth.meshSecretPath ? { meshSecretPath: meshAuth.meshSecretPath } : {}),
318
+ });
319
+ let selfId = null;
320
+ activityLogContext = ctx;
321
+ try {
322
+ broker.db.setAllowedUsers(allowedUsers);
323
+ const router = new MessageRouter(broker.db);
324
+ const persistedBrokerStableId = normalizeOptionalSetting(broker.db.getSetting("pinet.brokerStableId")) ??
325
+ broker.db
326
+ .getAllAgents()
327
+ .flatMap((agent) => {
328
+ const stableId = normalizeOptionalSetting(agent.stableId);
329
+ if (!stableId) {
330
+ return [];
331
+ }
332
+ if (deps.getMeshRoleFromMetadata(agent.metadata ?? undefined, "worker") !== "broker") {
333
+ return [];
334
+ }
335
+ const lastSeenMs = Date.parse(agent.lastSeen);
336
+ const connectedAtMs = Date.parse(agent.connectedAt);
337
+ const recencyMs = Number.isNaN(lastSeenMs)
338
+ ? Number.isNaN(connectedAtMs)
339
+ ? 0
340
+ : connectedAtMs
341
+ : lastSeenMs;
342
+ return [{ stableId, recencyMs }];
343
+ })
344
+ .sort((left, right) => right.recencyMs - left.recencyMs)[0]?.stableId ??
345
+ null;
346
+ const brokerStableId = persistedBrokerStableId ?? deps.getBrokerStableId();
347
+ deps.setBrokerStableId(brokerStableId);
348
+ broker.db.setSetting("pinet.brokerStableId", brokerStableId);
349
+ deps.setAgentOwnerToken(buildPinetOwnerToken(brokerStableId));
350
+ const activeSkinTheme = resolveConfiguredBrokerSkinTheme(deps.getSettings());
351
+ deps.setActiveSkinTheme(activeSkinTheme);
352
+ broker.db.setSetting("pinet.skinTheme", activeSkinTheme);
353
+ broker.server.setAgentRegistrationResolver((registration) => {
354
+ const theme = deps.getActiveSkinTheme() ?? DEFAULT_PINET_SKIN_THEME;
355
+ const assignment = buildPinetSkinAssignment({
356
+ theme,
357
+ role: deps.getMeshRoleFromMetadata(registration.metadata, "worker"),
358
+ seed: registration.stableId ?? registration.agentId,
359
+ });
360
+ return {
361
+ name: assignment.name,
362
+ emoji: assignment.emoji,
363
+ metadata: deps.buildSkinMetadata(registration.metadata, assignment.personality, assignment.statusVocabulary),
364
+ };
365
+ });
366
+ const selfAssignment = buildPinetSkinAssignment({
367
+ theme: activeSkinTheme,
368
+ role: "broker",
369
+ seed: brokerStableId,
370
+ });
371
+ const selfAgent = broker.db.registerAgent(ctx.sessionManager.getLeafId() ?? `broker-${process.pid}`, selfAssignment.name, selfAssignment.emoji, process.pid, deps.buildSkinMetadata(await deps.getAgentMetadata("broker"), selfAssignment.personality, selfAssignment.statusVocabulary), brokerStableId);
372
+ selfId = selfAgent.id;
373
+ deps.applyLocalAgentIdentity(selfAgent.name, selfAgent.emoji, selfAssignment.personality);
374
+ const brokerSelfId = selfId;
375
+ const adapterBindings = await buildPinetRuntimeAdapterBindings(deps.createAdapterBindings, {
376
+ broker,
377
+ router,
378
+ selfId: brokerSelfId,
379
+ ctx,
380
+ });
381
+ const adapterConnectResult = await connectPinetRuntimeAdapters({
382
+ broker,
383
+ bindings: adapterBindings,
384
+ onInbound: (message) => {
385
+ void deps.handleInboundMessage({ message, broker, router, selfId: brokerSelfId, ctx });
386
+ },
387
+ });
388
+ activeBroker = broker;
389
+ activeRouter = router;
390
+ activeSelfId = brokerSelfId;
391
+ resetBrokerDeliveryState(deps.deliveryState);
392
+ const releasedBrokerClaims = broker.db.releaseThreadClaims(brokerSelfId);
393
+ const recoveredTargetedBacklogCount = broker.db.recoverPendingTargetedBacklog(brokerSelfId);
394
+ const recoveredBrokerMessages = broker.db.getPendingInboxCount(brokerSelfId);
395
+ syncBrokerDbInbox(brokerSelfId, broker.db, ctx);
396
+ broker.server.onAgentMessage((targetAgentId) => {
397
+ if (targetAgentId !== brokerSelfId)
398
+ return;
399
+ syncBrokerDbInbox(brokerSelfId, broker.db, ctx);
400
+ });
401
+ broker.server.onAgentStatusChange((changedAgentId, status) => {
402
+ if (status === "idle") {
403
+ runMaintenance(ctx);
404
+ }
405
+ deps.onAgentStatusChange(ctx, changedAgentId, status);
406
+ });
407
+ startBrokerHeartbeat();
408
+ startBrokerMaintenance(ctx);
409
+ startBrokerScheduledWakeups(ctx);
410
+ return {
411
+ botUserId: adapterConnectResult.botUserId,
412
+ recoveredBrokerMessages,
413
+ recoveredTargetedBacklogCount,
414
+ releasedBrokerClaims,
415
+ };
416
+ }
417
+ catch (error) {
418
+ try {
419
+ if (selfId) {
420
+ broker.db.unregisterAgent(selfId);
421
+ }
422
+ }
423
+ catch {
424
+ /* best effort */
425
+ }
426
+ try {
427
+ await broker.stop();
428
+ }
429
+ catch {
430
+ /* best effort */
431
+ }
432
+ activeBroker = null;
433
+ activeRouter = null;
434
+ activeSelfId = null;
435
+ lastBrokerMaintenance = null;
436
+ lastBrokerMaintenanceSignature = "";
437
+ resetBrokerDeliveryState(deps.deliveryState);
438
+ stopObservability();
439
+ throw error;
440
+ }
441
+ },
442
+ async disconnect(options = {}) {
443
+ await disconnect(options);
444
+ },
445
+ claimThread(threadTs, channelId, source = "slack") {
446
+ if (!activeRouter || !activeSelfId) {
447
+ return;
448
+ }
449
+ activeRouter.claimThread(threadTs, activeSelfId, channelId, source);
450
+ },
451
+ runMaintenance(ctx) {
452
+ runMaintenance(ctx);
453
+ },
454
+ markDelivered(inboxIds) {
455
+ if (!activeBroker || !activeSelfId || inboxIds.length === 0) {
456
+ return;
457
+ }
458
+ activeBroker.db.markDelivered(inboxIds, activeSelfId);
459
+ markBrokerInboxIdsHandled(deps.deliveryState, inboxIds);
460
+ },
461
+ startObservability(ctx) {
462
+ startObservability(ctx);
463
+ },
464
+ clearFollowUpPending() {
465
+ ralphLoopState.followUpPending = false;
466
+ },
467
+ logActivity(entry) {
468
+ if (!activeBroker) {
469
+ return;
470
+ }
471
+ ensureActivityLogger().log(entry);
472
+ },
473
+ getRecentActivityEntries(limit = 20) {
474
+ return activityLogger?.getRecentEntries(limit) ?? [];
475
+ },
476
+ getBroker() {
477
+ return activeBroker;
478
+ },
479
+ getSelfId() {
480
+ return activeSelfId;
481
+ },
482
+ getLastMaintenance() {
483
+ return lastBrokerMaintenance;
484
+ },
485
+ getRalphSnoozeStatus() {
486
+ return getRalphLoopSnoozeStatus(ralphLoopState);
487
+ },
488
+ snoozeRalphLoop(input) {
489
+ return setRalphLoopSnooze(ralphLoopState, {
490
+ durationMs: input.durationMs,
491
+ reason: input.reason,
492
+ source: "manual",
493
+ });
494
+ },
495
+ clearRalphSnooze() {
496
+ clearRalphLoopSnooze(ralphLoopState);
497
+ return getRalphLoopSnoozeStatus(ralphLoopState);
498
+ },
499
+ heartbeatTimerActive() {
500
+ return brokerHeartbeatTimer != null;
501
+ },
502
+ maintenanceTimerActive() {
503
+ return brokerMaintenanceTimer != null;
504
+ },
505
+ isConnected() {
506
+ return activeBroker != null;
507
+ },
508
+ getHomeTabViewerIds() {
509
+ return [...brokerControlPlaneHomeTabViewers.entries()].map(([userId]) => userId);
510
+ },
511
+ getLastHomeTabSnapshot() {
512
+ return lastBrokerControlPlaneHomeTabSnapshot;
513
+ },
514
+ setLastHomeTabSnapshot(snapshot) {
515
+ lastBrokerControlPlaneHomeTabSnapshot = snapshot;
516
+ },
517
+ getLastHomeTabRefreshAt() {
518
+ return lastBrokerControlPlaneHomeTabRefreshAt;
519
+ },
520
+ setLastHomeTabRefreshAt(value) {
521
+ lastBrokerControlPlaneHomeTabRefreshAt = value;
522
+ },
523
+ getLastHomeTabError() {
524
+ return lastBrokerControlPlaneHomeTabError;
525
+ },
526
+ setLastHomeTabError(value) {
527
+ lastBrokerControlPlaneHomeTabError = value;
528
+ },
529
+ async publishCurrentHomeTabSafely(userId, ctx, openedAt = new Date().toISOString()) {
530
+ return publishCurrentHomeTabSafely(userId, ctx, openedAt);
531
+ },
532
+ };
533
+ }
@@ -0,0 +1,10 @@
1
+ import type { ThreadOwnerHint } from "./broker/router.js";
2
+ import { type SlackCall } from "./slack-access.js";
3
+ export interface BrokerThreadOwnerHintsDeps {
4
+ slack: SlackCall;
5
+ getBotToken: () => string;
6
+ }
7
+ export interface BrokerThreadOwnerHints {
8
+ resolveBrokerThreadOwnerHint: (channel: string, threadTs: string) => Promise<ThreadOwnerHint | null>;
9
+ }
10
+ export declare function createBrokerThreadOwnerHints(deps: BrokerThreadOwnerHintsDeps): BrokerThreadOwnerHints;
@@ -0,0 +1,14 @@
1
+ import { resolveSlackThreadOwnerHint } from "./slack-access.js";
2
+ export function createBrokerThreadOwnerHints(deps) {
3
+ async function resolveBrokerThreadOwnerHint(channel, threadTs) {
4
+ return resolveSlackThreadOwnerHint({
5
+ slack: deps.slack,
6
+ token: deps.getBotToken(),
7
+ channel,
8
+ threadTs,
9
+ });
10
+ }
11
+ return {
12
+ resolveBrokerThreadOwnerHint,
13
+ };
14
+ }
@@ -0,0 +1,80 @@
1
+ export interface SlackCanvasDocumentContent {
2
+ type: "markdown";
3
+ markdown: string;
4
+ }
5
+ export type SlackCanvasCreateMethod = "canvases.create" | "conversations.canvases.create";
6
+ export type SlackCanvasCreateKind = "standalone" | "channel";
7
+ export type SlackCanvasUpdateMode = "append" | "prepend" | "replace";
8
+ export type SlackCanvasSectionType = "h1" | "h2" | "h3" | "any_header";
9
+ export type SlackCanvasEditOperation = "insert_at_end" | "insert_at_start" | "replace";
10
+ export interface SlackCanvasCommentRecord {
11
+ id?: string;
12
+ userId?: string;
13
+ createdTs?: string;
14
+ text: string;
15
+ }
16
+ export interface SlackCanvasCommentsPage {
17
+ canvasId: string;
18
+ title?: string;
19
+ permalink?: string;
20
+ commentsCount: number;
21
+ returnedCount: number;
22
+ page?: number;
23
+ pages?: number;
24
+ comments: SlackCanvasCommentRecord[];
25
+ nextCursor?: string;
26
+ }
27
+ export interface SlackCanvasCreateInput {
28
+ kind?: string;
29
+ title?: string;
30
+ markdown?: string;
31
+ channelId?: string;
32
+ }
33
+ export interface SlackCanvasCreateRequest {
34
+ kind: SlackCanvasCreateKind;
35
+ method: SlackCanvasCreateMethod;
36
+ body: {
37
+ title?: string;
38
+ channel_id?: string;
39
+ document_content?: SlackCanvasDocumentContent;
40
+ };
41
+ }
42
+ export interface SlackCanvasEditRequest {
43
+ canvas_id: string;
44
+ changes: [
45
+ {
46
+ operation: SlackCanvasEditOperation;
47
+ document_content: SlackCanvasDocumentContent;
48
+ section_id?: string;
49
+ }
50
+ ];
51
+ }
52
+ export interface SlackCanvasSectionLookupRequest {
53
+ canvas_id: string;
54
+ criteria: {
55
+ contains_text: string;
56
+ section_types?: SlackCanvasSectionType[];
57
+ };
58
+ }
59
+ export interface SlackCanvasSectionLookupResult {
60
+ id?: string;
61
+ }
62
+ export declare function normalizeSlackCanvasCreateKind(kind?: string): SlackCanvasCreateKind;
63
+ export declare function normalizeSlackCanvasUpdateMode(mode?: string): SlackCanvasUpdateMode;
64
+ export declare function normalizeSlackCanvasSectionType(type?: string): SlackCanvasSectionType | undefined;
65
+ export declare function normalizeSlackCanvasCommentsLimit(limit?: number): number;
66
+ export declare function buildSlackCanvasCreateRequest(input: SlackCanvasCreateInput): SlackCanvasCreateRequest;
67
+ export declare function buildSlackCanvasEditRequest(input: {
68
+ canvasId: string;
69
+ markdown: string;
70
+ mode?: string;
71
+ sectionId?: string;
72
+ }): SlackCanvasEditRequest;
73
+ export declare function buildSlackCanvasSectionsLookupRequest(input: {
74
+ canvasId: string;
75
+ containsText: string;
76
+ sectionType?: string;
77
+ }): SlackCanvasSectionLookupRequest;
78
+ export declare function pickSlackCanvasSectionId(sections: SlackCanvasSectionLookupResult[] | undefined, sectionIndex?: number): string;
79
+ export declare function extractSlackChannelCanvasId(response: Record<string, unknown>): string | null;
80
+ export declare function extractSlackCanvasCommentsPage(response: Record<string, unknown>, fallbackCanvasId?: string): SlackCanvasCommentsPage;