@openacp/cli 2026.327.5 → 2026.328.2
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/README.md +13 -13
- package/dist/adapter-HGJENQCN.js +13 -0
- package/dist/agent-catalog-SZQQERV7.js +10 -0
- package/dist/{agent-dependencies-WS7Z2DFW.js → agent-dependencies-ED2ZTUHG.js} +1 -2
- package/dist/{agent-registry-5LZT7CUB.js → agent-registry-YOGP656W.js} +1 -2
- package/dist/agent-store-5UHZH2XI.js +8 -0
- package/dist/{api-client-AQPNKXI2.js → api-client-XTLRRFPX.js} +1 -2
- package/dist/api-server-DSUW637I.js +7 -0
- package/dist/api-server-WFB5K6FP.js +10 -0
- package/dist/{autostart-6JS565RY.js → autostart-CUPZMKKC.js} +3 -4
- package/dist/{chunk-WIIZNPCR.js → chunk-2KT6TROD.js} +12 -33
- package/dist/chunk-2KT6TROD.js.map +1 -0
- package/dist/{chunk-PPSMUECX.js → chunk-2R5XM3ES.js} +2 -2
- package/dist/{chunk-SNPYTMPR.js → chunk-3EWTPOF7.js} +2 -2
- package/dist/{chunk-YEULD3SG.js → chunk-3NAFXVQM.js} +7 -2
- package/dist/{chunk-YEULD3SG.js.map → chunk-3NAFXVQM.js.map} +1 -1
- package/dist/{chunk-QAQDGPB4.js → chunk-43JVXFYP.js} +3 -3
- package/dist/{chunk-KMMEFXIE.js → chunk-4B6PCWQP.js} +37 -9
- package/dist/chunk-4B6PCWQP.js.map +1 -0
- package/dist/{chunk-A6Y4GZM3.js → chunk-566W6INH.js} +2 -2
- package/dist/{chunk-ODUM3D6X.js → chunk-5HKQCYOI.js} +1 -39
- package/dist/chunk-5HKQCYOI.js.map +1 -0
- package/dist/{chunk-P2G275VD.js → chunk-5TCXYDLR.js} +3 -3
- package/dist/{chunk-XIBG7LSL.js → chunk-6VR4GWOO.js} +238 -108
- package/dist/chunk-6VR4GWOO.js.map +1 -0
- package/dist/{chunk-WXVT3AOY.js → chunk-7ZCQF6QM.js} +8 -3
- package/dist/chunk-7ZCQF6QM.js.map +1 -0
- package/dist/{chunk-S3ZGPPXY.js → chunk-E2SLHZAC.js} +8 -12
- package/dist/{chunk-S3ZGPPXY.js.map → chunk-E2SLHZAC.js.map} +1 -1
- package/dist/{chunk-RBYBSSGO.js → chunk-FCTC7KDT.js} +2 -2
- package/dist/{plugin-installer-QVJP6VKV.js → chunk-I53NEV3S.js} +6 -3
- package/dist/chunk-I53NEV3S.js.map +1 -0
- package/dist/{chunk-2YCW3QDV.js → chunk-IXMIC4GQ.js} +8 -7
- package/dist/chunk-IXMIC4GQ.js.map +1 -0
- package/dist/{chunk-BLQUXO7S.js → chunk-IZ5UEZF7.js} +27 -2
- package/dist/chunk-IZ5UEZF7.js.map +1 -0
- package/dist/{chunk-QVMEF6FB.js → chunk-JOMDPFQ2.js} +10 -24
- package/dist/chunk-JOMDPFQ2.js.map +1 -0
- package/dist/{chunk-4GMLGCF2.js → chunk-JUFN4XMB.js} +2 -2
- package/dist/{chunk-AD3X6DGK.js → chunk-NT6FYV27.js} +75 -13
- package/dist/chunk-NT6FYV27.js.map +1 -0
- package/dist/{chunk-HRKAXFWR.js → chunk-QBEQJFGL.js} +9 -9
- package/dist/{chunk-XMMAGAT4.js → chunk-R6KZYF7D.js} +8 -1
- package/dist/{chunk-XMMAGAT4.js.map → chunk-R6KZYF7D.js.map} +1 -1
- package/dist/{chunk-XWDW3XBE.js → chunk-RXMWJHWH.js} +687 -99
- package/dist/chunk-RXMWJHWH.js.map +1 -0
- package/dist/{chunk-SHTGQGAU.js → chunk-V2YZWYXT.js} +3 -3
- package/dist/{chunk-BQ6FR32N.js → chunk-VD3QSMVY.js} +2 -2
- package/dist/cli.js +103 -97
- package/dist/cli.js.map +1 -1
- package/dist/{config-I4FMCJGZ.js → config-UCAFCS5W.js} +3 -4
- package/dist/config-editor-OU6PUY66.js +10 -0
- package/dist/{config-registry-CUMNXFGK.js → config-registry-ZXAIJNYB.js} +2 -3
- package/dist/{context-XM6E22LM.js → context-7MPU7RL5.js} +1 -2
- package/dist/core-plugins-R2EVZAJV.js +22 -0
- package/dist/{daemon-PXO5QPCR.js → daemon-DTA6KYYY.js} +4 -5
- package/dist/{dev-loader-DRU3R7ZM.js → dev-loader-7P3HZCIA.js} +1 -3
- package/dist/{dev-loader-DRU3R7ZM.js.map → dev-loader-7P3HZCIA.js.map} +1 -1
- package/dist/doctor-D723IB2I.js +9 -0
- package/dist/file-service-HHB3JQIO.js +8 -0
- package/dist/index.d.ts +37 -145
- package/dist/index.js +32 -28
- package/dist/index.js.map +1 -1
- package/dist/{install-cloudflared-AN24L4DP.js → install-cloudflared-JRJ4BSOM.js} +3 -4
- package/dist/{install-cloudflared-AN24L4DP.js.map → install-cloudflared-JRJ4BSOM.js.map} +1 -1
- package/dist/{install-context-XPWTFT3J.js → install-context-EHYV5WRY.js} +2 -3
- package/dist/{install-context-XPWTFT3J.js.map → install-context-EHYV5WRY.js.map} +1 -1
- package/dist/{install-jq-CRVDJGF3.js → install-jq-ISTGT263.js} +3 -4
- package/dist/{install-jq-CRVDJGF3.js.map → install-jq-ISTGT263.js.map} +1 -1
- package/dist/{integrate-G6CVXTGT.js → integrate-JIEZYDOR.js} +1 -2
- package/dist/{integrate-G6CVXTGT.js.map → integrate-JIEZYDOR.js.map} +1 -1
- package/dist/{log-LZ7FTRKG.js → log-YZ243M5G.js} +4 -3
- package/dist/{main-3GF3EQTE.js → main-RRSX5SRL.js} +117 -40
- package/dist/main-RRSX5SRL.js.map +1 -0
- package/dist/{menu-YDQ2LWAR.js → menu-ALFN37IR.js} +1 -2
- package/dist/notifications-MO23S7S3.js +8 -0
- package/dist/{plugin-create-5HQRF2ID.js → plugin-create-EHL76ZZG.js} +1 -2
- package/dist/{plugin-create-5HQRF2ID.js.map → plugin-create-EHL76ZZG.js.map} +1 -1
- package/dist/plugin-installer-5XHORMLS.js +9 -0
- package/dist/{plugin-registry-WB3DR67H.js → plugin-registry-6J3YSFHF.js} +1 -2
- package/dist/{plugin-search-HQ4WQKOF.js → plugin-search-MGKAL5JM.js} +1 -2
- package/dist/{plugin-search-HQ4WQKOF.js.map → plugin-search-MGKAL5JM.js.map} +1 -1
- package/dist/{post-upgrade-3ADZRMYJ.js → post-upgrade-Y26S2ZQ7.js} +6 -7
- package/dist/{post-upgrade-3ADZRMYJ.js.map → post-upgrade-Y26S2ZQ7.js.map} +1 -1
- package/dist/{read-text-file-IRZM3QLM.js → read-text-file-DJBTITIB.js} +1 -2
- package/dist/{registry-client-AVGRE4CF.js → registry-client-GTBWLXYU.js} +1 -2
- package/dist/{security-YNRBW6S7.js → security-2BA265LN.js} +1 -2
- package/dist/{settings-manager-MD2U4ZV2.js → settings-manager-B4UN2LAC.js} +1 -2
- package/dist/{setup-A7VPW46C.js → setup-OI6A3OXW.js} +109 -82
- package/dist/setup-OI6A3OXW.js.map +1 -0
- package/dist/speech-GB7PHVQZ.js +9 -0
- package/dist/{suggest-7D6B542M.js → suggest-RST5VOHB.js} +1 -3
- package/dist/{suggest-7D6B542M.js.map → suggest-RST5VOHB.js.map} +1 -1
- package/dist/telegram-UVIAXADE.js +7 -0
- package/dist/tunnel-4WNFC7GO.js +7 -0
- package/dist/{tunnel-service-QJPUYEKU.js → tunnel-service-I2NFUX3V.js} +3 -4
- package/dist/{tunnel-service-QJPUYEKU.js.map → tunnel-service-I2NFUX3V.js.map} +1 -1
- package/dist/{validators-WSTBNKRW.js → validators-GITLOFXC.js} +1 -2
- package/dist/{version-NQZBM5M7.js → version-AXXV6IV2.js} +1 -2
- package/package.json +1 -3
- package/dist/adapter-JQFQ3JAO.js +0 -15
- package/dist/adapter-UORRGHNH.js +0 -1030
- package/dist/adapter-UORRGHNH.js.map +0 -1
- package/dist/agent-catalog-YHBFERYO.js +0 -11
- package/dist/agent-store-VSHNY5GT.js +0 -9
- package/dist/api-server-7G3ZUZRM.js +0 -8
- package/dist/api-server-CAYNPUF2.js +0 -11
- package/dist/chunk-2YCW3QDV.js.map +0 -1
- package/dist/chunk-32LVIEPW.js +0 -477
- package/dist/chunk-32LVIEPW.js.map +0 -1
- package/dist/chunk-AD3X6DGK.js.map +0 -1
- package/dist/chunk-BLQUXO7S.js.map +0 -1
- package/dist/chunk-KMMEFXIE.js.map +0 -1
- package/dist/chunk-ODUM3D6X.js.map +0 -1
- package/dist/chunk-QVMEF6FB.js.map +0 -1
- package/dist/chunk-VUNV25KB.js +0 -16
- package/dist/chunk-WIIZNPCR.js.map +0 -1
- package/dist/chunk-WXVT3AOY.js.map +0 -1
- package/dist/chunk-XIBG7LSL.js.map +0 -1
- package/dist/chunk-XWDW3XBE.js.map +0 -1
- package/dist/chunk-ZNSO2QVC.js +0 -124
- package/dist/chunk-ZNSO2QVC.js.map +0 -1
- package/dist/config-editor-7PKW42GZ.js +0 -11
- package/dist/core-plugins-Y5US6RED.js +0 -23
- package/dist/dist-UHQK5CXN.js +0 -21151
- package/dist/dist-UHQK5CXN.js.map +0 -1
- package/dist/doctor-QZQAP46W.js +0 -10
- package/dist/file-service-EUODJAIT.js +0 -9
- package/dist/main-3GF3EQTE.js.map +0 -1
- package/dist/notifications-D5BRDNSU.js +0 -9
- package/dist/plugin-installer-QVJP6VKV.js.map +0 -1
- package/dist/setup-A7VPW46C.js.map +0 -1
- package/dist/slack-2XNWBOWH.js +0 -8
- package/dist/speech-2GHQNRIO.js +0 -9
- package/dist/telegram-E65IWFBW.js +0 -8
- package/dist/tunnel-45HA72MB.js +0 -8
- package/dist/version-NQZBM5M7.js.map +0 -1
- /package/dist/{adapter-JQFQ3JAO.js.map → adapter-HGJENQCN.js.map} +0 -0
- /package/dist/{agent-catalog-YHBFERYO.js.map → agent-catalog-SZQQERV7.js.map} +0 -0
- /package/dist/{agent-dependencies-WS7Z2DFW.js.map → agent-dependencies-ED2ZTUHG.js.map} +0 -0
- /package/dist/{agent-registry-5LZT7CUB.js.map → agent-registry-YOGP656W.js.map} +0 -0
- /package/dist/{agent-store-VSHNY5GT.js.map → agent-store-5UHZH2XI.js.map} +0 -0
- /package/dist/{api-client-AQPNKXI2.js.map → api-client-XTLRRFPX.js.map} +0 -0
- /package/dist/{api-server-7G3ZUZRM.js.map → api-server-DSUW637I.js.map} +0 -0
- /package/dist/{api-server-CAYNPUF2.js.map → api-server-WFB5K6FP.js.map} +0 -0
- /package/dist/{autostart-6JS565RY.js.map → autostart-CUPZMKKC.js.map} +0 -0
- /package/dist/{chunk-PPSMUECX.js.map → chunk-2R5XM3ES.js.map} +0 -0
- /package/dist/{chunk-SNPYTMPR.js.map → chunk-3EWTPOF7.js.map} +0 -0
- /package/dist/{chunk-QAQDGPB4.js.map → chunk-43JVXFYP.js.map} +0 -0
- /package/dist/{chunk-A6Y4GZM3.js.map → chunk-566W6INH.js.map} +0 -0
- /package/dist/{chunk-P2G275VD.js.map → chunk-5TCXYDLR.js.map} +0 -0
- /package/dist/{chunk-RBYBSSGO.js.map → chunk-FCTC7KDT.js.map} +0 -0
- /package/dist/{chunk-4GMLGCF2.js.map → chunk-JUFN4XMB.js.map} +0 -0
- /package/dist/{chunk-HRKAXFWR.js.map → chunk-QBEQJFGL.js.map} +0 -0
- /package/dist/{chunk-SHTGQGAU.js.map → chunk-V2YZWYXT.js.map} +0 -0
- /package/dist/{chunk-BQ6FR32N.js.map → chunk-VD3QSMVY.js.map} +0 -0
- /package/dist/{chunk-VUNV25KB.js.map → config-UCAFCS5W.js.map} +0 -0
- /package/dist/{config-I4FMCJGZ.js.map → config-editor-OU6PUY66.js.map} +0 -0
- /package/dist/{config-editor-7PKW42GZ.js.map → config-registry-ZXAIJNYB.js.map} +0 -0
- /package/dist/{config-registry-CUMNXFGK.js.map → context-7MPU7RL5.js.map} +0 -0
- /package/dist/{context-XM6E22LM.js.map → core-plugins-R2EVZAJV.js.map} +0 -0
- /package/dist/{core-plugins-Y5US6RED.js.map → daemon-DTA6KYYY.js.map} +0 -0
- /package/dist/{daemon-PXO5QPCR.js.map → doctor-D723IB2I.js.map} +0 -0
- /package/dist/{doctor-QZQAP46W.js.map → file-service-HHB3JQIO.js.map} +0 -0
- /package/dist/{file-service-EUODJAIT.js.map → log-YZ243M5G.js.map} +0 -0
- /package/dist/{log-LZ7FTRKG.js.map → menu-ALFN37IR.js.map} +0 -0
- /package/dist/{menu-YDQ2LWAR.js.map → notifications-MO23S7S3.js.map} +0 -0
- /package/dist/{notifications-D5BRDNSU.js.map → plugin-installer-5XHORMLS.js.map} +0 -0
- /package/dist/{plugin-registry-WB3DR67H.js.map → plugin-registry-6J3YSFHF.js.map} +0 -0
- /package/dist/{read-text-file-IRZM3QLM.js.map → read-text-file-DJBTITIB.js.map} +0 -0
- /package/dist/{registry-client-AVGRE4CF.js.map → registry-client-GTBWLXYU.js.map} +0 -0
- /package/dist/{security-YNRBW6S7.js.map → security-2BA265LN.js.map} +0 -0
- /package/dist/{settings-manager-MD2U4ZV2.js.map → settings-manager-B4UN2LAC.js.map} +0 -0
- /package/dist/{slack-2XNWBOWH.js.map → speech-GB7PHVQZ.js.map} +0 -0
- /package/dist/{speech-2GHQNRIO.js.map → telegram-UVIAXADE.js.map} +0 -0
- /package/dist/{telegram-E65IWFBW.js.map → tunnel-4WNFC7GO.js.map} +0 -0
- /package/dist/{tunnel-45HA72MB.js.map → validators-GITLOFXC.js.map} +0 -0
- /package/dist/{validators-WSTBNKRW.js.map → version-AXXV6IV2.js.map} +0 -0
package/dist/adapter-UORRGHNH.js
DELETED
|
@@ -1,1030 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BaseRenderer,
|
|
3
|
-
MessagingAdapter
|
|
4
|
-
} from "./chunk-32LVIEPW.js";
|
|
5
|
-
import {
|
|
6
|
-
createChildLogger
|
|
7
|
-
} from "./chunk-XMMAGAT4.js";
|
|
8
|
-
import "./chunk-VUNV25KB.js";
|
|
9
|
-
|
|
10
|
-
// src/plugins/slack/adapter.ts
|
|
11
|
-
import fs from "fs";
|
|
12
|
-
import { App } from "@slack/bolt";
|
|
13
|
-
import { WebClient } from "@slack/web-api";
|
|
14
|
-
|
|
15
|
-
// src/plugins/slack/utils.ts
|
|
16
|
-
function isAudioClip(file) {
|
|
17
|
-
return file.mimetype === "video/mp4" && file.name?.startsWith("audio_message") || file.mimetype?.startsWith("audio/");
|
|
18
|
-
}
|
|
19
|
-
var SECTION_LIMIT = 3e3;
|
|
20
|
-
function splitSafe(text, limit = SECTION_LIMIT) {
|
|
21
|
-
if (text.length <= limit) return [text];
|
|
22
|
-
const chunks = [];
|
|
23
|
-
let remaining = text;
|
|
24
|
-
while (remaining.length > 0) {
|
|
25
|
-
if (remaining.length <= limit) {
|
|
26
|
-
chunks.push(remaining);
|
|
27
|
-
break;
|
|
28
|
-
}
|
|
29
|
-
let cut = remaining.lastIndexOf("\n", limit);
|
|
30
|
-
if (cut <= 0) cut = limit;
|
|
31
|
-
chunks.push(remaining.slice(0, cut));
|
|
32
|
-
remaining = remaining.slice(cut).trimStart();
|
|
33
|
-
}
|
|
34
|
-
return chunks;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// src/plugins/slack/formatter.ts
|
|
38
|
-
function markdownToMrkdwn(text) {
|
|
39
|
-
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();
|
|
40
|
-
}
|
|
41
|
-
var SECTION_LIMIT2 = 3e3;
|
|
42
|
-
function section(text) {
|
|
43
|
-
return { type: "section", text: { type: "mrkdwn", text: text.slice(0, SECTION_LIMIT2) } };
|
|
44
|
-
}
|
|
45
|
-
function context(text) {
|
|
46
|
-
return { type: "context", elements: [{ type: "mrkdwn", text }] };
|
|
47
|
-
}
|
|
48
|
-
var SlackFormatter = class {
|
|
49
|
-
formatOutgoing(message) {
|
|
50
|
-
switch (message.type) {
|
|
51
|
-
case "text": {
|
|
52
|
-
const text = message.text ?? "";
|
|
53
|
-
if (!text.trim()) return [];
|
|
54
|
-
const converted = markdownToMrkdwn(text);
|
|
55
|
-
return splitSafe(converted).map((chunk) => section(chunk));
|
|
56
|
-
}
|
|
57
|
-
case "thought":
|
|
58
|
-
return [context(`\u{1F4AD} _${(message.text ?? "").slice(0, 500)}_`)];
|
|
59
|
-
case "tool_call": {
|
|
60
|
-
const name = message.metadata?.name ?? "tool";
|
|
61
|
-
const input = message.metadata?.input;
|
|
62
|
-
const inputStr = input ? `
|
|
63
|
-
\`\`\`
|
|
64
|
-
${JSON.stringify(input, null, 2).slice(0, 500)}
|
|
65
|
-
\`\`\`` : "";
|
|
66
|
-
return [context(`\u{1F527} \`${name}\`${inputStr}`)];
|
|
67
|
-
}
|
|
68
|
-
case "tool_update": {
|
|
69
|
-
const name = message.metadata?.name ?? "tool";
|
|
70
|
-
const status = message.metadata?.status ?? "done";
|
|
71
|
-
const icon = status === "error" ? "\u274C" : "\u2705";
|
|
72
|
-
return [context(`${icon} \`${name}\` \u2014 ${status}`)];
|
|
73
|
-
}
|
|
74
|
-
case "plan":
|
|
75
|
-
return [
|
|
76
|
-
{ type: "divider" },
|
|
77
|
-
section(`\u{1F4CB} *Plan*
|
|
78
|
-
${message.text ?? ""}`)
|
|
79
|
-
];
|
|
80
|
-
case "usage": {
|
|
81
|
-
const meta = message.metadata ?? {};
|
|
82
|
-
const parts = [
|
|
83
|
-
meta.input_tokens != null ? `in: ${meta.input_tokens}` : null,
|
|
84
|
-
meta.output_tokens != null ? `out: ${meta.output_tokens}` : null,
|
|
85
|
-
meta.cost_usd != null ? `$${Number(meta.cost_usd).toFixed(4)}` : null
|
|
86
|
-
].filter((p) => p !== null);
|
|
87
|
-
return parts.length ? [context(`\u{1F4CA} ${parts.join(" \xB7 ")}`)] : [];
|
|
88
|
-
}
|
|
89
|
-
case "session_end":
|
|
90
|
-
return this.formatSessionEnd(message.text);
|
|
91
|
-
case "error":
|
|
92
|
-
return [section(`\u26A0\uFE0F *Error:* ${message.text ?? "Unknown error"}`)];
|
|
93
|
-
default:
|
|
94
|
-
return [];
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
formatPermissionRequest(req) {
|
|
98
|
-
return [
|
|
99
|
-
section(`\u{1F510} *Permission Request*
|
|
100
|
-
${req.description}`),
|
|
101
|
-
{
|
|
102
|
-
type: "actions",
|
|
103
|
-
block_id: `perm_${req.id}`,
|
|
104
|
-
elements: req.options.map((opt) => ({
|
|
105
|
-
type: "button",
|
|
106
|
-
text: { type: "plain_text", text: opt.label, emoji: true },
|
|
107
|
-
value: `${req.id}:${opt.id}`,
|
|
108
|
-
action_id: `perm_action_${opt.id}_${req.id}`,
|
|
109
|
-
style: opt.isAllow ? "primary" : "danger"
|
|
110
|
-
}))
|
|
111
|
-
}
|
|
112
|
-
];
|
|
113
|
-
}
|
|
114
|
-
formatNotification(text) {
|
|
115
|
-
return [section(text)];
|
|
116
|
-
}
|
|
117
|
-
formatSessionEnd(reason) {
|
|
118
|
-
return [
|
|
119
|
-
{ type: "divider" },
|
|
120
|
-
context(`\u2705 Session ended${reason ? ` \u2014 ${reason}` : ""}`)
|
|
121
|
-
];
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// src/plugins/slack/renderer.ts
|
|
126
|
-
var SlackRenderer = class extends BaseRenderer {
|
|
127
|
-
formatter;
|
|
128
|
-
constructor(formatter) {
|
|
129
|
-
super();
|
|
130
|
-
this.formatter = formatter ?? new SlackFormatter();
|
|
131
|
-
}
|
|
132
|
-
renderText(content) {
|
|
133
|
-
const blocks = this.formatter.formatOutgoing(content);
|
|
134
|
-
return {
|
|
135
|
-
body: content.text ?? "",
|
|
136
|
-
format: "structured",
|
|
137
|
-
components: blocks
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
renderThought(content, _verbosity) {
|
|
141
|
-
const blocks = this.formatter.formatOutgoing(content);
|
|
142
|
-
return {
|
|
143
|
-
body: content.text ?? "",
|
|
144
|
-
format: "structured",
|
|
145
|
-
components: blocks
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
renderToolCall(content, _verbosity) {
|
|
149
|
-
const blocks = this.formatter.formatOutgoing(content);
|
|
150
|
-
return {
|
|
151
|
-
body: content.text ?? "",
|
|
152
|
-
format: "structured",
|
|
153
|
-
components: blocks
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
renderToolUpdate(content, _verbosity) {
|
|
157
|
-
const blocks = this.formatter.formatOutgoing(content);
|
|
158
|
-
return {
|
|
159
|
-
body: content.text ?? "",
|
|
160
|
-
format: "structured",
|
|
161
|
-
components: blocks
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
renderPlan(content) {
|
|
165
|
-
const blocks = this.formatter.formatOutgoing(content);
|
|
166
|
-
return {
|
|
167
|
-
body: content.text ?? "",
|
|
168
|
-
format: "structured",
|
|
169
|
-
components: blocks
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
renderUsage(content, _verbosity) {
|
|
173
|
-
const blocks = this.formatter.formatOutgoing(content);
|
|
174
|
-
return {
|
|
175
|
-
body: content.text ?? "",
|
|
176
|
-
format: "structured",
|
|
177
|
-
components: blocks
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
renderError(content) {
|
|
181
|
-
const blocks = this.formatter.formatOutgoing(content);
|
|
182
|
-
return {
|
|
183
|
-
body: content.text ?? "",
|
|
184
|
-
format: "structured",
|
|
185
|
-
components: blocks
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
renderSessionEnd(content) {
|
|
189
|
-
const blocks = this.formatter.formatSessionEnd(content.text);
|
|
190
|
-
return {
|
|
191
|
-
body: content.text ?? "Session ended",
|
|
192
|
-
format: "structured",
|
|
193
|
-
components: blocks
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
renderPermission(request) {
|
|
197
|
-
const blocks = this.formatter.formatPermissionRequest(request);
|
|
198
|
-
return {
|
|
199
|
-
body: request.description,
|
|
200
|
-
format: "structured",
|
|
201
|
-
components: blocks,
|
|
202
|
-
actions: request.options.map((o) => ({
|
|
203
|
-
id: o.id,
|
|
204
|
-
label: o.label,
|
|
205
|
-
isAllow: o.isAllow
|
|
206
|
-
}))
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
renderNotification(notification) {
|
|
210
|
-
const emoji = {
|
|
211
|
-
completed: "\u2705",
|
|
212
|
-
error: "\u274C",
|
|
213
|
-
permission: "\u{1F510}",
|
|
214
|
-
input_required: "\u{1F4AC}",
|
|
215
|
-
budget_warning: "\u26A0\uFE0F"
|
|
216
|
-
};
|
|
217
|
-
const icon = emoji[notification.type] || "\u2139\uFE0F";
|
|
218
|
-
const text = `${icon} *${notification.sessionName ?? "Session"}*
|
|
219
|
-
${notification.summary}`;
|
|
220
|
-
const blocks = this.formatter.formatNotification(text);
|
|
221
|
-
return {
|
|
222
|
-
body: text,
|
|
223
|
-
format: "structured",
|
|
224
|
-
components: blocks
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
renderSystemMessage(content) {
|
|
228
|
-
const mrkdwn = markdownToMrkdwn(content.text ?? "");
|
|
229
|
-
return {
|
|
230
|
-
body: mrkdwn,
|
|
231
|
-
format: "structured",
|
|
232
|
-
components: [
|
|
233
|
-
{
|
|
234
|
-
type: "section",
|
|
235
|
-
text: { type: "mrkdwn", text: mrkdwn }
|
|
236
|
-
}
|
|
237
|
-
]
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
renderModeChange(content) {
|
|
241
|
-
const modeId = content.metadata?.modeId ?? "";
|
|
242
|
-
const text = `\u{1F504} *Mode:* ${modeId}`;
|
|
243
|
-
return {
|
|
244
|
-
body: text,
|
|
245
|
-
format: "structured",
|
|
246
|
-
components: [
|
|
247
|
-
{
|
|
248
|
-
type: "context",
|
|
249
|
-
elements: [{ type: "mrkdwn", text }]
|
|
250
|
-
}
|
|
251
|
-
]
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
renderConfigUpdate() {
|
|
255
|
-
const text = "\u2699\uFE0F *Config updated*";
|
|
256
|
-
return {
|
|
257
|
-
body: text,
|
|
258
|
-
format: "structured",
|
|
259
|
-
components: [
|
|
260
|
-
{
|
|
261
|
-
type: "context",
|
|
262
|
-
elements: [{ type: "mrkdwn", text }]
|
|
263
|
-
}
|
|
264
|
-
]
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
renderModelUpdate(content) {
|
|
268
|
-
const modelId = content.metadata?.modelId ?? "";
|
|
269
|
-
const text = `\u{1F916} *Model:* ${modelId}`;
|
|
270
|
-
return {
|
|
271
|
-
body: text,
|
|
272
|
-
format: "structured",
|
|
273
|
-
components: [
|
|
274
|
-
{
|
|
275
|
-
type: "context",
|
|
276
|
-
elements: [{ type: "mrkdwn", text }]
|
|
277
|
-
}
|
|
278
|
-
]
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
// src/plugins/slack/send-queue.ts
|
|
284
|
-
import PQueue from "p-queue";
|
|
285
|
-
var METHOD_RPM = {
|
|
286
|
-
"chat.postMessage": 50,
|
|
287
|
-
// Tier 3
|
|
288
|
-
"chat.update": 50,
|
|
289
|
-
// Tier 3
|
|
290
|
-
"conversations.create": 20,
|
|
291
|
-
// Tier 2
|
|
292
|
-
"conversations.rename": 20,
|
|
293
|
-
// Tier 2
|
|
294
|
-
"conversations.archive": 20,
|
|
295
|
-
// Tier 2
|
|
296
|
-
"conversations.invite": 20,
|
|
297
|
-
// Tier 2
|
|
298
|
-
"conversations.join": 20,
|
|
299
|
-
// Tier 2
|
|
300
|
-
"conversations.unarchive": 20,
|
|
301
|
-
// Tier 2
|
|
302
|
-
"conversations.info": 50
|
|
303
|
-
// Tier 3
|
|
304
|
-
};
|
|
305
|
-
var SlackSendQueue = class {
|
|
306
|
-
constructor(client) {
|
|
307
|
-
this.client = client;
|
|
308
|
-
for (const [method, rpm] of Object.entries(METHOD_RPM)) {
|
|
309
|
-
this.queues.set(method, new PQueue({
|
|
310
|
-
interval: Math.ceil(6e4 / rpm),
|
|
311
|
-
intervalCap: 1,
|
|
312
|
-
carryoverConcurrencyCount: true
|
|
313
|
-
}));
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
queues = /* @__PURE__ */ new Map();
|
|
317
|
-
async enqueue(method, params) {
|
|
318
|
-
const queue = this.queues.get(method);
|
|
319
|
-
if (!queue) throw new Error(`Unknown Slack method: ${method}`);
|
|
320
|
-
return queue.add(() => this.client.apiCall(method, params));
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
// src/plugins/slack/slug.ts
|
|
325
|
-
import { customAlphabet } from "nanoid";
|
|
326
|
-
var nanoidAlpha = customAlphabet("abcdefghijklmnopqrstuvwxyz0123456789", 4);
|
|
327
|
-
function toSlug(name, prefix = "openacp") {
|
|
328
|
-
const base = name.toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 60);
|
|
329
|
-
const suffix = nanoidAlpha();
|
|
330
|
-
return `${prefix}-${base}-${suffix}`.replace(/-+/g, "-");
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// src/plugins/slack/channel-manager.ts
|
|
334
|
-
var SlackChannelManager = class {
|
|
335
|
-
constructor(queue, config) {
|
|
336
|
-
this.queue = queue;
|
|
337
|
-
this.config = config;
|
|
338
|
-
}
|
|
339
|
-
async createChannel(sessionId, sessionName) {
|
|
340
|
-
let lastError;
|
|
341
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
342
|
-
const finalSlug = toSlug(sessionName, this.config.channelPrefix ?? "openacp");
|
|
343
|
-
try {
|
|
344
|
-
const res = await this.queue.enqueue(
|
|
345
|
-
"conversations.create",
|
|
346
|
-
{ name: finalSlug, is_private: true }
|
|
347
|
-
);
|
|
348
|
-
const channelId = res.channel.id;
|
|
349
|
-
const userIds = this.config.allowedUserIds ?? [];
|
|
350
|
-
if (userIds.length > 0) {
|
|
351
|
-
await this.queue.enqueue("conversations.invite", {
|
|
352
|
-
channel: channelId,
|
|
353
|
-
users: userIds.join(",")
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
return { channelId, channelSlug: finalSlug };
|
|
357
|
-
} catch (err) {
|
|
358
|
-
if (err?.data?.error === "name_taken" && attempt < 2) {
|
|
359
|
-
lastError = err;
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
throw err;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
throw lastError;
|
|
366
|
-
}
|
|
367
|
-
async archiveChannel(channelId) {
|
|
368
|
-
await this.queue.enqueue("conversations.archive", { channel: channelId });
|
|
369
|
-
}
|
|
370
|
-
async notifyChannel(text) {
|
|
371
|
-
if (this.config.notificationChannelId) {
|
|
372
|
-
await this.queue.enqueue("chat.postMessage", {
|
|
373
|
-
channel: this.config.notificationChannelId,
|
|
374
|
-
text
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
// src/plugins/slack/permission-handler.ts
|
|
381
|
-
var SlackPermissionHandler = class {
|
|
382
|
-
constructor(queue, onResponse) {
|
|
383
|
-
this.queue = queue;
|
|
384
|
-
this.onResponse = onResponse;
|
|
385
|
-
}
|
|
386
|
-
pendingMessages = /* @__PURE__ */ new Map();
|
|
387
|
-
trackPendingMessage(requestId, channelId, messageTs) {
|
|
388
|
-
this.pendingMessages.set(requestId, { channelId, messageTs });
|
|
389
|
-
}
|
|
390
|
-
async cleanupSession(channelId) {
|
|
391
|
-
for (const [requestId, info] of this.pendingMessages) {
|
|
392
|
-
if (info.channelId !== channelId) continue;
|
|
393
|
-
await this.queue.enqueue("chat.update", {
|
|
394
|
-
channel: info.channelId,
|
|
395
|
-
ts: info.messageTs,
|
|
396
|
-
blocks: []
|
|
397
|
-
});
|
|
398
|
-
this.pendingMessages.delete(requestId);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
register(app) {
|
|
402
|
-
app.action(
|
|
403
|
-
/^perm_action_/,
|
|
404
|
-
async ({ ack, body, action }) => {
|
|
405
|
-
await ack();
|
|
406
|
-
const value = action.value ?? "";
|
|
407
|
-
const colonIdx = value.indexOf(":");
|
|
408
|
-
if (colonIdx === -1) return;
|
|
409
|
-
const requestId = value.slice(0, colonIdx);
|
|
410
|
-
const optionId = value.slice(colonIdx + 1);
|
|
411
|
-
this.onResponse(requestId, optionId);
|
|
412
|
-
this.pendingMessages.delete(requestId);
|
|
413
|
-
const message = body.message;
|
|
414
|
-
if (message) {
|
|
415
|
-
await this.queue.enqueue("chat.update", {
|
|
416
|
-
channel: body.channel?.id ?? "",
|
|
417
|
-
ts: message.ts,
|
|
418
|
-
text: `\u2705 Permission response: *${optionId}*`,
|
|
419
|
-
blocks: []
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
// src/plugins/slack/event-router.ts
|
|
428
|
-
var log = createChildLogger({ module: "slack-event-router" });
|
|
429
|
-
var SlackEventRouter = class {
|
|
430
|
-
constructor(sessionLookup, onIncoming, botUserId, notificationChannelId, onNewSession, config, globalAllowedUserIds = []) {
|
|
431
|
-
this.sessionLookup = sessionLookup;
|
|
432
|
-
this.onIncoming = onIncoming;
|
|
433
|
-
this.botUserId = botUserId;
|
|
434
|
-
this.notificationChannelId = notificationChannelId;
|
|
435
|
-
this.onNewSession = onNewSession;
|
|
436
|
-
this.config = config;
|
|
437
|
-
this.globalAllowedUserIds = globalAllowedUserIds;
|
|
438
|
-
}
|
|
439
|
-
isAllowedUser(userId) {
|
|
440
|
-
const slackAllowed = this.config.allowedUserIds ?? [];
|
|
441
|
-
const allowed = slackAllowed.length > 0 ? slackAllowed : this.globalAllowedUserIds;
|
|
442
|
-
if (allowed.length === 0) return true;
|
|
443
|
-
return allowed.includes(userId);
|
|
444
|
-
}
|
|
445
|
-
register(app) {
|
|
446
|
-
app.message(async ({ message }) => {
|
|
447
|
-
log.debug({ message }, "Slack raw message event");
|
|
448
|
-
const msg = message;
|
|
449
|
-
if (msg.bot_id) return;
|
|
450
|
-
const subtype = msg.subtype;
|
|
451
|
-
if (subtype && subtype !== "file_share") return;
|
|
452
|
-
const channelId = msg.channel;
|
|
453
|
-
const text = msg.text ?? "";
|
|
454
|
-
const userId = msg.user ?? "";
|
|
455
|
-
const files = msg.files?.map((f) => ({
|
|
456
|
-
id: f.id,
|
|
457
|
-
name: f.name,
|
|
458
|
-
mimetype: f.mimetype,
|
|
459
|
-
size: f.size,
|
|
460
|
-
url_private: f.url_private
|
|
461
|
-
}));
|
|
462
|
-
log.debug({ channelId, userId, text }, "Slack message received");
|
|
463
|
-
if (userId === this.botUserId) return;
|
|
464
|
-
if (!this.isAllowedUser(userId)) {
|
|
465
|
-
log.warn({ userId }, "slack: message from non-allowed user rejected");
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
const session = this.sessionLookup(channelId);
|
|
469
|
-
if (session) {
|
|
470
|
-
log.debug({ channelId, sessionSlug: session.channelSlug }, "Routing to session");
|
|
471
|
-
this.onIncoming(session.channelSlug, text, userId, files);
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
log.debug({ channelId, notificationChannelId: this.notificationChannelId }, "No session found for channel");
|
|
475
|
-
if (this.notificationChannelId && channelId === this.notificationChannelId) {
|
|
476
|
-
this.onNewSession(text, userId);
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
};
|
|
482
|
-
|
|
483
|
-
// src/plugins/slack/text-buffer.ts
|
|
484
|
-
var log2 = createChildLogger({ module: "slack-text-buffer" });
|
|
485
|
-
var FLUSH_IDLE_MS = 2e3;
|
|
486
|
-
var SlackTextBuffer = class {
|
|
487
|
-
constructor(channelId, sessionId, queue) {
|
|
488
|
-
this.channelId = channelId;
|
|
489
|
-
this.sessionId = sessionId;
|
|
490
|
-
this.queue = queue;
|
|
491
|
-
}
|
|
492
|
-
buffer = "";
|
|
493
|
-
timer;
|
|
494
|
-
flushPromise;
|
|
495
|
-
lastMessageTs;
|
|
496
|
-
lastPostedText;
|
|
497
|
-
append(text) {
|
|
498
|
-
if (!text) return;
|
|
499
|
-
this.buffer += text;
|
|
500
|
-
this.resetTimer();
|
|
501
|
-
}
|
|
502
|
-
resetTimer() {
|
|
503
|
-
if (this.timer) clearTimeout(this.timer);
|
|
504
|
-
this.timer = setTimeout(() => {
|
|
505
|
-
this.timer = void 0;
|
|
506
|
-
this.flush().catch((err) => log2.error({ err, sessionId: this.sessionId }, "Text buffer flush error"));
|
|
507
|
-
}, FLUSH_IDLE_MS);
|
|
508
|
-
}
|
|
509
|
-
async flush() {
|
|
510
|
-
if (this.flushPromise) return this.flushPromise;
|
|
511
|
-
const text = this.buffer.trim();
|
|
512
|
-
if (!text) return;
|
|
513
|
-
this.buffer = "";
|
|
514
|
-
if (this.timer) {
|
|
515
|
-
clearTimeout(this.timer);
|
|
516
|
-
this.timer = void 0;
|
|
517
|
-
}
|
|
518
|
-
this.flushPromise = (async () => {
|
|
519
|
-
try {
|
|
520
|
-
const converted = markdownToMrkdwn(text);
|
|
521
|
-
const chunks = splitSafe(converted);
|
|
522
|
-
for (const chunk of chunks) {
|
|
523
|
-
if (!chunk.trim()) continue;
|
|
524
|
-
const result = await this.queue.enqueue("chat.postMessage", {
|
|
525
|
-
channel: this.channelId,
|
|
526
|
-
text: chunk,
|
|
527
|
-
blocks: [{ type: "section", text: { type: "mrkdwn", text: chunk } }]
|
|
528
|
-
});
|
|
529
|
-
this.lastMessageTs = result?.ts;
|
|
530
|
-
this.lastPostedText = chunk;
|
|
531
|
-
}
|
|
532
|
-
} finally {
|
|
533
|
-
this.flushPromise = void 0;
|
|
534
|
-
if (this.buffer.trim()) {
|
|
535
|
-
await this.flush();
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
})();
|
|
539
|
-
return this.flushPromise;
|
|
540
|
-
}
|
|
541
|
-
destroy() {
|
|
542
|
-
if (this.timer) {
|
|
543
|
-
clearTimeout(this.timer);
|
|
544
|
-
this.timer = void 0;
|
|
545
|
-
}
|
|
546
|
-
this.buffer = "";
|
|
547
|
-
}
|
|
548
|
-
/** Remove [TTS]...[/TTS] blocks — from buffer if unflushed, or edit posted message */
|
|
549
|
-
async stripTtsBlock() {
|
|
550
|
-
if (/\[TTS\][\s\S]*?\[\/TTS\]/.test(this.buffer)) {
|
|
551
|
-
this.buffer = this.buffer.replace(/\[TTS\][\s\S]*?\[\/TTS\]/g, "").replace(/\s{2,}/g, " ").trim();
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
if (this.lastMessageTs && this.lastPostedText && /\[TTS\][\s\S]*?\[\/TTS\]/.test(this.lastPostedText)) {
|
|
555
|
-
const cleaned = this.lastPostedText.replace(/\[TTS\][\s\S]*?\[\/TTS\]/g, "").replace(/\s{2,}/g, " ").trim();
|
|
556
|
-
if (cleaned) {
|
|
557
|
-
await this.queue.enqueue("chat.update", {
|
|
558
|
-
channel: this.channelId,
|
|
559
|
-
ts: this.lastMessageTs,
|
|
560
|
-
text: cleaned,
|
|
561
|
-
blocks: [{ type: "section", text: { type: "mrkdwn", text: cleaned } }]
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
this.lastPostedText = cleaned;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
};
|
|
568
|
-
|
|
569
|
-
// src/plugins/slack/adapter.ts
|
|
570
|
-
var log3 = createChildLogger({ module: "slack" });
|
|
571
|
-
var SlackAdapter = class extends MessagingAdapter {
|
|
572
|
-
name = "slack";
|
|
573
|
-
renderer;
|
|
574
|
-
capabilities = {
|
|
575
|
-
streaming: true,
|
|
576
|
-
richFormatting: true,
|
|
577
|
-
threads: true,
|
|
578
|
-
reactions: false,
|
|
579
|
-
fileUpload: true,
|
|
580
|
-
voice: true
|
|
581
|
-
};
|
|
582
|
-
core;
|
|
583
|
-
app;
|
|
584
|
-
webClient;
|
|
585
|
-
queue;
|
|
586
|
-
formatter;
|
|
587
|
-
channelManager;
|
|
588
|
-
permissionHandler;
|
|
589
|
-
eventRouter;
|
|
590
|
-
sessions = /* @__PURE__ */ new Map();
|
|
591
|
-
textBuffers = /* @__PURE__ */ new Map();
|
|
592
|
-
botUserId = "";
|
|
593
|
-
slackConfig;
|
|
594
|
-
fileService;
|
|
595
|
-
constructor(core, config) {
|
|
596
|
-
super(
|
|
597
|
-
{ configManager: core.configManager },
|
|
598
|
-
{ ...config, maxMessageLength: 3e3, enabled: config.enabled ?? true }
|
|
599
|
-
);
|
|
600
|
-
this.core = core;
|
|
601
|
-
this.slackConfig = config;
|
|
602
|
-
this.formatter = new SlackFormatter();
|
|
603
|
-
this.renderer = new SlackRenderer(this.formatter);
|
|
604
|
-
}
|
|
605
|
-
async start() {
|
|
606
|
-
const { botToken, appToken, signingSecret } = this.slackConfig;
|
|
607
|
-
if (!botToken || !appToken || !signingSecret) {
|
|
608
|
-
throw new Error("Slack adapter requires botToken, appToken, and signingSecret");
|
|
609
|
-
}
|
|
610
|
-
this.app = new App({
|
|
611
|
-
token: botToken,
|
|
612
|
-
appToken,
|
|
613
|
-
signingSecret,
|
|
614
|
-
socketMode: true
|
|
615
|
-
});
|
|
616
|
-
this.webClient = new WebClient(botToken);
|
|
617
|
-
this.queue = new SlackSendQueue(this.webClient);
|
|
618
|
-
this.fileService = this.core.fileService;
|
|
619
|
-
const authResult = await this.webClient.auth.test();
|
|
620
|
-
if (!authResult.user_id) {
|
|
621
|
-
throw new Error("Slack auth.test() did not return user_id \u2014 verify botToken is valid");
|
|
622
|
-
}
|
|
623
|
-
this.botUserId = authResult.user_id;
|
|
624
|
-
log3.info({ botUserId: this.botUserId }, "Slack bot authenticated");
|
|
625
|
-
this.channelManager = new SlackChannelManager(this.queue, this.slackConfig);
|
|
626
|
-
this.permissionHandler = new SlackPermissionHandler(
|
|
627
|
-
this.queue,
|
|
628
|
-
(requestId, optionId) => {
|
|
629
|
-
for (const [sessionId, _meta] of this.sessions) {
|
|
630
|
-
const session = this.core.sessionManager.getSession(sessionId);
|
|
631
|
-
if (session && session.permissionGate.requestId === requestId) {
|
|
632
|
-
session.permissionGate.resolve(optionId);
|
|
633
|
-
log3.info({ sessionId, requestId, optionId }, "Permission resolved");
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
log3.warn({ requestId, optionId }, "No matching session found for permission response");
|
|
638
|
-
}
|
|
639
|
-
);
|
|
640
|
-
this.permissionHandler.register(this.app);
|
|
641
|
-
this.eventRouter = new SlackEventRouter(
|
|
642
|
-
(slackChannelId) => {
|
|
643
|
-
for (const meta of this.sessions.values()) {
|
|
644
|
-
if (meta.channelId === slackChannelId) return meta;
|
|
645
|
-
}
|
|
646
|
-
return void 0;
|
|
647
|
-
},
|
|
648
|
-
(sessionChannelSlug, text, userId, files) => {
|
|
649
|
-
const processFiles = async () => {
|
|
650
|
-
if (!files?.length) return void 0;
|
|
651
|
-
const audioFiles = files.filter((f) => isAudioClip(f));
|
|
652
|
-
if (!audioFiles.length) return void 0;
|
|
653
|
-
const attachments = [];
|
|
654
|
-
for (const file of audioFiles) {
|
|
655
|
-
const buffer = await this.downloadSlackFile(file.url_private);
|
|
656
|
-
if (!buffer) continue;
|
|
657
|
-
const mimeType = file.mimetype === "video/mp4" ? "audio/mp4" : file.mimetype;
|
|
658
|
-
const sessionId = this.core.sessionManager.getSessionByThread("slack", sessionChannelSlug)?.id;
|
|
659
|
-
if (!sessionId) continue;
|
|
660
|
-
const att = await this.fileService.saveFile(sessionId, file.name, buffer, mimeType);
|
|
661
|
-
attachments.push(att);
|
|
662
|
-
}
|
|
663
|
-
return attachments.length > 0 ? attachments : void 0;
|
|
664
|
-
};
|
|
665
|
-
processFiles().then((attachments) => {
|
|
666
|
-
this.core.handleMessage({
|
|
667
|
-
channelId: "slack",
|
|
668
|
-
threadId: sessionChannelSlug,
|
|
669
|
-
userId,
|
|
670
|
-
text,
|
|
671
|
-
attachments
|
|
672
|
-
}).catch((err) => log3.error({ err }, "handleMessage error"));
|
|
673
|
-
}).catch((err) => log3.error({ err }, "Failed to process audio files"));
|
|
674
|
-
},
|
|
675
|
-
this.botUserId,
|
|
676
|
-
this.slackConfig.notificationChannelId,
|
|
677
|
-
// onNewSession: reply with guidance when user messages the notification channel
|
|
678
|
-
async (_text, _userId) => {
|
|
679
|
-
if (this.slackConfig.notificationChannelId) {
|
|
680
|
-
await this.queue.enqueue("chat.postMessage", {
|
|
681
|
-
channel: this.slackConfig.notificationChannelId,
|
|
682
|
-
text: "\u{1F4AC} To start a new session, use the `/openacp-new` slash command in any channel."
|
|
683
|
-
}).catch((err) => log3.warn({ err }, "Failed to send onNewSession reply"));
|
|
684
|
-
}
|
|
685
|
-
},
|
|
686
|
-
this.slackConfig,
|
|
687
|
-
this.core.configManager.get().security.allowedUserIds
|
|
688
|
-
);
|
|
689
|
-
this.eventRouter.register(this.app);
|
|
690
|
-
await this.app.start();
|
|
691
|
-
log3.info("Slack adapter started (Socket Mode)");
|
|
692
|
-
if (this.slackConfig.autoCreateSession !== false) {
|
|
693
|
-
await this._createStartupSession();
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
async downloadSlackFile(url) {
|
|
697
|
-
try {
|
|
698
|
-
const resp = await fetch(url, {
|
|
699
|
-
headers: { Authorization: `Bearer ${this.slackConfig.botToken}` }
|
|
700
|
-
});
|
|
701
|
-
if (!resp.ok) {
|
|
702
|
-
log3.warn({ status: resp.status }, "Failed to download Slack file");
|
|
703
|
-
return null;
|
|
704
|
-
}
|
|
705
|
-
const contentType = resp.headers.get("content-type") ?? "";
|
|
706
|
-
if (contentType.includes("text/html")) {
|
|
707
|
-
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.");
|
|
708
|
-
return null;
|
|
709
|
-
}
|
|
710
|
-
return Buffer.from(await resp.arrayBuffer());
|
|
711
|
-
} catch (err) {
|
|
712
|
-
log3.error({ err }, "Error downloading Slack file");
|
|
713
|
-
return null;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
async uploadAudioFile(channelId, att) {
|
|
717
|
-
const fileBuffer = await fs.promises.readFile(att.filePath);
|
|
718
|
-
await this.webClient.files.uploadV2({
|
|
719
|
-
channel_id: channelId,
|
|
720
|
-
file: fileBuffer,
|
|
721
|
-
filename: att.fileName
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
async _createStartupSession() {
|
|
725
|
-
try {
|
|
726
|
-
let reuseChannelId = this.slackConfig.startupChannelId;
|
|
727
|
-
if (reuseChannelId) {
|
|
728
|
-
try {
|
|
729
|
-
const info = await this.queue.enqueue(
|
|
730
|
-
"conversations.info",
|
|
731
|
-
{ channel: reuseChannelId }
|
|
732
|
-
);
|
|
733
|
-
const channel = info?.channel;
|
|
734
|
-
if (!channel || typeof channel.is_archived !== "boolean") {
|
|
735
|
-
log3.warn({ reuseChannelId }, "Unexpected conversations.info response shape, creating new channel");
|
|
736
|
-
reuseChannelId = void 0;
|
|
737
|
-
} else if (channel.is_archived) {
|
|
738
|
-
await this.queue.enqueue("conversations.unarchive", { channel: reuseChannelId });
|
|
739
|
-
log3.info({ channelId: reuseChannelId }, "Unarchived startup channel for reuse");
|
|
740
|
-
}
|
|
741
|
-
} catch {
|
|
742
|
-
reuseChannelId = void 0;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
if (reuseChannelId) {
|
|
746
|
-
let hasSession = false;
|
|
747
|
-
for (const m of this.sessions.values()) {
|
|
748
|
-
if (m.channelId === reuseChannelId) {
|
|
749
|
-
hasSession = true;
|
|
750
|
-
break;
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
if (!hasSession) {
|
|
754
|
-
const session = await this.core.handleNewSession("slack", void 0, void 0, { createThread: false });
|
|
755
|
-
const slug = `startup-${session.id.slice(0, 8)}`;
|
|
756
|
-
this.sessions.set(session.id, { channelId: reuseChannelId, channelSlug: slug });
|
|
757
|
-
session.threadId = slug;
|
|
758
|
-
await this.core.sessionManager.patchRecord(session.id, {
|
|
759
|
-
platform: { topicId: slug }
|
|
760
|
-
});
|
|
761
|
-
log3.info({ sessionId: session.id, channelId: reuseChannelId }, "Reused startup channel");
|
|
762
|
-
}
|
|
763
|
-
} else {
|
|
764
|
-
const session = await this.core.handleNewSession("slack", void 0, void 0, { createThread: true });
|
|
765
|
-
if (!session.threadId) {
|
|
766
|
-
log3.error({ sessionId: session.id }, "Startup session created without threadId");
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
const meta = this.sessions.get(session.id);
|
|
770
|
-
if (meta) {
|
|
771
|
-
await this.core.configManager.save(
|
|
772
|
-
{ channels: { slack: { startupChannelId: meta.channelId } } }
|
|
773
|
-
);
|
|
774
|
-
log3.info({ sessionId: session.id, channelId: meta.channelId }, "Saved startup channel to config");
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
if (this.slackConfig.notificationChannelId) {
|
|
778
|
-
const startupMeta = [...this.sessions.values()].find(
|
|
779
|
-
(m) => m.channelId === (reuseChannelId ?? this.slackConfig.startupChannelId)
|
|
780
|
-
);
|
|
781
|
-
if (startupMeta) {
|
|
782
|
-
await this.queue.enqueue("chat.postMessage", {
|
|
783
|
-
channel: this.slackConfig.notificationChannelId,
|
|
784
|
-
text: `\u2705 OpenACP ready \u2014 chat with the agent in <#${startupMeta.channelId}>`
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
} catch (err) {
|
|
789
|
-
log3.error({ err }, "Failed to create/reuse Slack startup session");
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
async stop() {
|
|
793
|
-
for (const [sessionId, buf] of this.textBuffers) {
|
|
794
|
-
try {
|
|
795
|
-
await buf.flush();
|
|
796
|
-
} catch (err) {
|
|
797
|
-
log3.warn({ err, sessionId }, "Flush failed during stop");
|
|
798
|
-
}
|
|
799
|
-
buf.destroy();
|
|
800
|
-
}
|
|
801
|
-
this.textBuffers.clear();
|
|
802
|
-
await this.app.stop();
|
|
803
|
-
log3.info("Slack adapter stopped");
|
|
804
|
-
}
|
|
805
|
-
// --- MessagingAdapter implementations ---
|
|
806
|
-
async createSessionThread(sessionId, name) {
|
|
807
|
-
const meta = await this.channelManager.createChannel(sessionId, name);
|
|
808
|
-
this.sessions.set(sessionId, meta);
|
|
809
|
-
log3.info({ sessionId, channelId: meta.channelId, slug: meta.channelSlug }, "Session channel created");
|
|
810
|
-
return meta.channelSlug;
|
|
811
|
-
}
|
|
812
|
-
async renameSessionThread(sessionId, newName) {
|
|
813
|
-
const meta = this.sessions.get(sessionId);
|
|
814
|
-
if (!meta) return;
|
|
815
|
-
const newSlug = toSlug(newName, this.slackConfig.channelPrefix ?? "openacp");
|
|
816
|
-
try {
|
|
817
|
-
await this.queue.enqueue("conversations.rename", {
|
|
818
|
-
channel: meta.channelId,
|
|
819
|
-
name: newSlug
|
|
820
|
-
});
|
|
821
|
-
meta.channelSlug = newSlug;
|
|
822
|
-
const session = this.core.sessionManager.getSession(sessionId);
|
|
823
|
-
if (session) session.threadId = newSlug;
|
|
824
|
-
const existingRecord = this.core.sessionManager.getSessionRecord(sessionId);
|
|
825
|
-
await this.core.sessionManager.patchRecord(sessionId, {
|
|
826
|
-
name: newName,
|
|
827
|
-
platform: { ...existingRecord?.platform ?? {}, topicId: newSlug }
|
|
828
|
-
});
|
|
829
|
-
log3.info({ sessionId, newSlug }, "Session channel renamed");
|
|
830
|
-
} catch (err) {
|
|
831
|
-
log3.warn({ err, sessionId }, "Failed to rename Slack channel");
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
async deleteSessionThread(sessionId) {
|
|
835
|
-
const meta = this.sessions.get(sessionId);
|
|
836
|
-
if (!meta) return;
|
|
837
|
-
try {
|
|
838
|
-
await this.permissionHandler.cleanupSession(meta.channelId);
|
|
839
|
-
} catch (err) {
|
|
840
|
-
log3.warn({ err, sessionId }, "Failed to clean up permission buttons");
|
|
841
|
-
}
|
|
842
|
-
try {
|
|
843
|
-
await this.channelManager.archiveChannel(meta.channelId);
|
|
844
|
-
log3.info({ sessionId, channelId: meta.channelId }, "Session channel archived");
|
|
845
|
-
} catch (err) {
|
|
846
|
-
log3.warn({ err, sessionId }, "Failed to archive Slack channel");
|
|
847
|
-
}
|
|
848
|
-
this.sessions.delete(sessionId);
|
|
849
|
-
const buf = this.textBuffers.get(sessionId);
|
|
850
|
-
if (buf) {
|
|
851
|
-
buf.destroy();
|
|
852
|
-
this.textBuffers.delete(sessionId);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
_sessionMetas = /* @__PURE__ */ new Map();
|
|
856
|
-
getSessionMeta(sessionId) {
|
|
857
|
-
return this._sessionMetas.get(sessionId);
|
|
858
|
-
}
|
|
859
|
-
getTextBuffer(sessionId, channelId) {
|
|
860
|
-
let buf = this.textBuffers.get(sessionId);
|
|
861
|
-
if (!buf) {
|
|
862
|
-
buf = new SlackTextBuffer(channelId, sessionId, this.queue);
|
|
863
|
-
this.textBuffers.set(sessionId, buf);
|
|
864
|
-
}
|
|
865
|
-
return buf;
|
|
866
|
-
}
|
|
867
|
-
async sendMessage(sessionId, content) {
|
|
868
|
-
const meta = this.sessions.get(sessionId);
|
|
869
|
-
if (!meta) {
|
|
870
|
-
log3.warn({ sessionId }, "No Slack channel for session, skipping message");
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
this._sessionMetas.set(sessionId, meta);
|
|
874
|
-
try {
|
|
875
|
-
await super.sendMessage(sessionId, content);
|
|
876
|
-
} finally {
|
|
877
|
-
this._sessionMetas.delete(sessionId);
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
// --- Handler overrides (dispatched by base class) ---
|
|
881
|
-
async handleText(sessionId, content) {
|
|
882
|
-
const meta = this.getSessionMeta(sessionId);
|
|
883
|
-
if (!meta) return;
|
|
884
|
-
const buf = this.getTextBuffer(sessionId, meta.channelId);
|
|
885
|
-
buf.append(content.text ?? "");
|
|
886
|
-
}
|
|
887
|
-
async handleSessionEnd(sessionId, content) {
|
|
888
|
-
const meta = this.getSessionMeta(sessionId);
|
|
889
|
-
if (!meta) return;
|
|
890
|
-
await this.flushTextBuffer(sessionId);
|
|
891
|
-
const blocks = this.formatter.formatOutgoing(content);
|
|
892
|
-
if (blocks.length === 0) return;
|
|
893
|
-
try {
|
|
894
|
-
await this.queue.enqueue("chat.postMessage", {
|
|
895
|
-
channel: meta.channelId,
|
|
896
|
-
text: content.text ?? content.type,
|
|
897
|
-
blocks
|
|
898
|
-
});
|
|
899
|
-
} catch (err) {
|
|
900
|
-
log3.error({ err, sessionId, type: content.type }, "Failed to post Slack message");
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
async handleError(sessionId, content) {
|
|
904
|
-
const meta = this.getSessionMeta(sessionId);
|
|
905
|
-
if (!meta) return;
|
|
906
|
-
await this.flushTextBuffer(sessionId);
|
|
907
|
-
const blocks = this.formatter.formatOutgoing(content);
|
|
908
|
-
if (blocks.length === 0) return;
|
|
909
|
-
try {
|
|
910
|
-
await this.queue.enqueue("chat.postMessage", {
|
|
911
|
-
channel: meta.channelId,
|
|
912
|
-
text: content.text ?? content.type,
|
|
913
|
-
blocks
|
|
914
|
-
});
|
|
915
|
-
} catch (err) {
|
|
916
|
-
log3.error({ err, sessionId, type: content.type }, "Failed to post Slack message");
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
async handleAttachment(sessionId, content) {
|
|
920
|
-
const meta = this.getSessionMeta(sessionId);
|
|
921
|
-
if (!meta || !content.attachment) return;
|
|
922
|
-
if (content.attachment.type === "audio") {
|
|
923
|
-
try {
|
|
924
|
-
await this.uploadAudioFile(meta.channelId, content.attachment);
|
|
925
|
-
const buf = this.textBuffers.get(sessionId);
|
|
926
|
-
if (buf) await buf.stripTtsBlock();
|
|
927
|
-
} catch (err) {
|
|
928
|
-
log3.error({ err, sessionId }, "Failed to upload audio to Slack");
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
async handleThought(sessionId, content, _verbosity) {
|
|
933
|
-
await this.postFormattedMessage(sessionId, content);
|
|
934
|
-
}
|
|
935
|
-
async handleToolCall(sessionId, content, _verbosity) {
|
|
936
|
-
await this.postFormattedMessage(sessionId, content);
|
|
937
|
-
}
|
|
938
|
-
async handleToolUpdate(sessionId, content, _verbosity) {
|
|
939
|
-
await this.postFormattedMessage(sessionId, content);
|
|
940
|
-
}
|
|
941
|
-
async handlePlan(sessionId, content, _verbosity) {
|
|
942
|
-
await this.postFormattedMessage(sessionId, content);
|
|
943
|
-
}
|
|
944
|
-
async handleUsage(sessionId, content, _verbosity) {
|
|
945
|
-
await this.postFormattedMessage(sessionId, content);
|
|
946
|
-
}
|
|
947
|
-
async handleSystem(sessionId, content) {
|
|
948
|
-
await this.postFormattedMessage(sessionId, content);
|
|
949
|
-
}
|
|
950
|
-
// --- Private helpers ---
|
|
951
|
-
async flushTextBuffer(sessionId) {
|
|
952
|
-
const buf = this.textBuffers.get(sessionId);
|
|
953
|
-
if (buf) {
|
|
954
|
-
try {
|
|
955
|
-
await buf.flush();
|
|
956
|
-
} catch (err) {
|
|
957
|
-
log3.warn({ err, sessionId }, "Flush failed on session_end");
|
|
958
|
-
}
|
|
959
|
-
buf.destroy();
|
|
960
|
-
this.textBuffers.delete(sessionId);
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
async postFormattedMessage(sessionId, content) {
|
|
964
|
-
const meta = this.getSessionMeta(sessionId);
|
|
965
|
-
if (!meta) return;
|
|
966
|
-
const blocks = this.formatter.formatOutgoing(content);
|
|
967
|
-
if (blocks.length === 0) return;
|
|
968
|
-
try {
|
|
969
|
-
await this.queue.enqueue("chat.postMessage", {
|
|
970
|
-
channel: meta.channelId,
|
|
971
|
-
text: content.text ?? content.type,
|
|
972
|
-
blocks
|
|
973
|
-
});
|
|
974
|
-
} catch (err) {
|
|
975
|
-
log3.error({ err, sessionId, type: content.type }, "Failed to post Slack message");
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
// NOTE: Async flow — different from Telegram adapter.
|
|
979
|
-
// Telegram: sendPermissionRequest awaits user response inline.
|
|
980
|
-
// Slack: posts interactive buttons and returns immediately.
|
|
981
|
-
// Resolution happens asynchronously via the Bolt action handler in
|
|
982
|
-
// SlackPermissionHandler, which calls the PermissionResponseCallback
|
|
983
|
-
// passed during construction. The callback iterates sessions to find
|
|
984
|
-
// the matching permissionGate and resolves it.
|
|
985
|
-
async sendPermissionRequest(sessionId, request) {
|
|
986
|
-
const meta = this.sessions.get(sessionId);
|
|
987
|
-
if (!meta) return;
|
|
988
|
-
log3.info({ sessionId, requestId: request.id }, "Sending Slack permission request");
|
|
989
|
-
const blocks = this.formatter.formatPermissionRequest(request);
|
|
990
|
-
try {
|
|
991
|
-
const result = await this.queue.enqueue("chat.postMessage", {
|
|
992
|
-
channel: meta.channelId,
|
|
993
|
-
text: `Permission request: ${request.description}`,
|
|
994
|
-
blocks
|
|
995
|
-
});
|
|
996
|
-
const ts = result?.ts;
|
|
997
|
-
if (ts) {
|
|
998
|
-
this.permissionHandler.trackPendingMessage(request.id, meta.channelId, ts);
|
|
999
|
-
}
|
|
1000
|
-
} catch (err) {
|
|
1001
|
-
log3.error({ err, sessionId }, "Failed to post Slack permission request");
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
async sendNotification(notification) {
|
|
1005
|
-
if (!this.slackConfig.notificationChannelId) return;
|
|
1006
|
-
const emoji = {
|
|
1007
|
-
completed: "\u2705",
|
|
1008
|
-
error: "\u274C",
|
|
1009
|
-
permission: "\u{1F510}",
|
|
1010
|
-
input_required: "\u{1F4AC}"
|
|
1011
|
-
};
|
|
1012
|
-
const icon = emoji[notification.type] ?? "\u2139\uFE0F";
|
|
1013
|
-
const text = `${icon} *${notification.sessionName ?? "Session"}*
|
|
1014
|
-
${notification.summary}`;
|
|
1015
|
-
const blocks = this.formatter.formatNotification(text);
|
|
1016
|
-
try {
|
|
1017
|
-
await this.queue.enqueue("chat.postMessage", {
|
|
1018
|
-
channel: this.slackConfig.notificationChannelId,
|
|
1019
|
-
text,
|
|
1020
|
-
blocks
|
|
1021
|
-
});
|
|
1022
|
-
} catch (err) {
|
|
1023
|
-
log3.warn({ err, sessionId: notification.sessionId }, "Failed to send Slack notification");
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
};
|
|
1027
|
-
export {
|
|
1028
|
-
SlackAdapter
|
|
1029
|
-
};
|
|
1030
|
-
//# sourceMappingURL=adapter-UORRGHNH.js.map
|