dsh-slack 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -0
- package/README.md +6 -0
- package/lib/inbox.d.ts +8 -1
- package/lib/inbox.js +28 -2
- package/lib/index.js +10 -2
- package/lib/slack-client.js +39 -8
- package/lib/tools.js +1 -3
- package/package.json +54 -57
package/README.en.md
CHANGED
package/README.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
# dsh-slack
|
|
4
4
|
|
|
5
|
+
> **让 agent 进你的 Slack**:发消息、看频道、收消息、回线程,双向通信。
|
|
6
|
+
|
|
7
|
+
   
|
|
8
|
+
|
|
9
|
+
|
|
5
10
|
DSH(DeepSeek Harness)社区插件:让 agent 与 Slack 双向通信。
|
|
6
11
|
|
|
7
12
|
> **v0.2 范围(双向)**:v0.1 只做「agent → Slack」单向通知;v0.2 新增 Socket Mode,
|
|
@@ -178,3 +183,4 @@ pnpm test # 先 build,再用 node:test 跑 test/*.test.mjs
|
|
|
178
183
|
## License
|
|
179
184
|
|
|
180
185
|
MIT
|
|
186
|
+
|
package/lib/inbox.d.ts
CHANGED
|
@@ -18,12 +18,19 @@ export declare function createInboxQueue(capacity?: number): InboxQueue;
|
|
|
18
18
|
*/
|
|
19
19
|
export declare class InboxQueue {
|
|
20
20
|
private items;
|
|
21
|
+
private readonly seen;
|
|
21
22
|
readonly capacity: number;
|
|
22
23
|
constructor(capacity?: number);
|
|
23
|
-
|
|
24
|
+
private key;
|
|
25
|
+
/** 追加一条消息;Slack 至少投递一次,重复事件直接忽略,超过容量时丢弃最旧。 */
|
|
24
26
|
push(message: InboxMessage): void;
|
|
25
27
|
/** 返回最近 limit 条消息(新的在前)。 */
|
|
26
28
|
list(limit: number): InboxMessage[];
|
|
29
|
+
/**
|
|
30
|
+
* 原子地取出最近 limit 条并清空整个队列。
|
|
31
|
+
* 避免“先 list 再 clear”之间新到的消息被误清掉。
|
|
32
|
+
*/
|
|
33
|
+
drain(limit: number): InboxMessage[];
|
|
27
34
|
/** 清空队列。 */
|
|
28
35
|
clear(): void;
|
|
29
36
|
/** 当前队列长度。 */
|
package/lib/inbox.js
CHANGED
|
@@ -13,15 +13,25 @@ export function createInboxQueue(capacity = INBOX_CAPACITY) {
|
|
|
13
13
|
*/
|
|
14
14
|
export class InboxQueue {
|
|
15
15
|
items = [];
|
|
16
|
+
seen = new Set();
|
|
16
17
|
capacity;
|
|
17
18
|
constructor(capacity = INBOX_CAPACITY) {
|
|
18
19
|
this.capacity = capacity;
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
+
key(message) {
|
|
22
|
+
return message.channel + '\u0000' + message.ts + '\u0000' + message.user + '\u0000' + message.text;
|
|
23
|
+
}
|
|
24
|
+
/** 追加一条消息;Slack 至少投递一次,重复事件直接忽略,超过容量时丢弃最旧。 */
|
|
21
25
|
push(message) {
|
|
26
|
+
const key = this.key(message);
|
|
27
|
+
if (this.seen.has(key))
|
|
28
|
+
return;
|
|
29
|
+
this.seen.add(key);
|
|
22
30
|
this.items.push(message);
|
|
23
31
|
if (this.items.length > this.capacity) {
|
|
24
|
-
this.items.splice(0, this.items.length - this.capacity);
|
|
32
|
+
const removed = this.items.splice(0, this.items.length - this.capacity);
|
|
33
|
+
for (const item of removed)
|
|
34
|
+
this.seen.delete(this.key(item));
|
|
25
35
|
}
|
|
26
36
|
}
|
|
27
37
|
/** 返回最近 limit 条消息(新的在前)。 */
|
|
@@ -31,9 +41,25 @@ export class InboxQueue {
|
|
|
31
41
|
return [];
|
|
32
42
|
return this.items.slice(-n).reverse();
|
|
33
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* 原子地取出最近 limit 条并清空整个队列。
|
|
46
|
+
* 避免“先 list 再 clear”之间新到的消息被误清掉。
|
|
47
|
+
*/
|
|
48
|
+
drain(limit) {
|
|
49
|
+
const n = Math.max(0, Math.trunc(limit));
|
|
50
|
+
if (n === 0) {
|
|
51
|
+
this.clear();
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const out = this.items.splice(-n).reverse();
|
|
55
|
+
this.items = [];
|
|
56
|
+
this.seen.clear();
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
34
59
|
/** 清空队列。 */
|
|
35
60
|
clear() {
|
|
36
61
|
this.items = [];
|
|
62
|
+
this.seen.clear();
|
|
37
63
|
}
|
|
38
64
|
/** 当前队列长度。 */
|
|
39
65
|
get size() {
|
package/lib/index.js
CHANGED
|
@@ -56,11 +56,19 @@ export function apply(ctx, rawConfig) {
|
|
|
56
56
|
}
|
|
57
57
|
const inboxQueue = createInboxQueue();
|
|
58
58
|
const configProvider = () => config;
|
|
59
|
+
let client;
|
|
60
|
+
let clientFingerprint = '';
|
|
59
61
|
const clientProvider = () => {
|
|
60
|
-
// 每次调用时重新解析配置(config.token
|
|
62
|
+
// 每次调用时重新解析配置(config.token 优先,环境变量回退)。
|
|
63
|
+
// 同一 token/API 地址复用 WebClient;配置变更时自动重建。
|
|
61
64
|
const current = parseConfig(rawConfig);
|
|
62
65
|
const token = requireToken(current);
|
|
63
|
-
|
|
66
|
+
const fingerprint = token + '\u0000' + (current?.slackApiUrl ?? '');
|
|
67
|
+
if (client === undefined || fingerprint !== clientFingerprint) {
|
|
68
|
+
client = createWebSlackClient(token, current?.slackApiUrl);
|
|
69
|
+
clientFingerprint = fingerprint;
|
|
70
|
+
}
|
|
71
|
+
return client;
|
|
64
72
|
};
|
|
65
73
|
const inboxProvider = () => inboxQueue;
|
|
66
74
|
const deps = { clientProvider, configProvider, inboxProvider };
|
package/lib/slack-client.js
CHANGED
|
@@ -17,14 +17,36 @@ export function createWebSlackClient(token, slackApiUrl) {
|
|
|
17
17
|
return { ts };
|
|
18
18
|
},
|
|
19
19
|
async listChannels() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
let cursor;
|
|
21
|
+
let pages = 0;
|
|
22
|
+
const allChannels = [];
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
// conversations.list 默认分页(每页约 100-200 条);企业工作区频道很多时
|
|
25
|
+
// 必须沿 next_cursor 翻完,否则模型会漏掉后面的频道。
|
|
26
|
+
do {
|
|
27
|
+
pages += 1;
|
|
28
|
+
if (pages > 20)
|
|
29
|
+
break;
|
|
30
|
+
const result = await client.conversations.list({
|
|
31
|
+
types: 'public_channel,private_channel',
|
|
32
|
+
...(cursor !== undefined ? { cursor } : {}),
|
|
33
|
+
});
|
|
34
|
+
for (const ch of (result.channels ?? [])) {
|
|
35
|
+
if (typeof ch.id === 'string' && typeof ch.name === 'string' && !seen.has(ch.id)) {
|
|
36
|
+
seen.add(ch.id);
|
|
37
|
+
allChannels.push({ id: ch.id, name: ch.name });
|
|
38
|
+
}
|
|
25
39
|
}
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
const next = result.response_metadata?.next_cursor;
|
|
41
|
+
cursor = typeof next === 'string' && next !== cursor ? next : '';
|
|
42
|
+
} while (cursor !== '');
|
|
43
|
+
return allChannels;
|
|
44
|
+
/*
|
|
45
|
+
// if (typeof ch.id === 'string' && typeof ch.name === 'string') {
|
|
46
|
+
// allChannels.push({ id: ch.id, name: ch.name })
|
|
47
|
+
*/
|
|
48
|
+
// }
|
|
49
|
+
// removed duplicate return
|
|
28
50
|
},
|
|
29
51
|
};
|
|
30
52
|
}
|
|
@@ -43,9 +65,18 @@ export function mapSlackError(error) {
|
|
|
43
65
|
if (code === 'not_in_channel') {
|
|
44
66
|
return '机器人不在该频道中(not_in_channel):请先把机器人 App 添加(invite)到该频道。';
|
|
45
67
|
}
|
|
46
|
-
if (code === 'token_revoked' || code === 'account_inactive' || code === 'missing_scope') {
|
|
68
|
+
if (code === 'token_revoked' || code === 'account_inactive' || code === 'missing_scope' || code === 'not_authed') {
|
|
47
69
|
return `Slack 令牌或权限不足(${code}):请确认已勾选 chat:write 与 channels:read,并重新安装 App 拿新令牌。`;
|
|
48
70
|
}
|
|
71
|
+
if (code === 'is_archived') {
|
|
72
|
+
return '频道已归档(is_archived):机器人不能在已归档频道发消息,请改用其他频道。';
|
|
73
|
+
}
|
|
74
|
+
if (code === 'msg_too_long') {
|
|
75
|
+
return '消息过长(msg_too_long):Slack 单条消息不能超过 40,000 字符,请精简后重试。';
|
|
76
|
+
}
|
|
77
|
+
if (code === 'ratelimited' || code === 'rate_limited') {
|
|
78
|
+
return 'Slack 触发限流(ratelimited):请求太频繁,请稍等几秒后重试。';
|
|
79
|
+
}
|
|
49
80
|
const message = error?.message;
|
|
50
81
|
return message ? `Slack API 调用失败:${message}` : 'Slack API 调用失败:未知错误。';
|
|
51
82
|
}
|
package/lib/tools.js
CHANGED
|
@@ -160,9 +160,7 @@ export function buildInboxTool(deps) {
|
|
|
160
160
|
const limit = clampLimit(raw.limit);
|
|
161
161
|
const markRead = raw.markRead === true;
|
|
162
162
|
const queue = deps.inboxProvider();
|
|
163
|
-
const messages = queue.list(limit);
|
|
164
|
-
if (markRead)
|
|
165
|
-
queue.clear();
|
|
163
|
+
const messages = markRead ? queue.drain(limit) : queue.list(limit);
|
|
166
164
|
return { messages: messages.map((m) => ({ ts: m.ts, channel: m.channel, user: m.user, text: m.text })) };
|
|
167
165
|
},
|
|
168
166
|
};
|
package/package.json
CHANGED
|
@@ -1,57 +1,54 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-slack",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "DSH 社区插件:Slack 通知/桥接(v0.2 支持 Socket Mode 双向:收件箱 + 线程回复)",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./lib/index.js",
|
|
7
|
-
"types": "./lib/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./lib/index.d.ts",
|
|
11
|
-
"default": "./lib/index.js"
|
|
12
|
-
},
|
|
13
|
-
"./package.json": "./package.json",
|
|
14
|
-
"./cordis.patch.yml": "./cordis.patch.yml"
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"lib",
|
|
18
|
-
"cordis.patch.yml",
|
|
19
|
-
"README.md"
|
|
20
|
-
],
|
|
21
|
-
"license": "MIT",
|
|
22
|
-
"dsh": {
|
|
23
|
-
"bundle": {
|
|
24
|
-
"patch": "./cordis.patch.yml"
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
},
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
],
|
|
56
|
-
"packageManager": "pnpm@11.7.0"
|
|
57
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-slack",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "DSH 社区插件:Slack 通知/桥接(v0.2 支持 Socket Mode 双向:收件箱 + 线程回复)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./lib/index.js",
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json",
|
|
14
|
+
"./cordis.patch.yml": "./cordis.patch.yml"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"lib",
|
|
18
|
+
"cordis.patch.yml",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dsh": {
|
|
23
|
+
"bundle": {
|
|
24
|
+
"patch": "./cordis.patch.yml"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@slack/socket-mode": "^3.0.0",
|
|
29
|
+
"@slack/web-api": "^8.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^24.0.0",
|
|
33
|
+
"typescript": "^5.7.3"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"url": "https://github.com/STARDUSTLC666/dsh-slack",
|
|
37
|
+
"type": "git"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/STARDUSTLC666/dsh-slack/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/STARDUSTLC666/dsh-slack#readme",
|
|
43
|
+
"author": "stardustlc",
|
|
44
|
+
"keywords": [
|
|
45
|
+
"dsh-plugin",
|
|
46
|
+
"deepseek-harness",
|
|
47
|
+
"dsh"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc -p tsconfig.json",
|
|
51
|
+
"test": "pnpm run build && node --test \"test/*.test.mjs\"",
|
|
52
|
+
"test:protocol": "node test/protocol-acceptance.mjs"
|
|
53
|
+
}
|
|
54
|
+
}
|