@gugu910/pi-slack-bridge 0.1.3

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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +299 -0
  3. package/dist/activity-log.js +304 -0
  4. package/dist/broker/adapters/slack.js +645 -0
  5. package/dist/broker/adapters/types.js +3 -0
  6. package/dist/broker/agent-messaging.js +154 -0
  7. package/dist/broker/auth.js +97 -0
  8. package/dist/broker/client.js +495 -0
  9. package/dist/broker/control-plane-canvas.js +357 -0
  10. package/dist/broker/index.js +125 -0
  11. package/dist/broker/leader.js +133 -0
  12. package/dist/broker/maintenance.js +135 -0
  13. package/dist/broker/paths.js +69 -0
  14. package/dist/broker/router.js +287 -0
  15. package/dist/broker/schema.js +1492 -0
  16. package/dist/broker/socket-server.js +665 -0
  17. package/dist/broker/types.js +12 -0
  18. package/dist/broker-delivery.js +34 -0
  19. package/dist/canvases.js +175 -0
  20. package/dist/deploy-manifest.js +238 -0
  21. package/dist/follower-delivery.js +83 -0
  22. package/dist/git-metadata.js +95 -0
  23. package/dist/guardrails.js +197 -0
  24. package/dist/helpers.js +2128 -0
  25. package/dist/home-tab.js +240 -0
  26. package/dist/index.js +3086 -0
  27. package/dist/pinet-commands.js +244 -0
  28. package/dist/ralph-loop.js +385 -0
  29. package/dist/reaction-triggers.js +160 -0
  30. package/dist/scheduled-wakeups.js +71 -0
  31. package/dist/slack-api.js +5 -0
  32. package/dist/slack-block-kit.js +425 -0
  33. package/dist/slack-export.js +214 -0
  34. package/dist/slack-modals.js +269 -0
  35. package/dist/slack-presence.js +98 -0
  36. package/dist/slack-socket-dedup.js +143 -0
  37. package/dist/slack-tools.js +1715 -0
  38. package/dist/slack-upload.js +147 -0
  39. package/dist/task-assignments.js +403 -0
  40. package/dist/ttl-cache.js +110 -0
  41. package/manifest.yaml +57 -0
  42. package/package.json +45 -0
@@ -0,0 +1,495 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.BrokerClient = exports.HEARTBEAT_INTERVAL_MS = exports.MAX_RECONNECT_DELAY_MS = exports.INITIAL_RECONNECT_DELAY_MS = exports.RECONNECT_DELAY_MS = exports.REQUEST_TIMEOUT_MS = exports.DEFAULT_SOCKET_PATH = void 0;
37
+ exports.computeReconnectDelay = computeReconnectDelay;
38
+ const net = __importStar(require("node:net"));
39
+ const auth_js_1 = require("./auth.js");
40
+ const paths_js_1 = require("./paths.js");
41
+ const types_js_1 = require("./types.js");
42
+ // ─── Constants (exported for testing) ────────────────────
43
+ exports.DEFAULT_SOCKET_PATH = paths_js_1.DEFAULT_SOCKET_PATH;
44
+ exports.REQUEST_TIMEOUT_MS = 5000;
45
+ exports.RECONNECT_DELAY_MS = 3000;
46
+ exports.INITIAL_RECONNECT_DELAY_MS = 1000;
47
+ exports.MAX_RECONNECT_DELAY_MS = 30000;
48
+ exports.HEARTBEAT_INTERVAL_MS = 5000;
49
+ /** Compute reconnect delay with exponential backoff and jitter. */
50
+ function computeReconnectDelay(attempt, random = Math.random()) {
51
+ const baseDelay = exports.INITIAL_RECONNECT_DELAY_MS * Math.pow(2, attempt);
52
+ const capped = Math.min(baseDelay, exports.MAX_RECONNECT_DELAY_MS);
53
+ // Add jitter: ±25%
54
+ const jitter = capped * (0.75 + random * 0.5);
55
+ return Math.round(jitter);
56
+ }
57
+ function createRpcRequestError(method, error) {
58
+ const err = new Error(error.message);
59
+ err.name = "BrokerRpcRequestError";
60
+ err.code = error.code;
61
+ err.data = error.data;
62
+ err.method = method;
63
+ return err;
64
+ }
65
+ function isRpcMethodNotFoundError(err, method) {
66
+ if (!(err instanceof Error)) {
67
+ return false;
68
+ }
69
+ const rpcErr = err;
70
+ if (rpcErr.method !== method) {
71
+ return false;
72
+ }
73
+ return rpcErr.code === types_js_1.RPC_METHOD_NOT_FOUND || err.message === `Unknown method: ${method}`;
74
+ }
75
+ function getMeshAuthCompatibilityError() {
76
+ return new Error("Broker does not support Pinet mesh auth (`auth`). Upgrade the broker or disable follower mesh auth when connecting to older/no-auth brokers.");
77
+ }
78
+ function getErrorCode(err) {
79
+ if (typeof err !== "object" || err == null || !("code" in err)) {
80
+ return null;
81
+ }
82
+ const code = err.code;
83
+ return typeof code === "string" ? code : null;
84
+ }
85
+ // ─── BrokerClient ────────────────────────────────────────
86
+ class BrokerClient {
87
+ connectOpts;
88
+ meshSecret;
89
+ meshSecretPath;
90
+ socket = null;
91
+ connected = false;
92
+ shuttingDown = false;
93
+ reconnectTimer = null;
94
+ heartbeatTimer = null;
95
+ disconnectHandler = null;
96
+ reconnectHandler = null;
97
+ reconnectAttempt = 0;
98
+ registrationSnapshot = null;
99
+ registeredIdentity = null;
100
+ nextId = 1;
101
+ pending = new Map();
102
+ buffer = "";
103
+ constructor(opts) {
104
+ if (opts === undefined) {
105
+ this.connectOpts = { path: exports.DEFAULT_SOCKET_PATH };
106
+ this.meshSecret = null;
107
+ this.meshSecretPath = null;
108
+ return;
109
+ }
110
+ if (typeof opts === "string") {
111
+ this.connectOpts = { path: opts };
112
+ this.meshSecret = null;
113
+ this.meshSecretPath = null;
114
+ return;
115
+ }
116
+ if ("path" in opts) {
117
+ this.connectOpts = { path: opts.path };
118
+ }
119
+ else {
120
+ this.connectOpts = { host: opts.host, port: opts.port };
121
+ }
122
+ const meshSecret = opts.meshSecret?.trim();
123
+ this.meshSecret = meshSecret && meshSecret.length > 0 ? meshSecret : null;
124
+ const meshSecretPath = opts.meshSecretPath?.trim();
125
+ this.meshSecretPath = meshSecretPath && meshSecretPath.length > 0 ? meshSecretPath : null;
126
+ }
127
+ // ─── Connection ──────────────────────────────────────
128
+ async connect() {
129
+ this.shuttingDown = false;
130
+ await this.connectSocket();
131
+ try {
132
+ await this.authenticateIfNeeded();
133
+ }
134
+ catch (err) {
135
+ this.disconnect();
136
+ throw err;
137
+ }
138
+ }
139
+ disconnect() {
140
+ this.shuttingDown = true;
141
+ if (this.reconnectTimer) {
142
+ clearTimeout(this.reconnectTimer);
143
+ this.reconnectTimer = null;
144
+ }
145
+ this.stopHeartbeat();
146
+ this.rejectAllPending(new Error("Client disconnected"));
147
+ try {
148
+ this.socket?.destroy();
149
+ }
150
+ catch {
151
+ /* ignore */
152
+ }
153
+ this.socket = null;
154
+ this.connected = false;
155
+ }
156
+ async disconnectGracefully() {
157
+ this.shuttingDown = true;
158
+ if (this.reconnectTimer) {
159
+ clearTimeout(this.reconnectTimer);
160
+ this.reconnectTimer = null;
161
+ }
162
+ this.stopHeartbeat();
163
+ try {
164
+ await this.unregister();
165
+ }
166
+ finally {
167
+ this.disconnect();
168
+ }
169
+ }
170
+ isConnected() {
171
+ return this.connected;
172
+ }
173
+ resolveMeshSecret() {
174
+ if (this.meshSecret) {
175
+ return this.meshSecret;
176
+ }
177
+ if (this.meshSecretPath) {
178
+ try {
179
+ return (0, auth_js_1.readMeshSecret)(this.meshSecretPath);
180
+ }
181
+ catch (err) {
182
+ if (getErrorCode(err) === "ENOENT") {
183
+ throw new Error(`Configured Pinet mesh secret file not found: ${this.meshSecretPath}. Set slack-bridge.meshSecretPath to an existing file, provide slack-bridge.meshSecret directly, or leave both unset to disable shared-secret auth.`);
184
+ }
185
+ throw err;
186
+ }
187
+ }
188
+ return null;
189
+ }
190
+ async authenticateIfNeeded() {
191
+ const meshSecret = this.resolveMeshSecret();
192
+ if (!meshSecret) {
193
+ return;
194
+ }
195
+ try {
196
+ await this.request("auth", { secret: meshSecret });
197
+ }
198
+ catch (err) {
199
+ if (isRpcMethodNotFoundError(err, "auth")) {
200
+ throw getMeshAuthCompatibilityError();
201
+ }
202
+ throw err;
203
+ }
204
+ }
205
+ // ─── Registration ────────────────────────────────────
206
+ async register(name, emoji, metadata, stableId) {
207
+ this.registrationSnapshot = {
208
+ name,
209
+ emoji,
210
+ ...(metadata ? { metadata } : {}),
211
+ ...(stableId ? { stableId } : {}),
212
+ };
213
+ return this.performRegister(this.registrationSnapshot);
214
+ }
215
+ async unregister() {
216
+ if (!this.connected)
217
+ return;
218
+ try {
219
+ await this.request("unregister");
220
+ }
221
+ finally {
222
+ this.stopHeartbeat();
223
+ this.registrationSnapshot = null;
224
+ this.registeredIdentity = null;
225
+ }
226
+ }
227
+ async heartbeat() {
228
+ await this.request("heartbeat");
229
+ }
230
+ // ─── Messaging ───────────────────────────────────────
231
+ async pollInbox() {
232
+ const result = (await this.request("inbox.poll"));
233
+ return result;
234
+ }
235
+ async ackMessages(ids) {
236
+ await this.request("inbox.ack", { ids });
237
+ }
238
+ async send(threadId, text, metadata) {
239
+ await this.request("send", { threadId, body: text, ...(metadata ? { metadata } : {}) });
240
+ }
241
+ // ─── Thread ownership ─────────────────────────────────
242
+ async claimThread(threadId, channel) {
243
+ const params = { threadId };
244
+ if (channel)
245
+ params.channel = channel;
246
+ const result = (await this.request("thread.claim", params));
247
+ return result;
248
+ }
249
+ async resolveThread(threadTs) {
250
+ const result = (await this.request("resolveThread", { threadTs }));
251
+ return typeof result.channelId === "string" ? result.channelId : null;
252
+ }
253
+ // ─── Status ────────────────────────────────────────────
254
+ async updateStatus(status) {
255
+ await this.request("status.update", { status });
256
+ }
257
+ // ─── Agent-to-agent messaging ─────────────────────────
258
+ async sendAgentMessage(target, body, metadata) {
259
+ const result = (await this.request("agent.message", {
260
+ targetAgent: target,
261
+ body,
262
+ ...(metadata ? { metadata } : {}),
263
+ }));
264
+ return result.messageId;
265
+ }
266
+ async sendAgentBroadcast(channel, body) {
267
+ const result = (await this.request("agent.broadcast", {
268
+ channel,
269
+ body,
270
+ }));
271
+ return {
272
+ channel: result.channel,
273
+ messageIds: result.messageIds,
274
+ recipients: result.recipients,
275
+ };
276
+ }
277
+ async scheduleWakeup(fireAt, body) {
278
+ const result = (await this.request("schedule.create", {
279
+ fireAt,
280
+ body,
281
+ }));
282
+ return {
283
+ id: result.id,
284
+ threadId: result.threadId,
285
+ fireAt: result.fireAt,
286
+ };
287
+ }
288
+ // ─── Queries ─────────────────────────────────────────
289
+ async listThreads() {
290
+ const result = (await this.request("threads.list"));
291
+ return result;
292
+ }
293
+ async listAgents(includeDisconnected = false) {
294
+ const result = (await this.request("agents.list", includeDisconnected ? { includeDisconnected: true } : undefined));
295
+ return result;
296
+ }
297
+ // ─── Slack proxy (read-through) ──────────────────────
298
+ async slackProxy(method, params) {
299
+ const result = (await this.request("slack.proxy", { method, params }));
300
+ return result;
301
+ }
302
+ // ─── Events ──────────────────────────────────────────
303
+ onDisconnect(handler) {
304
+ this.disconnectHandler = handler;
305
+ }
306
+ onReconnect(handler) {
307
+ this.reconnectHandler = handler;
308
+ }
309
+ getReconnectAttempt() {
310
+ return this.reconnectAttempt;
311
+ }
312
+ getRegisteredIdentity() {
313
+ return this.registeredIdentity ? { ...this.registeredIdentity } : null;
314
+ }
315
+ // ─── JSON-RPC transport ──────────────────────────────
316
+ request(method, params) {
317
+ if (!this.connected || !this.socket) {
318
+ return Promise.reject(new Error("Not connected to broker"));
319
+ }
320
+ const id = this.nextId++;
321
+ const msg = {
322
+ jsonrpc: "2.0",
323
+ id,
324
+ method,
325
+ ...(params ? { params } : {}),
326
+ };
327
+ return new Promise((resolve, reject) => {
328
+ const timer = setTimeout(() => {
329
+ this.pending.delete(id);
330
+ reject(new Error(`Request timed out: ${method}`));
331
+ }, exports.REQUEST_TIMEOUT_MS);
332
+ this.pending.set(id, { method, resolve, reject, timer });
333
+ const line = JSON.stringify(msg) + "\n";
334
+ this.socket.write(line, "utf-8", (err) => {
335
+ if (err) {
336
+ clearTimeout(timer);
337
+ this.pending.delete(id);
338
+ reject(err);
339
+ }
340
+ });
341
+ });
342
+ }
343
+ onData(chunk) {
344
+ this.buffer += chunk;
345
+ const lines = this.buffer.split("\n");
346
+ // Keep the last incomplete line in the buffer
347
+ this.buffer = lines.pop() ?? "";
348
+ for (const line of lines) {
349
+ if (!line.trim())
350
+ continue;
351
+ try {
352
+ const msg = JSON.parse(line);
353
+ this.handleResponse(msg);
354
+ }
355
+ catch {
356
+ /* malformed JSON — skip */
357
+ }
358
+ }
359
+ }
360
+ handleResponse(msg) {
361
+ const entry = this.pending.get(msg.id);
362
+ if (!entry)
363
+ return;
364
+ clearTimeout(entry.timer);
365
+ this.pending.delete(msg.id);
366
+ if (msg.error) {
367
+ entry.reject(createRpcRequestError(entry.method, msg.error));
368
+ }
369
+ else {
370
+ entry.resolve(msg.result);
371
+ }
372
+ }
373
+ // ─── Reconnect ──────────────────────────────────────
374
+ scheduleReconnect() {
375
+ if (this.shuttingDown || this.reconnectTimer)
376
+ return;
377
+ const delay = computeReconnectDelay(this.reconnectAttempt);
378
+ this.reconnectAttempt++;
379
+ this.reconnectTimer = setTimeout(() => {
380
+ this.reconnectTimer = null;
381
+ void this.reconnectOnce();
382
+ }, delay);
383
+ this.reconnectTimer.unref?.();
384
+ }
385
+ connectSocket() {
386
+ return new Promise((resolve, reject) => {
387
+ const sock = net.createConnection(this.connectOpts);
388
+ sock.on("connect", () => {
389
+ this.socket = sock;
390
+ this.connected = true;
391
+ this.buffer = "";
392
+ resolve();
393
+ });
394
+ sock.on("data", (chunk) => {
395
+ this.onData(chunk.toString("utf-8"));
396
+ });
397
+ sock.on("close", () => {
398
+ const wasConnected = this.connected;
399
+ this.connected = false;
400
+ this.socket = null;
401
+ this.stopHeartbeat();
402
+ this.rejectAllPending(new Error("Socket closed"));
403
+ if (wasConnected && !this.shuttingDown) {
404
+ this.disconnectHandler?.();
405
+ this.scheduleReconnect();
406
+ }
407
+ });
408
+ sock.on("error", (err) => {
409
+ if (!this.connected) {
410
+ reject(err);
411
+ }
412
+ // If already connected, the close event handles cleanup
413
+ });
414
+ });
415
+ }
416
+ async performRegister(snapshot) {
417
+ const result = (await this.request("register", {
418
+ name: snapshot.name,
419
+ emoji: snapshot.emoji,
420
+ pid: process.pid,
421
+ ...(snapshot.metadata ? { metadata: snapshot.metadata } : {}),
422
+ ...(snapshot.stableId ? { stableId: snapshot.stableId } : {}),
423
+ }));
424
+ this.registrationSnapshot = {
425
+ ...snapshot,
426
+ name: result.name,
427
+ emoji: result.emoji,
428
+ ...(result.metadata ? { metadata: result.metadata } : {}),
429
+ };
430
+ this.registeredIdentity = result;
431
+ this.startHeartbeat();
432
+ return result;
433
+ }
434
+ async reconnectOnce() {
435
+ try {
436
+ await this.connectSocket();
437
+ }
438
+ catch {
439
+ this.scheduleReconnect();
440
+ return;
441
+ }
442
+ try {
443
+ await this.authenticateIfNeeded();
444
+ if (this.registrationSnapshot) {
445
+ await this.performRegister(this.registrationSnapshot);
446
+ }
447
+ this.reconnectAttempt = 0;
448
+ this.reconnectHandler?.();
449
+ }
450
+ catch {
451
+ // Re-registration failed after the socket connected. Clear the connection
452
+ // state immediately instead of waiting for the async "close" event so the
453
+ // client cannot stay in a broken "connected but not registered" state.
454
+ // Then schedule the next reconnect attempt ourselves. (#139)
455
+ const failedSocket = this.socket;
456
+ this.socket = null;
457
+ this.connected = false;
458
+ this.buffer = "";
459
+ this.stopHeartbeat();
460
+ this.rejectAllPending(new Error("Socket closed"));
461
+ try {
462
+ failedSocket?.destroy();
463
+ }
464
+ catch {
465
+ /* ignore */
466
+ }
467
+ this.scheduleReconnect();
468
+ }
469
+ }
470
+ startHeartbeat() {
471
+ this.stopHeartbeat();
472
+ this.heartbeatTimer = setInterval(() => {
473
+ if (!this.connected)
474
+ return;
475
+ void this.heartbeat().catch(() => {
476
+ /* best effort */
477
+ });
478
+ }, exports.HEARTBEAT_INTERVAL_MS);
479
+ this.heartbeatTimer.unref?.();
480
+ }
481
+ stopHeartbeat() {
482
+ if (!this.heartbeatTimer)
483
+ return;
484
+ clearInterval(this.heartbeatTimer);
485
+ this.heartbeatTimer = null;
486
+ }
487
+ rejectAllPending(err) {
488
+ for (const [id, entry] of this.pending) {
489
+ clearTimeout(entry.timer);
490
+ entry.reject(err);
491
+ this.pending.delete(id);
492
+ }
493
+ }
494
+ }
495
+ exports.BrokerClient = BrokerClient;