@openacp/cli 0.6.10 → 2026.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +34 -16
  2. package/dist/cli.d.ts +11 -0
  3. package/dist/cli.js +27787 -467
  4. package/dist/cli.js.map +1 -1
  5. package/dist/data/registry-snapshot.json +1 -1
  6. package/dist/index.d.ts +1944 -463
  7. package/dist/index.js +17365 -102
  8. package/dist/index.js.map +1 -1
  9. package/package.json +13 -7
  10. package/dist/action-detect-P7ZE4NEM.js +0 -16
  11. package/dist/action-detect-P7ZE4NEM.js.map +0 -1
  12. package/dist/adapter-ZOANORGM.js +0 -799
  13. package/dist/adapter-ZOANORGM.js.map +0 -1
  14. package/dist/admin-6SYB6XCZ.js +0 -23
  15. package/dist/admin-6SYB6XCZ.js.map +0 -1
  16. package/dist/agent-catalog-FC3HGDEQ.js +0 -11
  17. package/dist/agent-catalog-FC3HGDEQ.js.map +0 -1
  18. package/dist/agent-dependencies-4OWBMZWZ.js +0 -24
  19. package/dist/agent-dependencies-4OWBMZWZ.js.map +0 -1
  20. package/dist/agent-registry-WT4NXPYG.js +0 -9
  21. package/dist/agent-registry-WT4NXPYG.js.map +0 -1
  22. package/dist/agent-store-VZLFPTZU.js +0 -9
  23. package/dist/agent-store-VZLFPTZU.js.map +0 -1
  24. package/dist/agents-QO7DKARJ.js +0 -15
  25. package/dist/agents-QO7DKARJ.js.map +0 -1
  26. package/dist/api-client-CFQT5U7D.js +0 -14
  27. package/dist/api-client-CFQT5U7D.js.map +0 -1
  28. package/dist/autostart-X33OGMX6.js +0 -23
  29. package/dist/autostart-X33OGMX6.js.map +0 -1
  30. package/dist/chunk-2CJ46J3C.js +0 -154
  31. package/dist/chunk-2CJ46J3C.js.map +0 -1
  32. package/dist/chunk-2HMQOC7N.js +0 -134
  33. package/dist/chunk-2HMQOC7N.js.map +0 -1
  34. package/dist/chunk-33RP6K2O.js +0 -435
  35. package/dist/chunk-33RP6K2O.js.map +0 -1
  36. package/dist/chunk-34M4OS5P.js +0 -83
  37. package/dist/chunk-34M4OS5P.js.map +0 -1
  38. package/dist/chunk-4CTX774K.js +0 -265
  39. package/dist/chunk-4CTX774K.js.map +0 -1
  40. package/dist/chunk-7QJS2XBD.js +0 -92
  41. package/dist/chunk-7QJS2XBD.js.map +0 -1
  42. package/dist/chunk-BNLGTZ34.js +0 -122
  43. package/dist/chunk-BNLGTZ34.js.map +0 -1
  44. package/dist/chunk-CS3KCJ5D.js +0 -4788
  45. package/dist/chunk-CS3KCJ5D.js.map +0 -1
  46. package/dist/chunk-GAK6PIBW.js +0 -224
  47. package/dist/chunk-GAK6PIBW.js.map +0 -1
  48. package/dist/chunk-I7WC6E5S.js +0 -71
  49. package/dist/chunk-I7WC6E5S.js.map +0 -1
  50. package/dist/chunk-J4SJTKIK.js +0 -203
  51. package/dist/chunk-J4SJTKIK.js.map +0 -1
  52. package/dist/chunk-JHYXKVV2.js +0 -183
  53. package/dist/chunk-JHYXKVV2.js.map +0 -1
  54. package/dist/chunk-JKBFUAJK.js +0 -282
  55. package/dist/chunk-JKBFUAJK.js.map +0 -1
  56. package/dist/chunk-KIRH7TUJ.js +0 -219
  57. package/dist/chunk-KIRH7TUJ.js.map +0 -1
  58. package/dist/chunk-LBIKITQT.js +0 -22
  59. package/dist/chunk-LBIKITQT.js.map +0 -1
  60. package/dist/chunk-LCRLAV4G.js +0 -1085
  61. package/dist/chunk-LCRLAV4G.js.map +0 -1
  62. package/dist/chunk-LGP2YGRL.js +0 -4880
  63. package/dist/chunk-LGP2YGRL.js.map +0 -1
  64. package/dist/chunk-MKHUZLII.js +0 -738
  65. package/dist/chunk-MKHUZLII.js.map +0 -1
  66. package/dist/chunk-NAMYZIS5.js +0 -1
  67. package/dist/chunk-NAMYZIS5.js.map +0 -1
  68. package/dist/chunk-NVPG6JCL.js +0 -724
  69. package/dist/chunk-NVPG6JCL.js.map +0 -1
  70. package/dist/chunk-O7CPGUAI.js +0 -298
  71. package/dist/chunk-O7CPGUAI.js.map +0 -1
  72. package/dist/chunk-OWP7RZ62.js +0 -697
  73. package/dist/chunk-OWP7RZ62.js.map +0 -1
  74. package/dist/chunk-S64CB6J3.js +0 -98
  75. package/dist/chunk-S64CB6J3.js.map +0 -1
  76. package/dist/chunk-UKT3G5IA.js +0 -484
  77. package/dist/chunk-UKT3G5IA.js.map +0 -1
  78. package/dist/chunk-V5GZQEIY.js +0 -101
  79. package/dist/chunk-V5GZQEIY.js.map +0 -1
  80. package/dist/chunk-VOIJ6OY4.js +0 -63
  81. package/dist/chunk-VOIJ6OY4.js.map +0 -1
  82. package/dist/chunk-VUNV25KB.js +0 -16
  83. package/dist/chunk-VUNV25KB.js.map +0 -1
  84. package/dist/chunk-W3EYKZNQ.js +0 -45
  85. package/dist/chunk-W3EYKZNQ.js.map +0 -1
  86. package/dist/chunk-WTZDAYZX.js +0 -172
  87. package/dist/chunk-WTZDAYZX.js.map +0 -1
  88. package/dist/chunk-XANPHG7W.js +0 -145
  89. package/dist/chunk-XANPHG7W.js.map +0 -1
  90. package/dist/config-6S355X75.js +0 -15
  91. package/dist/config-6S355X75.js.map +0 -1
  92. package/dist/config-editor-QQTZMWGD.js +0 -13
  93. package/dist/config-editor-QQTZMWGD.js.map +0 -1
  94. package/dist/config-registry-AHYI4MYL.js +0 -18
  95. package/dist/config-registry-AHYI4MYL.js.map +0 -1
  96. package/dist/daemon-4CS6HMB5.js +0 -30
  97. package/dist/daemon-4CS6HMB5.js.map +0 -1
  98. package/dist/discord-OMC52Y54.js +0 -2239
  99. package/dist/discord-OMC52Y54.js.map +0 -1
  100. package/dist/dist-UHQK5CXN.js +0 -21151
  101. package/dist/dist-UHQK5CXN.js.map +0 -1
  102. package/dist/doctor-HZZ5BSHB.js +0 -10
  103. package/dist/doctor-HZZ5BSHB.js.map +0 -1
  104. package/dist/doctor-OLYBO3V3.js +0 -15
  105. package/dist/doctor-OLYBO3V3.js.map +0 -1
  106. package/dist/install-cloudflared-Z7VCGOVG.js +0 -33
  107. package/dist/install-cloudflared-Z7VCGOVG.js.map +0 -1
  108. package/dist/install-jq-HUYSQWKR.js +0 -32
  109. package/dist/install-jq-HUYSQWKR.js.map +0 -1
  110. package/dist/integrate-PNEHRY2I.js +0 -373
  111. package/dist/integrate-PNEHRY2I.js.map +0 -1
  112. package/dist/log-NXABYJTT.js +0 -24
  113. package/dist/log-NXABYJTT.js.map +0 -1
  114. package/dist/main-XOZCLFUK.js +0 -238
  115. package/dist/main-XOZCLFUK.js.map +0 -1
  116. package/dist/menu-YY5MKHEK.js +0 -16
  117. package/dist/menu-YY5MKHEK.js.map +0 -1
  118. package/dist/new-session-FEO4J4VU.js +0 -17
  119. package/dist/new-session-FEO4J4VU.js.map +0 -1
  120. package/dist/post-upgrade-CJG5I7M2.js +0 -80
  121. package/dist/post-upgrade-CJG5I7M2.js.map +0 -1
  122. package/dist/session-IUSI7P5S.js +0 -20
  123. package/dist/session-IUSI7P5S.js.map +0 -1
  124. package/dist/settings-RQPAM4KC.js +0 -14
  125. package/dist/settings-RQPAM4KC.js.map +0 -1
  126. package/dist/setup-XHS4OMPM.js +0 -37
  127. package/dist/setup-XHS4OMPM.js.map +0 -1
  128. package/dist/suggest-7D6B542M.js +0 -38
  129. package/dist/suggest-7D6B542M.js.map +0 -1
  130. package/dist/tunnel-service-CJLUH6SZ.js +0 -1174
  131. package/dist/tunnel-service-CJLUH6SZ.js.map +0 -1
  132. package/dist/version-NQZBM5M7.js +0 -16
  133. package/dist/version-NQZBM5M7.js.map +0 -1
@@ -1,799 +0,0 @@
1
- import "./chunk-LGP2YGRL.js";
2
- import {
3
- ChannelAdapter
4
- } from "./chunk-LBIKITQT.js";
5
- import "./chunk-NAMYZIS5.js";
6
- import "./chunk-WTZDAYZX.js";
7
- import "./chunk-UKT3G5IA.js";
8
- import "./chunk-34M4OS5P.js";
9
- import "./chunk-MKHUZLII.js";
10
- import "./chunk-2CJ46J3C.js";
11
- import "./chunk-LCRLAV4G.js";
12
- import "./chunk-JKBFUAJK.js";
13
- import "./chunk-VOIJ6OY4.js";
14
- import "./chunk-JHYXKVV2.js";
15
- import "./chunk-33RP6K2O.js";
16
- import {
17
- createChildLogger
18
- } from "./chunk-GAK6PIBW.js";
19
- import "./chunk-VUNV25KB.js";
20
-
21
- // src/adapters/slack/adapter.ts
22
- import fs from "fs";
23
- import { App } from "@slack/bolt";
24
- import { WebClient } from "@slack/web-api";
25
-
26
- // src/adapters/slack/send-queue.ts
27
- import PQueue from "p-queue";
28
- var METHOD_RPM = {
29
- "chat.postMessage": 50,
30
- // Tier 3
31
- "chat.update": 50,
32
- // Tier 3
33
- "conversations.create": 20,
34
- // Tier 2
35
- "conversations.rename": 20,
36
- // Tier 2
37
- "conversations.archive": 20,
38
- // Tier 2
39
- "conversations.invite": 20,
40
- // Tier 2
41
- "conversations.join": 20,
42
- // Tier 2
43
- "conversations.unarchive": 20,
44
- // Tier 2
45
- "conversations.info": 50
46
- // Tier 3
47
- };
48
- var SlackSendQueue = class {
49
- constructor(client) {
50
- this.client = client;
51
- for (const [method, rpm] of Object.entries(METHOD_RPM)) {
52
- this.queues.set(method, new PQueue({
53
- interval: Math.ceil(6e4 / rpm),
54
- intervalCap: 1,
55
- carryoverConcurrencyCount: true
56
- }));
57
- }
58
- }
59
- queues = /* @__PURE__ */ new Map();
60
- async enqueue(method, params) {
61
- const queue = this.queues.get(method);
62
- if (!queue) throw new Error(`Unknown Slack method: ${method}`);
63
- return queue.add(() => this.client.apiCall(method, params));
64
- }
65
- };
66
-
67
- // src/adapters/slack/utils.ts
68
- function isAudioClip(file) {
69
- return file.mimetype === "video/mp4" && file.name?.startsWith("audio_message") || file.mimetype?.startsWith("audio/");
70
- }
71
- var SECTION_LIMIT = 3e3;
72
- function splitSafe(text, limit = SECTION_LIMIT) {
73
- if (text.length <= limit) return [text];
74
- const chunks = [];
75
- let remaining = text;
76
- while (remaining.length > 0) {
77
- if (remaining.length <= limit) {
78
- chunks.push(remaining);
79
- break;
80
- }
81
- let cut = remaining.lastIndexOf("\n", limit);
82
- if (cut <= 0) cut = limit;
83
- chunks.push(remaining.slice(0, cut));
84
- remaining = remaining.slice(cut).trimStart();
85
- }
86
- return chunks;
87
- }
88
-
89
- // src/adapters/slack/formatter.ts
90
- function markdownToMrkdwn(text) {
91
- return text.replace(/^#{1,6}\s+(.+)$/gm, "\0BOLD\0$1\0BOLD\0").replace(/\*\*(.+?)\*\*/g, "\0BOLD\0$1\0BOLD\0").replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "_$1_").replace(/\x00BOLD\x00(.+?)\x00BOLD\x00/g, "*$1*").replace(/~~(.+?)~~/g, "~$1~").replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, "<$2|$1>").replace(/^[ \t]*[-*]\s+/gm, "\u2022 ").trim();
92
- }
93
- var SECTION_LIMIT2 = 3e3;
94
- function section(text) {
95
- return { type: "section", text: { type: "mrkdwn", text: text.slice(0, SECTION_LIMIT2) } };
96
- }
97
- function context(text) {
98
- return { type: "context", elements: [{ type: "mrkdwn", text }] };
99
- }
100
- var SlackFormatter = class {
101
- formatOutgoing(message) {
102
- switch (message.type) {
103
- case "text": {
104
- const text = message.text ?? "";
105
- if (!text.trim()) return [];
106
- const converted = markdownToMrkdwn(text);
107
- return splitSafe(converted).map((chunk) => section(chunk));
108
- }
109
- case "thought":
110
- return [context(`\u{1F4AD} _${(message.text ?? "").slice(0, 500)}_`)];
111
- case "tool_call": {
112
- const name = message.metadata?.name ?? "tool";
113
- const input = message.metadata?.input;
114
- const inputStr = input ? `
115
- \`\`\`
116
- ${JSON.stringify(input, null, 2).slice(0, 500)}
117
- \`\`\`` : "";
118
- return [context(`\u{1F527} \`${name}\`${inputStr}`)];
119
- }
120
- case "tool_update": {
121
- const name = message.metadata?.name ?? "tool";
122
- const status = message.metadata?.status ?? "done";
123
- const icon = status === "error" ? "\u274C" : "\u2705";
124
- return [context(`${icon} \`${name}\` \u2014 ${status}`)];
125
- }
126
- case "plan":
127
- return [
128
- { type: "divider" },
129
- section(`\u{1F4CB} *Plan*
130
- ${message.text ?? ""}`)
131
- ];
132
- case "usage": {
133
- const meta = message.metadata ?? {};
134
- const parts = [
135
- meta.input_tokens != null ? `in: ${meta.input_tokens}` : null,
136
- meta.output_tokens != null ? `out: ${meta.output_tokens}` : null,
137
- meta.cost_usd != null ? `$${Number(meta.cost_usd).toFixed(4)}` : null
138
- ].filter((p) => p !== null);
139
- return parts.length ? [context(`\u{1F4CA} ${parts.join(" \xB7 ")}`)] : [];
140
- }
141
- case "session_end":
142
- return this.formatSessionEnd(message.text);
143
- case "error":
144
- return [section(`\u26A0\uFE0F *Error:* ${message.text ?? "Unknown error"}`)];
145
- default:
146
- return [];
147
- }
148
- }
149
- formatPermissionRequest(req) {
150
- return [
151
- section(`\u{1F510} *Permission Request*
152
- ${req.description}`),
153
- {
154
- type: "actions",
155
- block_id: `perm_${req.id}`,
156
- elements: req.options.map((opt) => ({
157
- type: "button",
158
- text: { type: "plain_text", text: opt.label, emoji: true },
159
- value: `${req.id}:${opt.id}`,
160
- action_id: `perm_action_${opt.id}_${req.id}`,
161
- style: opt.isAllow ? "primary" : "danger"
162
- }))
163
- }
164
- ];
165
- }
166
- formatNotification(text) {
167
- return [section(text)];
168
- }
169
- formatSessionEnd(reason) {
170
- return [
171
- { type: "divider" },
172
- context(`\u2705 Session ended${reason ? ` \u2014 ${reason}` : ""}`)
173
- ];
174
- }
175
- };
176
-
177
- // src/adapters/slack/slug.ts
178
- import { customAlphabet } from "nanoid";
179
- var nanoidAlpha = customAlphabet("abcdefghijklmnopqrstuvwxyz0123456789", 4);
180
- function toSlug(name, prefix = "openacp") {
181
- const base = name.toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 60);
182
- const suffix = nanoidAlpha();
183
- return `${prefix}-${base}-${suffix}`.replace(/-+/g, "-");
184
- }
185
-
186
- // src/adapters/slack/channel-manager.ts
187
- var SlackChannelManager = class {
188
- constructor(queue, config) {
189
- this.queue = queue;
190
- this.config = config;
191
- }
192
- async createChannel(sessionId, sessionName) {
193
- let lastError;
194
- for (let attempt = 0; attempt < 3; attempt++) {
195
- const finalSlug = toSlug(sessionName, this.config.channelPrefix ?? "openacp");
196
- try {
197
- const res = await this.queue.enqueue(
198
- "conversations.create",
199
- { name: finalSlug, is_private: true }
200
- );
201
- const channelId = res.channel.id;
202
- const userIds = this.config.allowedUserIds ?? [];
203
- if (userIds.length > 0) {
204
- await this.queue.enqueue("conversations.invite", {
205
- channel: channelId,
206
- users: userIds.join(",")
207
- });
208
- }
209
- return { channelId, channelSlug: finalSlug };
210
- } catch (err) {
211
- if (err?.data?.error === "name_taken" && attempt < 2) {
212
- lastError = err;
213
- continue;
214
- }
215
- throw err;
216
- }
217
- }
218
- throw lastError;
219
- }
220
- async archiveChannel(channelId) {
221
- await this.queue.enqueue("conversations.archive", { channel: channelId });
222
- }
223
- async notifyChannel(text) {
224
- if (this.config.notificationChannelId) {
225
- await this.queue.enqueue("chat.postMessage", {
226
- channel: this.config.notificationChannelId,
227
- text
228
- });
229
- }
230
- }
231
- };
232
-
233
- // src/adapters/slack/permission-handler.ts
234
- var SlackPermissionHandler = class {
235
- constructor(queue, onResponse) {
236
- this.queue = queue;
237
- this.onResponse = onResponse;
238
- }
239
- pendingMessages = /* @__PURE__ */ new Map();
240
- trackPendingMessage(requestId, channelId, messageTs) {
241
- this.pendingMessages.set(requestId, { channelId, messageTs });
242
- }
243
- async cleanupSession(channelId) {
244
- for (const [requestId, info] of this.pendingMessages) {
245
- if (info.channelId !== channelId) continue;
246
- await this.queue.enqueue("chat.update", {
247
- channel: info.channelId,
248
- ts: info.messageTs,
249
- blocks: []
250
- });
251
- this.pendingMessages.delete(requestId);
252
- }
253
- }
254
- register(app) {
255
- app.action(
256
- /^perm_action_/,
257
- async ({ ack, body, action }) => {
258
- await ack();
259
- const value = action.value ?? "";
260
- const colonIdx = value.indexOf(":");
261
- if (colonIdx === -1) return;
262
- const requestId = value.slice(0, colonIdx);
263
- const optionId = value.slice(colonIdx + 1);
264
- this.onResponse(requestId, optionId);
265
- this.pendingMessages.delete(requestId);
266
- const message = body.message;
267
- if (message) {
268
- await this.queue.enqueue("chat.update", {
269
- channel: body.channel?.id ?? "",
270
- ts: message.ts,
271
- text: `\u2705 Permission response: *${optionId}*`,
272
- blocks: []
273
- });
274
- }
275
- }
276
- );
277
- }
278
- };
279
-
280
- // src/adapters/slack/event-router.ts
281
- var log = createChildLogger({ module: "slack-event-router" });
282
- var SlackEventRouter = class {
283
- constructor(sessionLookup, onIncoming, botUserId, notificationChannelId, onNewSession, config, globalAllowedUserIds = []) {
284
- this.sessionLookup = sessionLookup;
285
- this.onIncoming = onIncoming;
286
- this.botUserId = botUserId;
287
- this.notificationChannelId = notificationChannelId;
288
- this.onNewSession = onNewSession;
289
- this.config = config;
290
- this.globalAllowedUserIds = globalAllowedUserIds;
291
- }
292
- isAllowedUser(userId) {
293
- const slackAllowed = this.config.allowedUserIds ?? [];
294
- const allowed = slackAllowed.length > 0 ? slackAllowed : this.globalAllowedUserIds;
295
- if (allowed.length === 0) return true;
296
- return allowed.includes(userId);
297
- }
298
- register(app) {
299
- app.message(async ({ message }) => {
300
- log.debug({ message }, "Slack raw message event");
301
- const msg = message;
302
- if (msg.bot_id) return;
303
- const subtype = msg.subtype;
304
- if (subtype && subtype !== "file_share") return;
305
- const channelId = msg.channel;
306
- const text = msg.text ?? "";
307
- const userId = msg.user ?? "";
308
- const files = msg.files?.map((f) => ({
309
- id: f.id,
310
- name: f.name,
311
- mimetype: f.mimetype,
312
- size: f.size,
313
- url_private: f.url_private
314
- }));
315
- log.debug({ channelId, userId, text }, "Slack message received");
316
- if (userId === this.botUserId) return;
317
- if (!this.isAllowedUser(userId)) {
318
- log.warn({ userId }, "slack: message from non-allowed user rejected");
319
- return;
320
- }
321
- const session = this.sessionLookup(channelId);
322
- if (session) {
323
- log.debug({ channelId, sessionSlug: session.channelSlug }, "Routing to session");
324
- this.onIncoming(session.channelSlug, text, userId, files);
325
- return;
326
- }
327
- log.debug({ channelId, notificationChannelId: this.notificationChannelId }, "No session found for channel");
328
- if (this.notificationChannelId && channelId === this.notificationChannelId) {
329
- this.onNewSession(text, userId);
330
- return;
331
- }
332
- });
333
- }
334
- };
335
-
336
- // src/adapters/slack/text-buffer.ts
337
- var log2 = createChildLogger({ module: "slack-text-buffer" });
338
- var FLUSH_IDLE_MS = 2e3;
339
- var SlackTextBuffer = class {
340
- constructor(channelId, sessionId, queue) {
341
- this.channelId = channelId;
342
- this.sessionId = sessionId;
343
- this.queue = queue;
344
- }
345
- buffer = "";
346
- timer;
347
- flushPromise;
348
- lastMessageTs;
349
- lastPostedText;
350
- append(text) {
351
- if (!text) return;
352
- this.buffer += text;
353
- this.resetTimer();
354
- }
355
- resetTimer() {
356
- if (this.timer) clearTimeout(this.timer);
357
- this.timer = setTimeout(() => {
358
- this.timer = void 0;
359
- this.flush().catch((err) => log2.error({ err, sessionId: this.sessionId }, "Text buffer flush error"));
360
- }, FLUSH_IDLE_MS);
361
- }
362
- async flush() {
363
- if (this.flushPromise) return this.flushPromise;
364
- const text = this.buffer.trim();
365
- if (!text) return;
366
- this.buffer = "";
367
- if (this.timer) {
368
- clearTimeout(this.timer);
369
- this.timer = void 0;
370
- }
371
- this.flushPromise = (async () => {
372
- try {
373
- const converted = markdownToMrkdwn(text);
374
- const chunks = splitSafe(converted);
375
- for (const chunk of chunks) {
376
- if (!chunk.trim()) continue;
377
- const result = await this.queue.enqueue("chat.postMessage", {
378
- channel: this.channelId,
379
- text: chunk,
380
- blocks: [{ type: "section", text: { type: "mrkdwn", text: chunk } }]
381
- });
382
- this.lastMessageTs = result?.ts;
383
- this.lastPostedText = chunk;
384
- }
385
- } finally {
386
- this.flushPromise = void 0;
387
- if (this.buffer.trim()) {
388
- await this.flush();
389
- }
390
- }
391
- })();
392
- return this.flushPromise;
393
- }
394
- destroy() {
395
- if (this.timer) {
396
- clearTimeout(this.timer);
397
- this.timer = void 0;
398
- }
399
- this.buffer = "";
400
- }
401
- /** Remove [TTS]...[/TTS] blocks — from buffer if unflushed, or edit posted message */
402
- async stripTtsBlock() {
403
- if (/\[TTS\][\s\S]*?\[\/TTS\]/.test(this.buffer)) {
404
- this.buffer = this.buffer.replace(/\[TTS\][\s\S]*?\[\/TTS\]/g, "").replace(/\s{2,}/g, " ").trim();
405
- return;
406
- }
407
- if (this.lastMessageTs && this.lastPostedText && /\[TTS\][\s\S]*?\[\/TTS\]/.test(this.lastPostedText)) {
408
- const cleaned = this.lastPostedText.replace(/\[TTS\][\s\S]*?\[\/TTS\]/g, "").replace(/\s{2,}/g, " ").trim();
409
- if (cleaned) {
410
- await this.queue.enqueue("chat.update", {
411
- channel: this.channelId,
412
- ts: this.lastMessageTs,
413
- text: cleaned,
414
- blocks: [{ type: "section", text: { type: "mrkdwn", text: cleaned } }]
415
- });
416
- }
417
- this.lastPostedText = cleaned;
418
- }
419
- }
420
- };
421
-
422
- // src/adapters/slack/adapter.ts
423
- var log3 = createChildLogger({ module: "slack" });
424
- var SlackAdapter = class extends ChannelAdapter {
425
- app;
426
- webClient;
427
- queue;
428
- formatter;
429
- channelManager;
430
- permissionHandler;
431
- eventRouter;
432
- sessions = /* @__PURE__ */ new Map();
433
- textBuffers = /* @__PURE__ */ new Map();
434
- botUserId = "";
435
- slackConfig;
436
- fileService;
437
- constructor(core, config) {
438
- super(core, config);
439
- this.slackConfig = config;
440
- this.formatter = new SlackFormatter();
441
- }
442
- async start() {
443
- const { botToken, appToken, signingSecret } = this.slackConfig;
444
- if (!botToken || !appToken || !signingSecret) {
445
- throw new Error("Slack adapter requires botToken, appToken, and signingSecret");
446
- }
447
- this.app = new App({
448
- token: botToken,
449
- appToken,
450
- signingSecret,
451
- socketMode: true
452
- });
453
- this.webClient = new WebClient(botToken);
454
- this.queue = new SlackSendQueue(this.webClient);
455
- this.fileService = this.core.fileService;
456
- const authResult = await this.webClient.auth.test();
457
- if (!authResult.user_id) {
458
- throw new Error("Slack auth.test() did not return user_id \u2014 verify botToken is valid");
459
- }
460
- this.botUserId = authResult.user_id;
461
- log3.info({ botUserId: this.botUserId }, "Slack bot authenticated");
462
- this.channelManager = new SlackChannelManager(this.queue, this.slackConfig);
463
- this.permissionHandler = new SlackPermissionHandler(
464
- this.queue,
465
- (requestId, optionId) => {
466
- for (const [sessionId, _meta] of this.sessions) {
467
- const session = this.core.sessionManager.getSession(sessionId);
468
- if (session && session.permissionGate.requestId === requestId) {
469
- session.permissionGate.resolve(optionId);
470
- log3.info({ sessionId, requestId, optionId }, "Permission resolved");
471
- return;
472
- }
473
- }
474
- log3.warn({ requestId, optionId }, "No matching session found for permission response");
475
- }
476
- );
477
- this.permissionHandler.register(this.app);
478
- this.eventRouter = new SlackEventRouter(
479
- (slackChannelId) => {
480
- for (const meta of this.sessions.values()) {
481
- if (meta.channelId === slackChannelId) return meta;
482
- }
483
- return void 0;
484
- },
485
- (sessionChannelSlug, text, userId, files) => {
486
- const processFiles = async () => {
487
- if (!files?.length) return void 0;
488
- const audioFiles = files.filter((f) => isAudioClip(f));
489
- if (!audioFiles.length) return void 0;
490
- const attachments = [];
491
- for (const file of audioFiles) {
492
- const buffer = await this.downloadSlackFile(file.url_private);
493
- if (!buffer) continue;
494
- const mimeType = file.mimetype === "video/mp4" ? "audio/mp4" : file.mimetype;
495
- const sessionId = this.core.sessionManager.getSessionByThread("slack", sessionChannelSlug)?.id;
496
- if (!sessionId) continue;
497
- const att = await this.fileService.saveFile(sessionId, file.name, buffer, mimeType);
498
- attachments.push(att);
499
- }
500
- return attachments.length > 0 ? attachments : void 0;
501
- };
502
- processFiles().then((attachments) => {
503
- this.core.handleMessage({
504
- channelId: "slack",
505
- threadId: sessionChannelSlug,
506
- userId,
507
- text,
508
- attachments
509
- }).catch((err) => log3.error({ err }, "handleMessage error"));
510
- }).catch((err) => log3.error({ err }, "Failed to process audio files"));
511
- },
512
- this.botUserId,
513
- this.slackConfig.notificationChannelId,
514
- // onNewSession: reply with guidance when user messages the notification channel
515
- async (_text, _userId) => {
516
- if (this.slackConfig.notificationChannelId) {
517
- await this.queue.enqueue("chat.postMessage", {
518
- channel: this.slackConfig.notificationChannelId,
519
- text: "\u{1F4AC} To start a new session, use the `/openacp-new` slash command in any channel."
520
- }).catch((err) => log3.warn({ err }, "Failed to send onNewSession reply"));
521
- }
522
- },
523
- this.slackConfig,
524
- this.core.configManager.get().security.allowedUserIds
525
- );
526
- this.eventRouter.register(this.app);
527
- await this.app.start();
528
- log3.info("Slack adapter started (Socket Mode)");
529
- if (this.slackConfig.autoCreateSession !== false) {
530
- await this._createStartupSession();
531
- }
532
- }
533
- async downloadSlackFile(url) {
534
- try {
535
- const resp = await fetch(url, {
536
- headers: { Authorization: `Bearer ${this.slackConfig.botToken}` }
537
- });
538
- if (!resp.ok) {
539
- log3.warn({ status: resp.status }, "Failed to download Slack file");
540
- return null;
541
- }
542
- const contentType = resp.headers.get("content-type") ?? "";
543
- if (contentType.includes("text/html")) {
544
- log3.warn("Slack file download returned HTML instead of binary \u2014 bot likely missing files:read scope. Reinstall the Slack app with files:read scope.");
545
- return null;
546
- }
547
- return Buffer.from(await resp.arrayBuffer());
548
- } catch (err) {
549
- log3.error({ err }, "Error downloading Slack file");
550
- return null;
551
- }
552
- }
553
- async uploadAudioFile(channelId, att) {
554
- const fileBuffer = await fs.promises.readFile(att.filePath);
555
- await this.webClient.files.uploadV2({
556
- channel_id: channelId,
557
- file: fileBuffer,
558
- filename: att.fileName
559
- });
560
- }
561
- async _createStartupSession() {
562
- try {
563
- let reuseChannelId = this.slackConfig.startupChannelId;
564
- if (reuseChannelId) {
565
- try {
566
- const info = await this.queue.enqueue(
567
- "conversations.info",
568
- { channel: reuseChannelId }
569
- );
570
- const channel = info?.channel;
571
- if (!channel || typeof channel.is_archived !== "boolean") {
572
- log3.warn({ reuseChannelId }, "Unexpected conversations.info response shape, creating new channel");
573
- reuseChannelId = void 0;
574
- } else if (channel.is_archived) {
575
- await this.queue.enqueue("conversations.unarchive", { channel: reuseChannelId });
576
- log3.info({ channelId: reuseChannelId }, "Unarchived startup channel for reuse");
577
- }
578
- } catch {
579
- reuseChannelId = void 0;
580
- }
581
- }
582
- if (reuseChannelId) {
583
- let hasSession = false;
584
- for (const m of this.sessions.values()) {
585
- if (m.channelId === reuseChannelId) {
586
- hasSession = true;
587
- break;
588
- }
589
- }
590
- if (!hasSession) {
591
- const session = await this.core.handleNewSession("slack", void 0, void 0, { createThread: false });
592
- const slug = `startup-${session.id.slice(0, 8)}`;
593
- this.sessions.set(session.id, { channelId: reuseChannelId, channelSlug: slug });
594
- session.threadId = slug;
595
- await this.core.sessionManager.patchRecord(session.id, {
596
- platform: { topicId: slug }
597
- });
598
- log3.info({ sessionId: session.id, channelId: reuseChannelId }, "Reused startup channel");
599
- }
600
- } else {
601
- const session = await this.core.handleNewSession("slack", void 0, void 0, { createThread: true });
602
- if (!session.threadId) {
603
- log3.error({ sessionId: session.id }, "Startup session created without threadId");
604
- return;
605
- }
606
- const meta = this.sessions.get(session.id);
607
- if (meta) {
608
- await this.core.configManager.save(
609
- { channels: { slack: { startupChannelId: meta.channelId } } }
610
- );
611
- log3.info({ sessionId: session.id, channelId: meta.channelId }, "Saved startup channel to config");
612
- }
613
- }
614
- if (this.slackConfig.notificationChannelId) {
615
- const startupMeta = [...this.sessions.values()].find(
616
- (m) => m.channelId === (reuseChannelId ?? this.slackConfig.startupChannelId)
617
- );
618
- if (startupMeta) {
619
- await this.queue.enqueue("chat.postMessage", {
620
- channel: this.slackConfig.notificationChannelId,
621
- text: `\u2705 OpenACP ready \u2014 chat with the agent in <#${startupMeta.channelId}>`
622
- });
623
- }
624
- }
625
- } catch (err) {
626
- log3.error({ err }, "Failed to create/reuse Slack startup session");
627
- }
628
- }
629
- async stop() {
630
- for (const [sessionId, buf] of this.textBuffers) {
631
- try {
632
- await buf.flush();
633
- } catch (err) {
634
- log3.warn({ err, sessionId }, "Flush failed during stop");
635
- }
636
- buf.destroy();
637
- }
638
- this.textBuffers.clear();
639
- await this.app.stop();
640
- log3.info("Slack adapter stopped");
641
- }
642
- // --- ChannelAdapter implementations ---
643
- async createSessionThread(sessionId, name) {
644
- const meta = await this.channelManager.createChannel(sessionId, name);
645
- this.sessions.set(sessionId, meta);
646
- log3.info({ sessionId, channelId: meta.channelId, slug: meta.channelSlug }, "Session channel created");
647
- return meta.channelSlug;
648
- }
649
- async renameSessionThread(sessionId, newName) {
650
- const meta = this.sessions.get(sessionId);
651
- if (!meta) return;
652
- const newSlug = toSlug(newName, this.slackConfig.channelPrefix ?? "openacp");
653
- try {
654
- await this.queue.enqueue("conversations.rename", {
655
- channel: meta.channelId,
656
- name: newSlug
657
- });
658
- meta.channelSlug = newSlug;
659
- const session = this.core.sessionManager.getSession(sessionId);
660
- if (session) session.threadId = newSlug;
661
- const existingRecord = this.core.sessionManager.getSessionRecord(sessionId);
662
- await this.core.sessionManager.patchRecord(sessionId, {
663
- name: newName,
664
- platform: { ...existingRecord?.platform ?? {}, topicId: newSlug }
665
- });
666
- log3.info({ sessionId, newSlug }, "Session channel renamed");
667
- } catch (err) {
668
- log3.warn({ err, sessionId }, "Failed to rename Slack channel");
669
- }
670
- }
671
- async deleteSessionThread(sessionId) {
672
- const meta = this.sessions.get(sessionId);
673
- if (!meta) return;
674
- try {
675
- await this.permissionHandler.cleanupSession(meta.channelId);
676
- } catch (err) {
677
- log3.warn({ err, sessionId }, "Failed to clean up permission buttons");
678
- }
679
- try {
680
- await this.channelManager.archiveChannel(meta.channelId);
681
- log3.info({ sessionId, channelId: meta.channelId }, "Session channel archived");
682
- } catch (err) {
683
- log3.warn({ err, sessionId }, "Failed to archive Slack channel");
684
- }
685
- this.sessions.delete(sessionId);
686
- const buf = this.textBuffers.get(sessionId);
687
- if (buf) {
688
- buf.destroy();
689
- this.textBuffers.delete(sessionId);
690
- }
691
- }
692
- getTextBuffer(sessionId, channelId) {
693
- let buf = this.textBuffers.get(sessionId);
694
- if (!buf) {
695
- buf = new SlackTextBuffer(channelId, sessionId, this.queue);
696
- this.textBuffers.set(sessionId, buf);
697
- }
698
- return buf;
699
- }
700
- async sendMessage(sessionId, content) {
701
- const meta = this.sessions.get(sessionId);
702
- if (!meta) {
703
- log3.warn({ sessionId }, "No Slack channel for session, skipping message");
704
- return;
705
- }
706
- if (content.type === "text") {
707
- const buf = this.getTextBuffer(sessionId, meta.channelId);
708
- buf.append(content.text ?? "");
709
- return;
710
- }
711
- if (content.type === "session_end" || content.type === "error") {
712
- const buf = this.textBuffers.get(sessionId);
713
- if (buf) {
714
- try {
715
- await buf.flush();
716
- } catch (err) {
717
- log3.warn({ err, sessionId }, "Flush failed on session_end");
718
- }
719
- buf.destroy();
720
- this.textBuffers.delete(sessionId);
721
- }
722
- }
723
- if (content.type === "attachment" && content.attachment) {
724
- if (content.attachment.type === "audio") {
725
- try {
726
- await this.uploadAudioFile(meta.channelId, content.attachment);
727
- const buf = this.textBuffers.get(sessionId);
728
- if (buf) await buf.stripTtsBlock();
729
- } catch (err) {
730
- log3.error({ err, sessionId }, "Failed to upload audio to Slack");
731
- }
732
- }
733
- return;
734
- }
735
- const blocks = this.formatter.formatOutgoing(content);
736
- if (blocks.length === 0) return;
737
- try {
738
- await this.queue.enqueue("chat.postMessage", {
739
- channel: meta.channelId,
740
- text: content.text ?? content.type,
741
- blocks
742
- });
743
- } catch (err) {
744
- log3.error({ err, sessionId, type: content.type }, "Failed to post Slack message");
745
- }
746
- }
747
- // NOTE: Async flow — different from Telegram adapter.
748
- // Telegram: sendPermissionRequest awaits user response inline.
749
- // Slack: posts interactive buttons and returns immediately.
750
- // Resolution happens asynchronously via the Bolt action handler in
751
- // SlackPermissionHandler, which calls the PermissionResponseCallback
752
- // passed during construction. The callback iterates sessions to find
753
- // the matching permissionGate and resolves it.
754
- async sendPermissionRequest(sessionId, request) {
755
- const meta = this.sessions.get(sessionId);
756
- if (!meta) return;
757
- log3.info({ sessionId, requestId: request.id }, "Sending Slack permission request");
758
- const blocks = this.formatter.formatPermissionRequest(request);
759
- try {
760
- const result = await this.queue.enqueue("chat.postMessage", {
761
- channel: meta.channelId,
762
- text: `Permission request: ${request.description}`,
763
- blocks
764
- });
765
- const ts = result?.ts;
766
- if (ts) {
767
- this.permissionHandler.trackPendingMessage(request.id, meta.channelId, ts);
768
- }
769
- } catch (err) {
770
- log3.error({ err, sessionId }, "Failed to post Slack permission request");
771
- }
772
- }
773
- async sendNotification(notification) {
774
- if (!this.slackConfig.notificationChannelId) return;
775
- const emoji = {
776
- completed: "\u2705",
777
- error: "\u274C",
778
- permission: "\u{1F510}",
779
- input_required: "\u{1F4AC}"
780
- };
781
- const icon = emoji[notification.type] ?? "\u2139\uFE0F";
782
- const text = `${icon} *${notification.sessionName ?? "Session"}*
783
- ${notification.summary}`;
784
- const blocks = this.formatter.formatNotification(text);
785
- try {
786
- await this.queue.enqueue("chat.postMessage", {
787
- channel: this.slackConfig.notificationChannelId,
788
- text,
789
- blocks
790
- });
791
- } catch (err) {
792
- log3.warn({ err, sessionId: notification.sessionId }, "Failed to send Slack notification");
793
- }
794
- }
795
- };
796
- export {
797
- SlackAdapter
798
- };
799
- //# sourceMappingURL=adapter-ZOANORGM.js.map