hyperclaw 4.0.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -3
- package/dist/a2ui-protocol-CfBI44-Q.js +75 -0
- package/dist/agents-routing-ChHiZp36.js +327 -0
- package/dist/agents-routing-ChqZ6l2S.js +4 -0
- package/dist/api-keys-guide-BCcOl0Q7.js +149 -0
- package/dist/audit-BaIiyWFu.js +441 -0
- package/dist/bounty-tools-DWudyZie.js +211 -0
- package/dist/browser-tools-BsTeGMnX.js +5 -0
- package/dist/browser-tools-D8_rLe2p.js +179 -0
- package/dist/claw-tasks-CgTsiNE8.js +80 -0
- package/dist/connector-5N0-X_xs.js +194 -0
- package/dist/connector-B3v0qcXg.js +425 -0
- package/dist/connector-B8R3iBY1.js +280 -0
- package/dist/connector-BAM-08NN.js +189 -0
- package/dist/connector-BC8FIVu4.js +181 -0
- package/dist/connector-BDmwwaVc.js +213 -0
- package/dist/connector-BGjbBy69.js +225 -0
- package/dist/connector-BO2SRzfG.js +218 -0
- package/dist/connector-BfXky0L3.js +167 -0
- package/dist/connector-BiiSJpx3.js +192 -0
- package/dist/connector-BnDmIhIu.js +85 -0
- package/dist/connector-C1HSoUyk.js +189 -0
- package/dist/connector-CKQHZOXg.js +568 -0
- package/dist/connector-CRl-iidy.js +239 -0
- package/dist/connector-Ci9glMD-.js +340 -0
- package/dist/connector-CjtZIEDj.js +181 -0
- package/dist/connector-Ck6JtOsX.js +531 -0
- package/dist/connector-D8Kelee0.js +286 -0
- package/dist/connector-DAnRJ0oP.js +162 -0
- package/dist/connector-DXTp5PE8.js +508 -0
- package/dist/connector-Dih6dUPP.js +173 -0
- package/dist/connector-DqTH_tPX.js +182 -0
- package/dist/connector-DrnEiiyP.js +419 -0
- package/dist/connector-DtR5GGTX.js +167 -0
- package/dist/connector-Tky_qS_K.js +350 -0
- package/dist/connector-ZSc3oTTy.js +305 -0
- package/dist/connector-sW5yhU1m.js +498 -0
- package/dist/connector-u3ICd3Ic.js +552 -0
- package/dist/cost-tracker-DD9wtWsr.js +103 -0
- package/dist/credentials-store-C6ir0Dae.js +4 -0
- package/dist/credentials-store-H13LqOwJ.js +77 -0
- package/dist/cron-tasks-Bli7Kzd2.js +82 -0
- package/dist/daemon-Bg4GtCmc.js +318 -0
- package/dist/daemon-DhmwY8k4.js +5 -0
- package/dist/delivery-BmIYy9VQ.js +4 -0
- package/dist/delivery-pWUPBp1F.js +95 -0
- package/dist/destructive-gate-D6vWOdEl.js +101 -0
- package/dist/developer-keys-CPWT7Q6S.js +8 -0
- package/dist/developer-keys-DrrcUqFa.js +127 -0
- package/dist/doctor-BvCe8BBk.js +230 -0
- package/dist/doctor-CxyPLYsJ.js +6 -0
- package/dist/engine-CEDSqXfw.js +256 -0
- package/dist/engine-Da4JMNpI.js +7 -0
- package/dist/env-resolve-CiXbWYwe.js +10 -0
- package/dist/env-resolve-CmGWhWXJ.js +115 -0
- package/dist/extraction-tools-HOZstZ0y.js +91 -0
- package/dist/extraction-tools-m4lmAv7l.js +5 -0
- package/dist/form_data-Cz040rio.js +8657 -0
- package/dist/gmail-watch-setup-Du7DVV7S.js +40 -0
- package/dist/health-B-asI__D.js +6 -0
- package/dist/health-Ds2YlpTB.js +152 -0
- package/dist/heartbeat-engine-BYT5ayQH.js +83 -0
- package/dist/hub-D0XwdjM-.js +515 -0
- package/dist/hub-LiD5Iztb.js +6 -0
- package/dist/hyperclawbot-zvczQgKx.js +505 -0
- package/dist/inference-BKVkBREb.js +6 -0
- package/dist/inference-DCXH4Q3x.js +922 -0
- package/dist/knowledge-graph-iBG76fvm.js +131 -0
- package/dist/loader-CC45xGpC.js +4 -0
- package/dist/loader-CnEdOyjT.js +400 -0
- package/dist/logger-ybOp7VOC.js +83 -0
- package/dist/manager-03ipO9R0.js +105 -0
- package/dist/manager-BpDfbDjg.js +117 -0
- package/dist/manager-Bxl0sqlh.js +4 -0
- package/dist/manager-CrVDn6eN.js +6 -0
- package/dist/manager-FCgF1plu.js +218 -0
- package/dist/manager-rgCsaWT1.js +40 -0
- package/dist/mcp-CfoSU4Uz.js +139 -0
- package/dist/mcp-loader-DkRBsLpk.js +94 -0
- package/dist/memory-BlHL7JCO.js +4 -0
- package/dist/memory-DsS_eFvJ.js +270 -0
- package/dist/memory-auto-BkvtSFUw.js +5 -0
- package/dist/memory-auto-Bnz_-1wP.js +306 -0
- package/dist/memory-integration-cSYkZyEo.js +91 -0
- package/dist/moltbook-BtLDZTfM.js +81 -0
- package/dist/node-Dw2Gi-cP.js +222 -0
- package/dist/nodes-registry-B8dmrlLv.js +52 -0
- package/dist/oauth-flow-DQPvMHRH.js +150 -0
- package/dist/oauth-provider-Uo4Nib_c.js +110 -0
- package/dist/observability-BV-Yx0V9.js +89 -0
- package/dist/onboard-0WoDxbv_.js +10 -0
- package/dist/onboard-BXNXCQp4.js +4070 -0
- package/dist/orchestrator-DmnEvMaL.js +189 -0
- package/dist/orchestrator-RI3bpqqc.js +6 -0
- package/dist/pairing-6iM27aD8.js +196 -0
- package/dist/pairing-dGoiGepK.js +4 -0
- package/dist/pc-access-CgCsYrpt.js +8 -0
- package/dist/pc-access-_iH2aorG.js +819 -0
- package/dist/pending-approval-CUXjysAo.js +22 -0
- package/dist/reminders-store-Drjed_-h.js +58 -0
- package/dist/renderer-BVQrd0_g.js +225 -0
- package/dist/rules-BE4GV6cV.js +103 -0
- package/dist/run-main.js +1607 -443
- package/dist/runner-DatMMYYE.js +1271 -0
- package/dist/sdk/index.js +2 -2
- package/dist/sdk/index.mjs +2 -2
- package/dist/security-BqNyT4ID.js +4 -0
- package/dist/security-tpgqPWWH.js +73 -0
- package/dist/server-D4wVHiX9.js +4 -0
- package/dist/server-Dh3JlBFB.js +1255 -0
- package/dist/session-store-BUiPz0Vv.js +5 -0
- package/dist/session-store-is4B6qmD.js +113 -0
- package/dist/sessions-tools-CbUTFe4i.js +5 -0
- package/dist/sessions-tools-CeqD7iil.js +95 -0
- package/dist/skill-loader-BaNLVmJy.js +7 -0
- package/dist/skill-loader-HgpF6Vqs.js +159 -0
- package/dist/skill-runtime-CJN24QPW.js +102 -0
- package/dist/skill-runtime-w1ig_lcw.js +5 -0
- package/dist/src-BxPHKO5x.js +63 -0
- package/dist/src-DIc-L2IG.js +20 -0
- package/dist/src-g_rNx5rh.js +458 -0
- package/dist/sub-agent-tools-CHQoHz9c.js +39 -0
- package/dist/theme-DcxwcUgZ.js +180 -0
- package/dist/theme-cx0fkgWC.js +8 -0
- package/dist/tool-policy-CNT-mF2Z.js +189 -0
- package/dist/tts-elevenlabs-BRosZv-f.js +61 -0
- package/dist/update-check-C2Dz85wJ.js +81 -0
- package/dist/vision-BMmiIKy7.js +121 -0
- package/dist/vision-tools-DVuYc17I.js +51 -0
- package/dist/vision-tools-U3YC4L-g.js +5 -0
- package/dist/voice-transcription-B555DbWR.js +138 -0
- package/dist/website-watch-tools-DFMrJU-R.js +139 -0
- package/dist/website-watch-tools-Du3W5sN7.js +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-jS-bbMI5.js');
|
|
2
|
+
const chalk = require_chunk.__toESM(require("chalk"));
|
|
3
|
+
const fs_extra = require_chunk.__toESM(require("fs-extra"));
|
|
4
|
+
const path = require_chunk.__toESM(require("path"));
|
|
5
|
+
const os = require_chunk.__toESM(require("os"));
|
|
6
|
+
const crypto = require_chunk.__toESM(require("crypto"));
|
|
7
|
+
const https = require_chunk.__toESM(require("https"));
|
|
8
|
+
const events = require_chunk.__toESM(require("events"));
|
|
9
|
+
|
|
10
|
+
//#region extensions/slack/src/connector.ts
|
|
11
|
+
const STATE_BASE = path.default.join(os.default.homedir(), ".hyperclaw");
|
|
12
|
+
const DEFAULT_CHUNK = 3e3;
|
|
13
|
+
function slackApi(token, method, body = {}) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const payload = JSON.stringify(body);
|
|
16
|
+
const req = https.default.request({
|
|
17
|
+
hostname: "slack.com",
|
|
18
|
+
port: 443,
|
|
19
|
+
path: `/api/${method}`,
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: {
|
|
22
|
+
Authorization: `Bearer ${token}`,
|
|
23
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
24
|
+
"Content-Length": Buffer.byteLength(payload)
|
|
25
|
+
}
|
|
26
|
+
}, (res) => {
|
|
27
|
+
let data = "";
|
|
28
|
+
res.on("data", (c) => data += c);
|
|
29
|
+
res.on("end", () => {
|
|
30
|
+
try {
|
|
31
|
+
const r = JSON.parse(data);
|
|
32
|
+
if (!r.ok) reject(new Error(r.error || "Slack API error"));
|
|
33
|
+
else resolve(r);
|
|
34
|
+
} catch {
|
|
35
|
+
reject(new Error("Slack: invalid JSON"));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
req.on("error", reject);
|
|
40
|
+
req.setTimeout(3e4, () => {
|
|
41
|
+
req.destroy();
|
|
42
|
+
reject(new Error("Slack API timeout"));
|
|
43
|
+
});
|
|
44
|
+
req.write(payload);
|
|
45
|
+
req.end();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function chunkText(text, limit, mode) {
|
|
49
|
+
if (mode === "newline") {
|
|
50
|
+
const paras = text.split(/\n\n+/);
|
|
51
|
+
const chunks$1 = [];
|
|
52
|
+
let cur = "";
|
|
53
|
+
for (const p of paras) if ((cur + "\n\n" + p).length > limit && cur) {
|
|
54
|
+
chunks$1.push(cur.trim());
|
|
55
|
+
cur = p;
|
|
56
|
+
} else cur = cur ? cur + "\n\n" + p : p;
|
|
57
|
+
if (cur) chunks$1.push(cur.trim());
|
|
58
|
+
return chunks$1.filter(Boolean);
|
|
59
|
+
}
|
|
60
|
+
const chunks = [];
|
|
61
|
+
for (let i = 0; i < text.length; i += limit) chunks.push(text.slice(i, i + limit));
|
|
62
|
+
return chunks.length ? chunks : [""];
|
|
63
|
+
}
|
|
64
|
+
function generateCode() {
|
|
65
|
+
return Array.from({ length: 6 }, () => "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"[Math.floor(Math.random() * 32)]).join("");
|
|
66
|
+
}
|
|
67
|
+
var SlackAccount = class extends events.EventEmitter {
|
|
68
|
+
cfg;
|
|
69
|
+
accountId;
|
|
70
|
+
botUserId = "";
|
|
71
|
+
teamName = "";
|
|
72
|
+
running = false;
|
|
73
|
+
wsReconnectDelay = 1e3;
|
|
74
|
+
constructor(accountId, cfg) {
|
|
75
|
+
super();
|
|
76
|
+
this.accountId = accountId;
|
|
77
|
+
this.cfg = {
|
|
78
|
+
mode: "socket",
|
|
79
|
+
userTokenReadOnly: true,
|
|
80
|
+
textChunkLimit: DEFAULT_CHUNK,
|
|
81
|
+
chunkMode: "length",
|
|
82
|
+
streaming: "partial",
|
|
83
|
+
nativeStreaming: true,
|
|
84
|
+
dmPolicy: "pairing",
|
|
85
|
+
allowFrom: [],
|
|
86
|
+
groupPolicy: "allowlist",
|
|
87
|
+
channels: {},
|
|
88
|
+
replyToMode: "off",
|
|
89
|
+
thread: {
|
|
90
|
+
historyScope: "thread",
|
|
91
|
+
inheritParent: false,
|
|
92
|
+
initialHistoryLimit: 20
|
|
93
|
+
},
|
|
94
|
+
actions: {
|
|
95
|
+
messages: true,
|
|
96
|
+
reactions: true,
|
|
97
|
+
pins: true,
|
|
98
|
+
memberInfo: true,
|
|
99
|
+
emojiList: true
|
|
100
|
+
},
|
|
101
|
+
...cfg
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async authenticate() {
|
|
105
|
+
const token = this.cfg.botToken;
|
|
106
|
+
const auth = await slackApi(token, "auth.test");
|
|
107
|
+
this.botUserId = auth.user_id;
|
|
108
|
+
this.teamName = auth.team;
|
|
109
|
+
console.log(chalk.default.green(` 💼 Slack [${this.accountId}]: @${auth.user} in ${auth.team}`));
|
|
110
|
+
}
|
|
111
|
+
async connect() {
|
|
112
|
+
await this.loadState();
|
|
113
|
+
await this.authenticate();
|
|
114
|
+
this.running = true;
|
|
115
|
+
this.emit("connected", {
|
|
116
|
+
userId: this.botUserId,
|
|
117
|
+
team: this.teamName,
|
|
118
|
+
accountId: this.accountId
|
|
119
|
+
});
|
|
120
|
+
const mode = this.cfg.mode ?? "socket";
|
|
121
|
+
if (mode === "socket") this.connectSocketMode();
|
|
122
|
+
}
|
|
123
|
+
disconnect() {
|
|
124
|
+
this.running = false;
|
|
125
|
+
}
|
|
126
|
+
async connectSocketMode() {
|
|
127
|
+
if (!this.cfg.appToken) {
|
|
128
|
+
console.error(`[slack:${this.accountId}] Socket Mode requires appToken (xapp-...)`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
let WebSocketImpl;
|
|
132
|
+
try {
|
|
133
|
+
WebSocketImpl = await import("ws");
|
|
134
|
+
} catch {
|
|
135
|
+
console.error(`[slack:${this.accountId}] ws package not found — install: npm i ws`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
while (this.running) try {
|
|
139
|
+
const resp = await slackApi(this.cfg.appToken, "apps.connections.open");
|
|
140
|
+
const wsUrl = resp.url;
|
|
141
|
+
await new Promise((resolve) => {
|
|
142
|
+
const WS = WebSocketImpl.default ?? WebSocketImpl;
|
|
143
|
+
const ws = new WS(wsUrl);
|
|
144
|
+
ws.on("open", () => {
|
|
145
|
+
this.wsReconnectDelay = 1e3;
|
|
146
|
+
});
|
|
147
|
+
ws.on("message", (raw) => {
|
|
148
|
+
let envelope;
|
|
149
|
+
try {
|
|
150
|
+
envelope = JSON.parse(raw.toString());
|
|
151
|
+
} catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (envelope.envelope_id) ws.send(JSON.stringify({ envelope_id: envelope.envelope_id }));
|
|
155
|
+
this.handleSocketEnvelope(envelope);
|
|
156
|
+
});
|
|
157
|
+
ws.on("close", () => resolve());
|
|
158
|
+
ws.on("error", () => resolve());
|
|
159
|
+
});
|
|
160
|
+
if (!this.running) break;
|
|
161
|
+
await new Promise((r) => setTimeout(r, this.wsReconnectDelay));
|
|
162
|
+
this.wsReconnectDelay = Math.min(this.wsReconnectDelay * 2, 3e4);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
if (this.running) {
|
|
165
|
+
console.log(chalk.default.yellow(` ⚠ Slack [${this.accountId}] socket: ${e.message}`));
|
|
166
|
+
await new Promise((r) => setTimeout(r, this.wsReconnectDelay));
|
|
167
|
+
this.wsReconnectDelay = Math.min(this.wsReconnectDelay * 2, 3e4);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async handleSocketEnvelope(envelope) {
|
|
172
|
+
const type = envelope.type || "";
|
|
173
|
+
switch (type) {
|
|
174
|
+
case "hello": break;
|
|
175
|
+
case "disconnect": break;
|
|
176
|
+
case "events_api": {
|
|
177
|
+
const payload = envelope.payload || {};
|
|
178
|
+
if (payload.type === "event_callback") await this.handleEvent(payload.event, payload);
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case "slash_commands": {
|
|
182
|
+
const p = envelope.payload || {};
|
|
183
|
+
if (p.command || p.user_id) await this.handleSlashCommand(p);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "block_actions":
|
|
187
|
+
case "interactive": {
|
|
188
|
+
const p = envelope.payload || {};
|
|
189
|
+
this.emit("interaction", {
|
|
190
|
+
accountId: this.accountId,
|
|
191
|
+
type,
|
|
192
|
+
payload: p
|
|
193
|
+
});
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async handleWebhook(body, signature, timestamp) {
|
|
199
|
+
if (!this.verifySignature(body, signature, timestamp)) {
|
|
200
|
+
console.log(chalk.default.yellow(` ⚠ Slack [${this.accountId}]: invalid signature`));
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
let payload;
|
|
204
|
+
try {
|
|
205
|
+
payload = JSON.parse(body);
|
|
206
|
+
} catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
if (payload.type === "url_verification") return payload.challenge;
|
|
210
|
+
if (payload.type === "event_callback") await this.handleEvent(payload.event, payload);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
verifySignature(body, signature, timestamp) {
|
|
214
|
+
if (!this.cfg.signingSecret) return false;
|
|
215
|
+
const ts = parseInt(timestamp);
|
|
216
|
+
if (Math.abs(Date.now() / 1e3 - ts) > 300) return false;
|
|
217
|
+
const baseString = `v0:${timestamp}:${body}`;
|
|
218
|
+
const hash = "v0=" + crypto.default.createHmac("sha256", this.cfg.signingSecret).update(baseString).digest("hex");
|
|
219
|
+
try {
|
|
220
|
+
return crypto.default.timingSafeEqual(Buffer.from(hash), Buffer.from(signature));
|
|
221
|
+
} catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async handleEvent(event, _outer) {
|
|
226
|
+
if (!event) return;
|
|
227
|
+
if (event.bot_id || event.user === this.botUserId) return;
|
|
228
|
+
const evType = event.type || "";
|
|
229
|
+
if (["reaction_added", "reaction_removed"].includes(evType)) {
|
|
230
|
+
this.emit("system_event", {
|
|
231
|
+
accountId: this.accountId,
|
|
232
|
+
type: evType,
|
|
233
|
+
event
|
|
234
|
+
});
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if ([
|
|
238
|
+
"member_joined_channel",
|
|
239
|
+
"member_left_channel",
|
|
240
|
+
"channel_rename",
|
|
241
|
+
"pin_added",
|
|
242
|
+
"pin_removed"
|
|
243
|
+
].includes(evType)) {
|
|
244
|
+
this.emit("system_event", {
|
|
245
|
+
accountId: this.accountId,
|
|
246
|
+
type: evType,
|
|
247
|
+
event
|
|
248
|
+
});
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (evType !== "message" && evType !== "app_mention") return;
|
|
252
|
+
if (!event.text || !event.user || !event.channel) return;
|
|
253
|
+
if (event.subtype && event.subtype !== "file_share") return;
|
|
254
|
+
const channelType = event.channel_type || "";
|
|
255
|
+
const isDM = channelType === "im";
|
|
256
|
+
const isGroup = channelType === "mpim";
|
|
257
|
+
const isChannel = !isDM && !isGroup;
|
|
258
|
+
const isAppMention = evType === "app_mention";
|
|
259
|
+
const threadTs = event.thread_ts;
|
|
260
|
+
const ts = event.ts || "";
|
|
261
|
+
const text = event.text.replace(/<@[A-Z0-9]+>/g, "").trim();
|
|
262
|
+
if (isDM) {
|
|
263
|
+
const dmEnabled = this.cfg.dm?.enabled !== false;
|
|
264
|
+
if (!dmEnabled) return;
|
|
265
|
+
const allowed = await this.checkDMPolicy(event.user, event.channel, text, ts);
|
|
266
|
+
if (!allowed) return;
|
|
267
|
+
} else if (isGroup) {
|
|
268
|
+
const groupEnabled = this.cfg.dm?.groupEnabled === true;
|
|
269
|
+
if (!groupEnabled) return;
|
|
270
|
+
const groupChannels = this.cfg.dm?.groupChannels;
|
|
271
|
+
if (groupChannels?.length && !groupChannels.includes(event.channel)) return;
|
|
272
|
+
} else if (!this.checkChannelPolicy(event.channel, event.user, text, isAppMention)) return;
|
|
273
|
+
this.addReaction(this.resolveAckReaction(), event.channel, ts);
|
|
274
|
+
const chatType = isDM ? "direct" : isGroup ? "group" : "channel";
|
|
275
|
+
const replyToMode = this.resolveReplyToMode(chatType);
|
|
276
|
+
this.emit("message", {
|
|
277
|
+
channelId: "slack",
|
|
278
|
+
accountId: this.accountId,
|
|
279
|
+
chatId: event.channel,
|
|
280
|
+
from: event.user,
|
|
281
|
+
text,
|
|
282
|
+
ts,
|
|
283
|
+
threadTs,
|
|
284
|
+
isDM,
|
|
285
|
+
isGroup,
|
|
286
|
+
isChannel,
|
|
287
|
+
chatType,
|
|
288
|
+
replyToMode
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
async handleSlashCommand(payload) {
|
|
292
|
+
const text = [payload.command, payload.text].filter(Boolean).join(" ");
|
|
293
|
+
const userId = payload.user_id || "";
|
|
294
|
+
const channelId = payload.channel_id || "";
|
|
295
|
+
if (!userId || !channelId) return;
|
|
296
|
+
this.emit("message", {
|
|
297
|
+
channelId: "slack",
|
|
298
|
+
accountId: this.accountId,
|
|
299
|
+
chatId: channelId,
|
|
300
|
+
from: userId,
|
|
301
|
+
text,
|
|
302
|
+
isDM: false,
|
|
303
|
+
isSlashCommand: true,
|
|
304
|
+
ephemeral: this.cfg.slashCommand?.ephemeral !== false
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
async checkDMPolicy(userId, channelId, text, ts) {
|
|
308
|
+
const policy = this.cfg.dm?.policy ?? this.cfg.dmPolicy ?? "pairing";
|
|
309
|
+
const allowFrom = this.cfg.allowFrom ?? this.cfg.dm?.allowFrom ?? [];
|
|
310
|
+
switch (policy) {
|
|
311
|
+
case "disabled": return false;
|
|
312
|
+
case "open": return true;
|
|
313
|
+
case "allowlist":
|
|
314
|
+
if (allowFrom.includes(userId) || allowFrom.includes("*")) return true;
|
|
315
|
+
await this.sendMessage(channelId, "HyperClaw: Not on allowlist.");
|
|
316
|
+
return false;
|
|
317
|
+
case "pairing": {
|
|
318
|
+
if (this.cfg.approvedPairings.includes(userId)) return true;
|
|
319
|
+
const upper = text.trim().toUpperCase().match(/[A-Z0-9]{6}/)?.[0];
|
|
320
|
+
if (upper && this.cfg.pendingPairings[upper]) {
|
|
321
|
+
this.cfg.approvedPairings.push(userId);
|
|
322
|
+
delete this.cfg.pendingPairings[upper];
|
|
323
|
+
await this.saveState();
|
|
324
|
+
await this.sendMessage(channelId, "Paired! You can now send messages.");
|
|
325
|
+
this.emit("pairing:approved", {
|
|
326
|
+
userId,
|
|
327
|
+
channelId: "slack",
|
|
328
|
+
accountId: this.accountId
|
|
329
|
+
});
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
const code = generateCode();
|
|
333
|
+
this.cfg.pendingPairings[code] = userId;
|
|
334
|
+
await this.saveState();
|
|
335
|
+
await this.sendMessage(channelId, `*HyperClaw Pairing*\n\nCode: \`${code}\`\nApprove: \`hyperclaw pairing approve slack ${code}\``);
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
checkChannelPolicy(channelId, userId, text, isAppMention) {
|
|
342
|
+
const policy = this.cfg.groupPolicy ?? "allowlist";
|
|
343
|
+
if (policy === "disabled") return false;
|
|
344
|
+
const chCfg = this.cfg.channels?.[channelId] || {};
|
|
345
|
+
if (policy === "open") {
|
|
346
|
+
if (chCfg.allowFrom?.length && !chCfg.allowFrom.includes(userId)) return false;
|
|
347
|
+
if (chCfg.users?.length && !chCfg.users.includes(userId)) return false;
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
if (!this.cfg.channels?.[channelId]) return isAppMention;
|
|
351
|
+
if (chCfg.allowFrom?.length && !chCfg.allowFrom.includes(userId)) return false;
|
|
352
|
+
if (chCfg.users?.length && !chCfg.users.includes(userId)) return false;
|
|
353
|
+
if (chCfg.requireMention !== false && !isAppMention) return false;
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
resolveAckReaction() {
|
|
357
|
+
return this.cfg.ackReaction ?? "";
|
|
358
|
+
}
|
|
359
|
+
resolveTypingReaction() {
|
|
360
|
+
return this.cfg.typingReaction ?? "";
|
|
361
|
+
}
|
|
362
|
+
resolveReplyToMode(chatType) {
|
|
363
|
+
const byType = this.cfg.replyToModeByChatType;
|
|
364
|
+
if (byType) {
|
|
365
|
+
if (chatType === "direct" && byType.direct) return byType.direct;
|
|
366
|
+
if (chatType === "group" && byType.group) return byType.group;
|
|
367
|
+
if (chatType === "channel" && byType.channel) return byType.channel;
|
|
368
|
+
}
|
|
369
|
+
return this.cfg.replyToMode ?? "off";
|
|
370
|
+
}
|
|
371
|
+
async addReaction(emoji, channel, timestamp) {
|
|
372
|
+
if (!emoji || !channel || !timestamp) return;
|
|
373
|
+
const name = emoji.replace(/:/g, "");
|
|
374
|
+
await slackApi(this.cfg.botToken, "reactions.add", {
|
|
375
|
+
channel,
|
|
376
|
+
timestamp,
|
|
377
|
+
name
|
|
378
|
+
}).catch(() => {});
|
|
379
|
+
}
|
|
380
|
+
async removeReaction(emoji, channel, timestamp) {
|
|
381
|
+
if (!emoji || !channel || !timestamp) return;
|
|
382
|
+
const name = emoji.replace(/:/g, "");
|
|
383
|
+
await slackApi(this.cfg.botToken, "reactions.remove", {
|
|
384
|
+
channel,
|
|
385
|
+
timestamp,
|
|
386
|
+
name
|
|
387
|
+
}).catch(() => {});
|
|
388
|
+
}
|
|
389
|
+
async sendMessage(channel, text, threadTs, opts = {}) {
|
|
390
|
+
const limit = this.cfg.textChunkLimit ?? DEFAULT_CHUNK;
|
|
391
|
+
const mode = this.cfg.chunkMode ?? "length";
|
|
392
|
+
const chunks = chunkText(text, limit, mode);
|
|
393
|
+
const replyThread = threadTs && this.resolveReplyToMode("channel") !== "off";
|
|
394
|
+
for (const chunk of chunks) {
|
|
395
|
+
const payload = {
|
|
396
|
+
channel,
|
|
397
|
+
text: chunk,
|
|
398
|
+
mrkdwn: true,
|
|
399
|
+
...replyThread ? { thread_ts: threadTs } : {}
|
|
400
|
+
};
|
|
401
|
+
if (opts.ephemeral && opts.userId) await slackApi(this.cfg.botToken, "chat.postEphemeral", {
|
|
402
|
+
...payload,
|
|
403
|
+
user: opts.userId
|
|
404
|
+
});
|
|
405
|
+
else await slackApi(this.cfg.botToken, "chat.postMessage", payload);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async sendTyping(channelId) {}
|
|
409
|
+
async startStream(channel, threadTs) {
|
|
410
|
+
if (this.cfg.streaming === "off" || !this.cfg.nativeStreaming) return null;
|
|
411
|
+
try {
|
|
412
|
+
const r = await slackApi(this.cfg.botToken, "chat.startStream", {
|
|
413
|
+
channel,
|
|
414
|
+
...threadTs ? { thread_ts: threadTs } : {}
|
|
415
|
+
});
|
|
416
|
+
return r.stream_ts || null;
|
|
417
|
+
} catch {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
async appendStream(streamTs, text) {
|
|
422
|
+
await slackApi(this.cfg.botToken, "chat.appendStream", {
|
|
423
|
+
stream_ts: streamTs,
|
|
424
|
+
text
|
|
425
|
+
}).catch(() => {});
|
|
426
|
+
}
|
|
427
|
+
async stopStream(streamTs, text) {
|
|
428
|
+
await slackApi(this.cfg.botToken, "chat.stopStream", {
|
|
429
|
+
stream_ts: streamTs,
|
|
430
|
+
text
|
|
431
|
+
}).catch(() => {});
|
|
432
|
+
}
|
|
433
|
+
async setAssistantStatus(channelId, threadTs, status) {
|
|
434
|
+
await slackApi(this.cfg.botToken, "assistant.threads.setStatus", {
|
|
435
|
+
channel_id: channelId,
|
|
436
|
+
thread_ts: threadTs,
|
|
437
|
+
status
|
|
438
|
+
}).catch(() => {});
|
|
439
|
+
}
|
|
440
|
+
approvePairing(code) {
|
|
441
|
+
const upper = code.toUpperCase();
|
|
442
|
+
if (!this.cfg.pendingPairings[upper]) return false;
|
|
443
|
+
this.cfg.approvedPairings.push(this.cfg.pendingPairings[upper]);
|
|
444
|
+
delete this.cfg.pendingPairings[upper];
|
|
445
|
+
this.saveState();
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
async openDM(userId) {
|
|
449
|
+
const r = await slackApi(this.cfg.botToken, "conversations.open", { users: userId });
|
|
450
|
+
return r.channel.id;
|
|
451
|
+
}
|
|
452
|
+
async sendDM(userId, text) {
|
|
453
|
+
const channelId = await this.openDM(userId);
|
|
454
|
+
await this.sendMessage(channelId, text);
|
|
455
|
+
}
|
|
456
|
+
stateFile() {
|
|
457
|
+
return path.default.join(STATE_BASE, `slack-state-${this.accountId}.json`);
|
|
458
|
+
}
|
|
459
|
+
async loadState() {
|
|
460
|
+
try {
|
|
461
|
+
const s = await fs_extra.default.readJson(this.stateFile());
|
|
462
|
+
if (s.p) this.cfg.pendingPairings = s.p;
|
|
463
|
+
if (s.a) this.cfg.approvedPairings = s.a;
|
|
464
|
+
} catch {}
|
|
465
|
+
}
|
|
466
|
+
async saveState() {
|
|
467
|
+
await fs_extra.default.ensureDir(STATE_BASE);
|
|
468
|
+
await fs_extra.default.writeJson(this.stateFile(), {
|
|
469
|
+
p: this.cfg.pendingPairings,
|
|
470
|
+
a: this.cfg.approvedPairings
|
|
471
|
+
}, { spaces: 2 });
|
|
472
|
+
}
|
|
473
|
+
isRunning() {
|
|
474
|
+
return this.running;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
var SlackConnector = class extends events.EventEmitter {
|
|
478
|
+
config;
|
|
479
|
+
accounts = [];
|
|
480
|
+
constructor(config) {
|
|
481
|
+
super();
|
|
482
|
+
this.config = config;
|
|
483
|
+
}
|
|
484
|
+
async connect() {
|
|
485
|
+
const sharedState = {
|
|
486
|
+
approvedPairings: this.config.approvedPairings ?? [],
|
|
487
|
+
pendingPairings: this.config.pendingPairings ?? {}
|
|
488
|
+
};
|
|
489
|
+
const accountEntries = Object.entries(this.config.accounts || {});
|
|
490
|
+
if (accountEntries.length === 0) {
|
|
491
|
+
const acct = new SlackAccount("default", {
|
|
492
|
+
...this.config,
|
|
493
|
+
...sharedState
|
|
494
|
+
});
|
|
495
|
+
this.wire(acct);
|
|
496
|
+
await acct.connect();
|
|
497
|
+
this.accounts.push(acct);
|
|
498
|
+
} else for (const [id, acctCfg] of accountEntries) {
|
|
499
|
+
const merged = {
|
|
500
|
+
...this.config,
|
|
501
|
+
...acctCfg,
|
|
502
|
+
botToken: acctCfg.botToken || this.config.botToken,
|
|
503
|
+
...sharedState
|
|
504
|
+
};
|
|
505
|
+
if (!merged.botToken) {
|
|
506
|
+
console.error(`[slack] Account "${id}" has no botToken — skipping`);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
const acct = new SlackAccount(id, merged);
|
|
510
|
+
this.wire(acct);
|
|
511
|
+
try {
|
|
512
|
+
await acct.connect();
|
|
513
|
+
this.accounts.push(acct);
|
|
514
|
+
} catch (e) {
|
|
515
|
+
console.error(`[slack] Account "${id}" failed: ${e.message}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
wire(acct) {
|
|
520
|
+
acct.on("message", (msg) => this.emit("message", msg));
|
|
521
|
+
acct.on("connected", (info) => this.emit("connected", info));
|
|
522
|
+
acct.on("pairing:approved", (info) => this.emit("pairing:approved", info));
|
|
523
|
+
acct.on("system_event", (ev) => this.emit("system_event", ev));
|
|
524
|
+
acct.on("interaction", (ev) => this.emit("interaction", ev));
|
|
525
|
+
}
|
|
526
|
+
async sendMessage(channel, text, threadTs) {
|
|
527
|
+
const acct = this.accounts[0];
|
|
528
|
+
if (!acct) throw new Error("Slack: no connected account");
|
|
529
|
+
await acct.sendMessage(channel, text, threadTs);
|
|
530
|
+
}
|
|
531
|
+
async sendTyping(channelId) {}
|
|
532
|
+
async handleWebhook(body, signature, timestamp) {
|
|
533
|
+
for (const acct of this.accounts) {
|
|
534
|
+
const r = await acct.handleWebhook(body, signature, timestamp);
|
|
535
|
+
if (r != null) return r;
|
|
536
|
+
}
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
approvePairing(code) {
|
|
540
|
+
return this.accounts.some((a) => a.approvePairing(code));
|
|
541
|
+
}
|
|
542
|
+
disconnect() {
|
|
543
|
+
for (const a of this.accounts) a.disconnect();
|
|
544
|
+
this.accounts = [];
|
|
545
|
+
}
|
|
546
|
+
isRunning() {
|
|
547
|
+
return this.accounts.some((a) => a.isRunning());
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
//#endregion
|
|
552
|
+
exports.SlackConnector = SlackConnector;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-jS-bbMI5.js');
|
|
2
|
+
const fs_extra = require_chunk.__toESM(require("fs-extra"));
|
|
3
|
+
const path = require_chunk.__toESM(require("path"));
|
|
4
|
+
|
|
5
|
+
//#region src/infra/cost-tracker.ts
|
|
6
|
+
/** Rough cost per 1M tokens by provider/model (Claude Sonnet ballpark). */
|
|
7
|
+
const DEFAULT_COST_PER_1M_INPUT = 3;
|
|
8
|
+
const DEFAULT_COST_PER_1M_OUTPUT = 15;
|
|
9
|
+
function estimateCost(usage, model) {
|
|
10
|
+
if (!usage) return 0;
|
|
11
|
+
const inp = (usage.input || 0) + (usage.cacheRead || 0);
|
|
12
|
+
const inputCost = inp / 1e6 * DEFAULT_COST_PER_1M_INPUT;
|
|
13
|
+
const outputCost = (usage.output || 0) / 1e6 * DEFAULT_COST_PER_1M_OUTPUT;
|
|
14
|
+
return inputCost + outputCost;
|
|
15
|
+
}
|
|
16
|
+
/** Path to cost log file for a session. */
|
|
17
|
+
function getCostLogPath(baseDir, sessionId) {
|
|
18
|
+
return path.default.join(baseDir, "costs", `session-${sessionId}.jsonl`);
|
|
19
|
+
}
|
|
20
|
+
/** Append a cost entry for a session. */
|
|
21
|
+
async function recordUsage(baseDir, sessionId, usage, opts) {
|
|
22
|
+
const costUsd = estimateCost(usage, opts?.model);
|
|
23
|
+
const entry = {
|
|
24
|
+
sessionId,
|
|
25
|
+
source: opts?.source,
|
|
26
|
+
tenantId: opts?.tenantId,
|
|
27
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28
|
+
usage,
|
|
29
|
+
costUsd,
|
|
30
|
+
model: opts?.model
|
|
31
|
+
};
|
|
32
|
+
const dir = path.default.join(baseDir, "costs");
|
|
33
|
+
await fs_extra.default.ensureDir(dir);
|
|
34
|
+
const fp = getCostLogPath(baseDir, sessionId);
|
|
35
|
+
await fs_extra.default.appendFile(fp, JSON.stringify(entry) + "\n");
|
|
36
|
+
}
|
|
37
|
+
/** Read and aggregate all entries for a session. */
|
|
38
|
+
async function getSessionSummary(baseDir, sessionId) {
|
|
39
|
+
const fp = getCostLogPath(baseDir, sessionId);
|
|
40
|
+
if (!await fs_extra.default.pathExists(fp)) return {
|
|
41
|
+
input: 0,
|
|
42
|
+
output: 0,
|
|
43
|
+
cacheRead: 0,
|
|
44
|
+
costUsd: 0,
|
|
45
|
+
runs: 0
|
|
46
|
+
};
|
|
47
|
+
const lines = (await fs_extra.default.readFile(fp, "utf8")).trim().split("\n").filter(Boolean);
|
|
48
|
+
let input = 0, output = 0, cacheRead = 0, costUsd = 0;
|
|
49
|
+
for (const line of lines) try {
|
|
50
|
+
const e = JSON.parse(line);
|
|
51
|
+
input += e.usage.input || 0;
|
|
52
|
+
output += e.usage.output || 0;
|
|
53
|
+
cacheRead += e.usage.cacheRead || 0;
|
|
54
|
+
costUsd += e.costUsd || 0;
|
|
55
|
+
} catch {}
|
|
56
|
+
return {
|
|
57
|
+
input,
|
|
58
|
+
output,
|
|
59
|
+
cacheRead,
|
|
60
|
+
costUsd,
|
|
61
|
+
runs: lines.length
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Get global summary across all sessions. */
|
|
65
|
+
async function getGlobalSummary(baseDir) {
|
|
66
|
+
const dir = path.default.join(baseDir, "costs");
|
|
67
|
+
if (!await fs_extra.default.pathExists(dir)) return {
|
|
68
|
+
sessions: 0,
|
|
69
|
+
totalInput: 0,
|
|
70
|
+
totalOutput: 0,
|
|
71
|
+
totalCacheRead: 0,
|
|
72
|
+
totalCostUsd: 0,
|
|
73
|
+
totalRuns: 0
|
|
74
|
+
};
|
|
75
|
+
const files = (await fs_extra.default.readdir(dir)).filter((f) => f.startsWith("session-") && f.endsWith(".jsonl"));
|
|
76
|
+
let totalInput = 0, totalOutput = 0, totalCacheRead = 0, totalCostUsd = 0, totalRuns = 0;
|
|
77
|
+
for (const f of files) {
|
|
78
|
+
const fp = path.default.join(dir, f);
|
|
79
|
+
const content = await fs_extra.default.readFile(fp, "utf8").catch(() => "");
|
|
80
|
+
for (const line of content.trim().split("\n").filter(Boolean)) try {
|
|
81
|
+
const e = JSON.parse(line);
|
|
82
|
+
totalInput += e.usage.input || 0;
|
|
83
|
+
totalOutput += e.usage.output || 0;
|
|
84
|
+
totalCacheRead += e.usage.cacheRead || 0;
|
|
85
|
+
totalCostUsd += e.costUsd || 0;
|
|
86
|
+
totalRuns++;
|
|
87
|
+
} catch {}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
sessions: files.length,
|
|
91
|
+
totalInput,
|
|
92
|
+
totalOutput,
|
|
93
|
+
totalCacheRead,
|
|
94
|
+
totalCostUsd,
|
|
95
|
+
totalRuns
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
exports.estimateCost = estimateCost;
|
|
101
|
+
exports.getGlobalSummary = getGlobalSummary;
|
|
102
|
+
exports.getSessionSummary = getSessionSummary;
|
|
103
|
+
exports.recordUsage = recordUsage;
|