@zhin.js/plugin-group-suite 1.1.7 → 1.1.8
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 +55 -24
- package/commands/forget/index.js +1 -0
- package/commands/forget/index.ts +1 -0
- package/commands/keyword-add/[keyword]/index.js +10 -7
- package/commands/keyword-add/[keyword]/index.ts +11 -8
- package/commands/keyword-list/index.js +8 -5
- package/commands/keyword-list/index.ts +9 -6
- package/commands/keyword-remove/[keyword]/index.js +10 -6
- package/commands/keyword-remove/[keyword]/index.ts +11 -7
- package/commands/stats-card/index.js +23 -0
- package/commands/stats-card/index.ts +24 -0
- package/commands/teach/index.js +1 -0
- package/commands/teach/index.ts +1 -0
- package/commands/teach-regex/index.js +1 -0
- package/commands/teach-regex/index.ts +1 -0
- package/handlers/welcome/index.js +25 -0
- package/handlers/welcome/index.ts +24 -0
- package/lib/checkin-lib.d.ts +4 -24
- package/lib/checkin-lib.d.ts.map +1 -1
- package/lib/checkin-lib.js +3 -4
- package/lib/checkin-lib.js.map +1 -1
- package/lib/config.d.ts +4 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +2 -0
- package/lib/config.js.map +1 -1
- package/lib/conversation-scope.d.ts +21 -0
- package/lib/conversation-scope.d.ts.map +1 -0
- package/lib/conversation-scope.js +33 -0
- package/lib/conversation-scope.js.map +1 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/keyword-store.d.ts +4 -0
- package/lib/keyword-store.d.ts.map +1 -1
- package/lib/keyword-store.js +15 -0
- package/lib/keyword-store.js.map +1 -1
- package/lib/runtime-state.d.ts +5 -1
- package/lib/runtime-state.d.ts.map +1 -1
- package/lib/runtime-state.js.map +1 -1
- package/lib/shared-runtime.d.ts +3 -8
- package/lib/shared-runtime.d.ts.map +1 -1
- package/lib/shared-runtime.js +4 -10
- package/lib/shared-runtime.js.map +1 -1
- package/lib/stats-lib.d.ts +5 -26
- package/lib/stats-lib.d.ts.map +1 -1
- package/lib/stats-lib.js +29 -15
- package/lib/stats-lib.js.map +1 -1
- package/lib/teach-lib.d.ts +2 -9
- package/lib/teach-lib.d.ts.map +1 -1
- package/lib/teach-lib.js +6 -8
- package/lib/teach-lib.js.map +1 -1
- package/middlewares/keyword-reply/index.js +3 -5
- package/middlewares/keyword-reply/index.ts +3 -5
- package/middlewares/stats-count/index.js +3 -7
- package/middlewares/stats-count/index.ts +3 -7
- package/package.json +27 -9
- package/plugin.js +9 -9
- package/schedules/flush-stats/index.js +11 -0
- package/schedules/flush-stats/index.ts +11 -0
- package/schema.json +51 -10
- package/skills/checkin/tools/checkin_query/index.js +5 -8
- package/skills/checkin/tools/checkin_query/index.ts +5 -9
- package/skills/checkin/tools/checkin_rank/index.js +4 -7
- package/skills/checkin/tools/checkin_rank/index.ts +4 -7
- package/skills/group-daily-analysis/tools/stats_query/index.js +3 -4
- package/skills/group-daily-analysis/tools/stats_query/index.ts +3 -4
- package/skills/group-daily-analysis/tools/stats_user/index.js +4 -5
- package/skills/group-daily-analysis/tools/stats_user/index.ts +4 -5
- package/src/checkin-lib.ts +7 -7
- package/src/config.ts +6 -0
- package/src/conversation-scope.ts +45 -0
- package/src/index.ts +1 -0
- package/src/keyword-store.ts +22 -0
- package/src/keywords.ts +72 -0
- package/src/runtime-state.ts +5 -1
- package/src/shared-runtime.ts +5 -14
- package/src/stats-lib.ts +36 -29
- package/src/teach-lib.ts +8 -17
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-group-suite",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "Zhin.js
|
|
3
|
+
"version": "1.1.8",
|
|
4
|
+
"description": "Zhin.js 群运营套件:签到、教学、统计、持久词库与群欢迎",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"schema.json",
|
|
19
19
|
"commands",
|
|
20
20
|
"middlewares",
|
|
21
|
+
"handlers",
|
|
22
|
+
"schedules",
|
|
21
23
|
"src",
|
|
22
24
|
"lib",
|
|
23
25
|
"skills",
|
|
@@ -41,22 +43,24 @@
|
|
|
41
43
|
"license": "MIT",
|
|
42
44
|
"dependencies": {
|
|
43
45
|
"@zhin.js/components": "1.1.2",
|
|
44
|
-
"@zhin.js/core": "1.1.
|
|
46
|
+
"@zhin.js/core": "1.1.43",
|
|
45
47
|
"@zhin.js/jsx": "1.1.2",
|
|
46
|
-
"@zhin.js/skill": "1.1.
|
|
47
|
-
"@zhin.js/tool": "1.1.
|
|
48
|
+
"@zhin.js/skill": "1.1.5",
|
|
49
|
+
"@zhin.js/tool": "1.1.5"
|
|
48
50
|
},
|
|
49
51
|
"devDependencies": {
|
|
50
52
|
"typescript": "^6.0.3",
|
|
51
53
|
"vitest": "^4.1.10",
|
|
52
54
|
"zod": "^4.4.3",
|
|
53
|
-
"zhin.js": "1.1.
|
|
55
|
+
"zhin.js": "1.1.8"
|
|
54
56
|
},
|
|
55
57
|
"peerDependencies": {
|
|
56
58
|
"zod": "^4.0.0",
|
|
57
|
-
"@zhin.js/command": "^1.1.
|
|
58
|
-
"@zhin.js/middleware": "^1.1.
|
|
59
|
-
"zhin.js": "^1.1.
|
|
59
|
+
"@zhin.js/command": "^1.1.6",
|
|
60
|
+
"@zhin.js/middleware": "^1.1.5",
|
|
61
|
+
"zhin.js": "^1.1.8",
|
|
62
|
+
"@zhin.js/handler": "^1.1.4",
|
|
63
|
+
"@zhin.js/schedule-feature": "^1.1.4"
|
|
60
64
|
},
|
|
61
65
|
"peerDependenciesMeta": {
|
|
62
66
|
"@zhin.js/command": {
|
|
@@ -67,6 +71,12 @@
|
|
|
67
71
|
},
|
|
68
72
|
"zod": {
|
|
69
73
|
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"@zhin.js/handler": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"@zhin.js/schedule-feature": {
|
|
79
|
+
"optional": true
|
|
70
80
|
}
|
|
71
81
|
},
|
|
72
82
|
"repository": {
|
|
@@ -103,6 +113,14 @@
|
|
|
103
113
|
{
|
|
104
114
|
"package": "@zhin.js/skill",
|
|
105
115
|
"api": "^1.0.0"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"package": "@zhin.js/handler",
|
|
119
|
+
"api": "^1.0.0"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"package": "@zhin.js/schedule-feature",
|
|
123
|
+
"api": "^1.0.0"
|
|
106
124
|
}
|
|
107
125
|
],
|
|
108
126
|
"plugins": []
|
package/plugin.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { definePlugin, databaseHostToken } from 'zhin.js';
|
|
3
|
+
import { resolveGroupSuiteConfig } from "./lib/config.js";
|
|
3
4
|
import { createInMemoryGroupSuiteDb } from "./lib/memory-store.js";
|
|
4
5
|
import { createGroupSuiteRuntime, groupSuiteRuntimeToken } from "./lib/runtime-state.js";
|
|
5
|
-
import { flushStatsBuffer } from "./lib/stats-lib.js";
|
|
6
6
|
import { defineGroupSuiteTables } from "./lib/tables.js";
|
|
7
7
|
/**
|
|
8
8
|
* Plugin Runtime:
|
|
9
9
|
* - checkin / teach / stats commands + keyword middleware
|
|
10
10
|
* - DB: prefer `databaseHostToken`; else in-memory fallback
|
|
11
|
-
* - Stats
|
|
11
|
+
* - Stats writes run inside admitted schedule/query operations
|
|
12
12
|
*/
|
|
13
13
|
export default definePlugin({
|
|
14
14
|
name: 'group-suite',
|
|
@@ -25,14 +25,14 @@ export default definePlugin({
|
|
|
25
25
|
}
|
|
26
26
|
return createInMemoryGroupSuiteDb();
|
|
27
27
|
})();
|
|
28
|
+
const config = resolveGroupSuiteConfig(context.config.get());
|
|
29
|
+
if (config.keywordReply && !config.ephemeralKeywords) {
|
|
30
|
+
// Fail before activation rather than claiming durable keywords in a memory fallback.
|
|
31
|
+
if (!context.resources.has(databaseHostToken))
|
|
32
|
+
throw new Error('Persistent keywords require a Database Host; demos may explicitly set ephemeralKeywords: true');
|
|
33
|
+
void context.resources.use(databaseHostToken).state;
|
|
34
|
+
}
|
|
28
35
|
const runtime = createGroupSuiteRuntime(db);
|
|
29
36
|
context.resources.provide(groupSuiteRuntimeToken, runtime);
|
|
30
|
-
const flushTimer = setInterval(() => {
|
|
31
|
-
void flushStatsBuffer(runtime);
|
|
32
|
-
}, 10_000);
|
|
33
|
-
context.lifecycle.add(async () => {
|
|
34
|
-
clearInterval(flushTimer);
|
|
35
|
-
await flushStatsBuffer(runtime);
|
|
36
|
-
});
|
|
37
37
|
},
|
|
38
38
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineSchedule } from '@zhin.js/schedule-feature';
|
|
3
|
+
import { groupSuiteRuntimeToken } from "../../lib/runtime-state.js";
|
|
4
|
+
import { flushStatsBuffer } from "../../lib/stats-lib.js";
|
|
5
|
+
export default defineSchedule({
|
|
6
|
+
cron: '*/10 * * * * *',
|
|
7
|
+
description: 'Flush owner-scoped message count batches',
|
|
8
|
+
execute(context) {
|
|
9
|
+
return flushStatsBuffer(context.use(groupSuiteRuntimeToken));
|
|
10
|
+
},
|
|
11
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineSchedule } from '@zhin.js/schedule-feature';
|
|
2
|
+
import { groupSuiteRuntimeToken } from '../../src/runtime-state.js';
|
|
3
|
+
import { flushStatsBuffer } from '../../src/stats-lib.js';
|
|
4
|
+
|
|
5
|
+
export default defineSchedule({
|
|
6
|
+
cron: '*/10 * * * * *',
|
|
7
|
+
description: 'Flush owner-scoped message count batches',
|
|
8
|
+
execute(context) {
|
|
9
|
+
return flushStatsBuffer(context.use(groupSuiteRuntimeToken));
|
|
10
|
+
},
|
|
11
|
+
});
|
package/schema.json
CHANGED
|
@@ -3,15 +3,56 @@
|
|
|
3
3
|
"type": "object",
|
|
4
4
|
"additionalProperties": false,
|
|
5
5
|
"properties": {
|
|
6
|
-
"keywordReply": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
6
|
+
"keywordReply": {
|
|
7
|
+
"type": "boolean",
|
|
8
|
+
"default": false
|
|
9
|
+
},
|
|
10
|
+
"basePointsMin": {
|
|
11
|
+
"type": "number",
|
|
12
|
+
"default": 10
|
|
13
|
+
},
|
|
14
|
+
"basePointsMax": {
|
|
15
|
+
"type": "number",
|
|
16
|
+
"default": 30
|
|
17
|
+
},
|
|
18
|
+
"streakBonus": {
|
|
19
|
+
"type": "number",
|
|
20
|
+
"default": 5
|
|
21
|
+
},
|
|
22
|
+
"streakCap": {
|
|
23
|
+
"type": "number",
|
|
24
|
+
"default": 50
|
|
25
|
+
},
|
|
26
|
+
"rankSize": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"default": 10
|
|
29
|
+
},
|
|
30
|
+
"teachMaxPerGroup": {
|
|
31
|
+
"type": "number",
|
|
32
|
+
"default": 200
|
|
33
|
+
},
|
|
34
|
+
"teachCooldownMs": {
|
|
35
|
+
"type": "number",
|
|
36
|
+
"default": 3000
|
|
37
|
+
},
|
|
38
|
+
"teachAllowRegex": {
|
|
39
|
+
"type": "boolean",
|
|
40
|
+
"default": true
|
|
41
|
+
},
|
|
42
|
+
"teachPageSize": {
|
|
43
|
+
"type": "number",
|
|
44
|
+
"default": 10
|
|
45
|
+
},
|
|
46
|
+
"welcomeMessage": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"maxLength": 2000,
|
|
49
|
+
"default": "",
|
|
50
|
+
"description": "Group welcome text. Empty disables; {name}/{id} refer to the joined member."
|
|
51
|
+
},
|
|
52
|
+
"ephemeralKeywords": {
|
|
53
|
+
"type": "boolean",
|
|
54
|
+
"default": false,
|
|
55
|
+
"description": "Explicit volatile keywords. With false, automatic replies require Database Host at startup and keyword management requires its persistent state service when invoked."
|
|
56
|
+
}
|
|
16
57
|
}
|
|
17
58
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { conversationKey, toolGroupConversation } from "../../../../lib/conversation-scope.js";
|
|
2
3
|
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
4
|
import { getCheckinModel } from "../../../../lib/db-store.js";
|
|
4
5
|
import { groupSuiteRuntimeToken } from "../../../../lib/runtime-state.js";
|
|
@@ -13,19 +14,18 @@ export default defineAgentTool({
|
|
|
13
14
|
},
|
|
14
15
|
},
|
|
15
16
|
requiresApproval: 'never',
|
|
17
|
+
scopes: ['group'],
|
|
16
18
|
async execute({ user_id: userId, group_id: requestedGroupId }, context) {
|
|
17
19
|
const runtime = context.use(groupSuiteRuntimeToken);
|
|
18
20
|
const model = getCheckinModel(runtime.db);
|
|
19
21
|
if (!model)
|
|
20
22
|
return '签到数据库尚未就绪';
|
|
21
|
-
const groupId =
|
|
23
|
+
const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
|
|
22
24
|
const query = {
|
|
23
25
|
...(userId ? { user_id: userId } : {}),
|
|
24
|
-
|
|
26
|
+
context_type: 'group', context_id: groupId,
|
|
25
27
|
};
|
|
26
|
-
const rows =
|
|
27
|
-
? await model.select().where(query)
|
|
28
|
-
: await model.select();
|
|
28
|
+
const rows = await model.select().where(query);
|
|
29
29
|
if (userId) {
|
|
30
30
|
if (rows.length === 0)
|
|
31
31
|
return `用户 ${userId} 没有签到记录`;
|
|
@@ -37,6 +37,3 @@ export default defineAgentTool({
|
|
|
37
37
|
return `签到系统统计\n总用户: ${rows.length}\n总积分: ${totalPoints}\n今日签到: ${todayCount}人`;
|
|
38
38
|
},
|
|
39
39
|
});
|
|
40
|
-
function currentGroupId(origin) {
|
|
41
|
-
return origin.kind === 'im' && origin.scope !== 'private' ? origin.sceneId : '';
|
|
42
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { conversationKey, toolGroupConversation } from '../../../../src/conversation-scope.js';
|
|
1
2
|
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
3
|
import { getCheckinModel } from '../../../../src/db-store.js';
|
|
3
4
|
import { groupSuiteRuntimeToken } from '../../../../src/runtime-state.js';
|
|
@@ -13,18 +14,17 @@ export default defineAgentTool<{ user_id?: string; group_id?: string }>({
|
|
|
13
14
|
},
|
|
14
15
|
},
|
|
15
16
|
requiresApproval: 'never',
|
|
17
|
+
scopes: ['group'],
|
|
16
18
|
async execute({ user_id: userId, group_id: requestedGroupId }, context) {
|
|
17
19
|
const runtime = context.use(groupSuiteRuntimeToken);
|
|
18
20
|
const model = getCheckinModel(runtime.db);
|
|
19
21
|
if (!model) return '签到数据库尚未就绪';
|
|
20
|
-
const groupId =
|
|
22
|
+
const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
|
|
21
23
|
const query = {
|
|
22
24
|
...(userId ? { user_id: userId } : {}),
|
|
23
|
-
|
|
25
|
+
context_type: 'group', context_id: groupId,
|
|
24
26
|
};
|
|
25
|
-
const rows =
|
|
26
|
-
? await model.select().where(query)
|
|
27
|
-
: await model.select();
|
|
27
|
+
const rows = await model.select().where(query);
|
|
28
28
|
if (userId) {
|
|
29
29
|
if (rows.length === 0) return `用户 ${userId} 没有签到记录`;
|
|
30
30
|
const user = rows[0]!;
|
|
@@ -35,7 +35,3 @@ export default defineAgentTool<{ user_id?: string; group_id?: string }>({
|
|
|
35
35
|
return `签到系统统计\n总用户: ${rows.length}\n总积分: ${totalPoints}\n今日签到: ${todayCount}人`;
|
|
36
36
|
},
|
|
37
37
|
});
|
|
38
|
-
|
|
39
|
-
function currentGroupId(origin: import('@zhin.js/tool').ToolInvocationOrigin): string {
|
|
40
|
-
return origin.kind === 'im' && origin.scope !== 'private' ? origin.sceneId : '';
|
|
41
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { conversationKey, toolGroupConversation } from "../../../../lib/conversation-scope.js";
|
|
2
3
|
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
4
|
import { getCheckinModel } from "../../../../lib/db-store.js";
|
|
4
5
|
import { groupSuiteRuntimeToken } from "../../../../lib/runtime-state.js";
|
|
@@ -12,17 +13,13 @@ export default defineAgentTool({
|
|
|
12
13
|
},
|
|
13
14
|
},
|
|
14
15
|
requiresApproval: 'never',
|
|
16
|
+
scopes: ['group'],
|
|
15
17
|
async execute({ limit, group_id: requestedGroupId }, context) {
|
|
16
18
|
const model = getCheckinModel(context.use(groupSuiteRuntimeToken).db);
|
|
17
19
|
if (!model)
|
|
18
20
|
return '签到数据库尚未就绪';
|
|
19
|
-
const groupId = requestedGroupId
|
|
20
|
-
|
|
21
|
-
? context.origin.sceneId
|
|
22
|
-
: '');
|
|
23
|
-
const rows = groupId
|
|
24
|
-
? await model.select().where({ context_type: 'group', context_id: groupId })
|
|
25
|
-
: await model.select();
|
|
21
|
+
const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
|
|
22
|
+
const rows = await model.select().where({ context_type: 'group', context_id: groupId });
|
|
26
23
|
const count = Math.min(Math.max(Number(limit) || 10, 1), 50);
|
|
27
24
|
const ranked = [...rows]
|
|
28
25
|
.sort((left, right) => Number(right.points ?? 0) - Number(left.points ?? 0))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { conversationKey, toolGroupConversation } from '../../../../src/conversation-scope.js';
|
|
1
2
|
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
3
|
import { getCheckinModel } from '../../../../src/db-store.js';
|
|
3
4
|
import { groupSuiteRuntimeToken } from '../../../../src/runtime-state.js';
|
|
@@ -12,16 +13,12 @@ export default defineAgentTool<{ limit?: number; group_id?: string }>({
|
|
|
12
13
|
},
|
|
13
14
|
},
|
|
14
15
|
requiresApproval: 'never',
|
|
16
|
+
scopes: ['group'],
|
|
15
17
|
async execute({ limit, group_id: requestedGroupId }, context) {
|
|
16
18
|
const model = getCheckinModel(context.use(groupSuiteRuntimeToken).db);
|
|
17
19
|
if (!model) return '签到数据库尚未就绪';
|
|
18
|
-
const groupId = requestedGroupId
|
|
19
|
-
|
|
20
|
-
? context.origin.sceneId
|
|
21
|
-
: '');
|
|
22
|
-
const rows = groupId
|
|
23
|
-
? await model.select().where({ context_type: 'group', context_id: groupId })
|
|
24
|
-
: await model.select();
|
|
20
|
+
const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
|
|
21
|
+
const rows = await model.select().where({ context_type: 'group', context_id: groupId });
|
|
25
22
|
const count = Math.min(Math.max(Number(limit) || 10, 1), 50);
|
|
26
23
|
const ranked = [...rows]
|
|
27
24
|
.sort((left, right) => Number(right.points ?? 0) - Number(left.points ?? 0))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { conversationKey, toolGroupConversation } from "../../../../lib/conversation-scope.js";
|
|
2
3
|
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
4
|
import { groupSuiteRuntimeToken } from "../../../../lib/runtime-state.js";
|
|
4
5
|
import { flushStatsBuffer, monthStartStr, queryStats, weekStartStr } from "../../../../lib/stats-lib.js";
|
|
@@ -13,13 +14,11 @@ export default defineAgentTool({
|
|
|
13
14
|
},
|
|
14
15
|
},
|
|
15
16
|
requiresApproval: 'never',
|
|
17
|
+
scopes: ['group'],
|
|
16
18
|
async execute({ group_id: requestedGroupId, period = 'today' }, context) {
|
|
17
19
|
const runtime = context.use(groupSuiteRuntimeToken);
|
|
18
20
|
await flushStatsBuffer(runtime);
|
|
19
|
-
const groupId = requestedGroupId
|
|
20
|
-
?? (context.origin.kind === 'im' && context.origin.scope !== 'private'
|
|
21
|
-
? context.origin.sceneId
|
|
22
|
-
: '');
|
|
21
|
+
const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
|
|
23
22
|
const fromDate = period === 'week'
|
|
24
23
|
? weekStartStr()
|
|
25
24
|
: period === 'month' ? monthStartStr() : todayStr();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { conversationKey, toolGroupConversation } from '../../../../src/conversation-scope.js';
|
|
1
2
|
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
3
|
import { groupSuiteRuntimeToken } from '../../../../src/runtime-state.js';
|
|
3
4
|
import { flushStatsBuffer, monthStartStr, queryStats, weekStartStr } from '../../../../src/stats-lib.js';
|
|
@@ -15,13 +16,11 @@ export default defineAgentTool<{ group_id?: string; period?: Period }>({
|
|
|
15
16
|
},
|
|
16
17
|
},
|
|
17
18
|
requiresApproval: 'never',
|
|
19
|
+
scopes: ['group'],
|
|
18
20
|
async execute({ group_id: requestedGroupId, period = 'today' }, context) {
|
|
19
21
|
const runtime = context.use(groupSuiteRuntimeToken);
|
|
20
22
|
await flushStatsBuffer(runtime);
|
|
21
|
-
const groupId = requestedGroupId
|
|
22
|
-
?? (context.origin.kind === 'im' && context.origin.scope !== 'private'
|
|
23
|
-
? context.origin.sceneId
|
|
24
|
-
: '');
|
|
23
|
+
const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
|
|
25
24
|
const fromDate = period === 'week'
|
|
26
25
|
? weekStartStr()
|
|
27
26
|
: period === 'month' ? monthStartStr() : todayStr();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { conversationKey, toolGroupConversation } from "../../../../lib/conversation-scope.js";
|
|
2
3
|
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
4
|
import { getStatsModel } from "../../../../lib/db-store.js";
|
|
4
5
|
import { groupSuiteRuntimeToken } from "../../../../lib/runtime-state.js";
|
|
@@ -15,19 +16,17 @@ export default defineAgentTool({
|
|
|
15
16
|
required: ['user_id'],
|
|
16
17
|
},
|
|
17
18
|
requiresApproval: 'never',
|
|
19
|
+
scopes: ['group'],
|
|
18
20
|
async execute({ user_id: userId, group_id: requestedGroupId }, context) {
|
|
19
21
|
const runtime = context.use(groupSuiteRuntimeToken);
|
|
20
22
|
await flushStatsBuffer(runtime);
|
|
21
23
|
const model = getStatsModel(runtime.db);
|
|
22
24
|
if (!model)
|
|
23
25
|
return '统计数据库尚未就绪';
|
|
24
|
-
const groupId = requestedGroupId
|
|
25
|
-
?? (context.origin.kind === 'im' && context.origin.scope !== 'private'
|
|
26
|
-
? context.origin.sceneId
|
|
27
|
-
: '');
|
|
26
|
+
const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
|
|
28
27
|
const rows = await model.select().where({
|
|
29
28
|
user_id: userId,
|
|
30
|
-
|
|
29
|
+
group_id: groupId,
|
|
31
30
|
});
|
|
32
31
|
if (rows.length === 0)
|
|
33
32
|
return `用户 ${userId} 暂无消息记录`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { conversationKey, toolGroupConversation } from '../../../../src/conversation-scope.js';
|
|
1
2
|
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
3
|
import { getStatsModel } from '../../../../src/db-store.js';
|
|
3
4
|
import { groupSuiteRuntimeToken } from '../../../../src/runtime-state.js';
|
|
@@ -15,18 +16,16 @@ export default defineAgentTool<{ user_id: string; group_id?: string }>({
|
|
|
15
16
|
required: ['user_id'],
|
|
16
17
|
},
|
|
17
18
|
requiresApproval: 'never',
|
|
19
|
+
scopes: ['group'],
|
|
18
20
|
async execute({ user_id: userId, group_id: requestedGroupId }, context) {
|
|
19
21
|
const runtime = context.use(groupSuiteRuntimeToken);
|
|
20
22
|
await flushStatsBuffer(runtime);
|
|
21
23
|
const model = getStatsModel(runtime.db);
|
|
22
24
|
if (!model) return '统计数据库尚未就绪';
|
|
23
|
-
const groupId = requestedGroupId
|
|
24
|
-
?? (context.origin.kind === 'im' && context.origin.scope !== 'private'
|
|
25
|
-
? context.origin.sceneId
|
|
26
|
-
: '');
|
|
25
|
+
const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
|
|
27
26
|
const rows = await model.select().where({
|
|
28
27
|
user_id: userId,
|
|
29
|
-
|
|
28
|
+
group_id: groupId,
|
|
30
29
|
});
|
|
31
30
|
if (rows.length === 0) return `用户 ${userId} 暂无消息记录`;
|
|
32
31
|
const today = todayStr();
|
package/src/checkin-lib.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ConversationInput } from './conversation-scope.js';
|
|
1
2
|
import type { GroupSuiteConfig } from './config.js';
|
|
2
3
|
import { getCheckinModel } from './db-store.js';
|
|
3
4
|
import {
|
|
@@ -64,7 +65,7 @@ function serializedCheckin<T>(
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
export async function doCheckin(
|
|
67
|
-
input:
|
|
68
|
+
input: ConversationInput,
|
|
68
69
|
cfg: GroupSuiteConfig,
|
|
69
70
|
runtime: GroupSuiteRuntime,
|
|
70
71
|
): Promise<string> {
|
|
@@ -113,7 +114,7 @@ export async function doCheckin(
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
export async function myPoints(
|
|
116
|
-
input:
|
|
117
|
+
input: ConversationInput,
|
|
117
118
|
runtime: GroupSuiteRuntime,
|
|
118
119
|
): Promise<string> {
|
|
119
120
|
const M = getCheckinModel(runtime.db);
|
|
@@ -139,7 +140,7 @@ export async function myPoints(
|
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
export async function pointsRank(
|
|
142
|
-
input:
|
|
143
|
+
input: ConversationInput,
|
|
143
144
|
cfg: GroupSuiteConfig,
|
|
144
145
|
runtime: GroupSuiteRuntime,
|
|
145
146
|
): Promise<string> {
|
|
@@ -147,9 +148,7 @@ export async function pointsRank(
|
|
|
147
148
|
if (!M) return '签到数据库尚未就绪';
|
|
148
149
|
const { type: ctxType, id: ctxId } = resolveContextKey(input);
|
|
149
150
|
const all = (await M.select().where(
|
|
150
|
-
ctxType
|
|
151
|
-
? { context_type: 'global' }
|
|
152
|
-
: { context_type: ctxType, context_id: ctxId },
|
|
151
|
+
{ context_type: ctxType, context_id: ctxId },
|
|
153
152
|
)) as Record<string, unknown>[];
|
|
154
153
|
if (all.length === 0) return '还没有人签到过哦~';
|
|
155
154
|
const sorted = all.sort((a, b) => Number(b.points || 0) - Number(a.points || 0));
|
|
@@ -159,7 +158,8 @@ export async function pointsRank(
|
|
|
159
158
|
const prefix = i < 3 ? medals[i] : `${i + 1}.`;
|
|
160
159
|
return `${prefix} ${u.user_name || u.user_id} — ${u.points}分 (${u.total_checkins}天)`;
|
|
161
160
|
});
|
|
162
|
-
const title = ctxType === 'group' ? '本群积分排行'
|
|
161
|
+
const title = ctxType === 'group' ? '本群积分排行'
|
|
162
|
+
: ctxType === 'channel' ? '本频道积分排行' : '当前私聊积分排行';
|
|
163
163
|
const { id: userId } = resolveSender(input);
|
|
164
164
|
const myRank = sorted.findIndex((u) => u.user_id === userId);
|
|
165
165
|
const footer = myRank >= 0 ? `\n你的排名: 第${myRank + 1}名` : '';
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export interface GroupSuiteConfig {
|
|
2
2
|
keywordReply: boolean;
|
|
3
|
+
/** Explicitly volatile keywords for demos without a persistent Database Host. */
|
|
4
|
+
ephemeralKeywords: boolean;
|
|
5
|
+
/** Empty disables group member welcomes. {name} and {id} are literal placeholders. */
|
|
6
|
+
welcomeMessage: string;
|
|
3
7
|
basePointsMin: number;
|
|
4
8
|
basePointsMax: number;
|
|
5
9
|
streakBonus: number;
|
|
@@ -13,6 +17,8 @@ export interface GroupSuiteConfig {
|
|
|
13
17
|
|
|
14
18
|
export const DEFAULT_GROUP_SUITE_CONFIG: GroupSuiteConfig = {
|
|
15
19
|
keywordReply: false,
|
|
20
|
+
ephemeralKeywords: false,
|
|
21
|
+
welcomeMessage: '',
|
|
16
22
|
basePointsMin: 10,
|
|
17
23
|
basePointsMax: 30,
|
|
18
24
|
streakBonus: 5,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createDeliveryTarget, type DeliveryTarget } from 'zhin.js';
|
|
2
|
+
import { adapterFeatureId, type AdapterIndex } from 'zhin.js/adapter';
|
|
3
|
+
import type { ToolInvocationOrigin } from '@zhin.js/tool';
|
|
4
|
+
|
|
5
|
+
export interface ConversationInput {
|
|
6
|
+
readonly conversation?: DeliveryTarget;
|
|
7
|
+
readonly sender?: Readonly<{ id: string; name?: string }>;
|
|
8
|
+
readonly content?: unknown;
|
|
9
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Versioned, unambiguous identity; historical bare group ids are deliberately not read. */
|
|
13
|
+
export function conversationKey(target: DeliveryTarget): string {
|
|
14
|
+
const canonical = createDeliveryTarget({ conversation: target });
|
|
15
|
+
return JSON.stringify(['conversation-v1', canonical.endpoint.adapter, canonical.endpoint.id,
|
|
16
|
+
canonical.kind, canonical.id, canonical.parent?.kind ?? null, canonical.parent?.id ?? null,
|
|
17
|
+
canonical.threadId ?? null]);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function requireConversation(input: ConversationInput): DeliveryTarget {
|
|
21
|
+
if (!input.conversation) throw new Error('Group Suite requires a canonical conversation');
|
|
22
|
+
return createDeliveryTarget({ conversation: input.conversation });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Tool origins contain an endpoint selector, not a canonical endpoint capability id. */
|
|
26
|
+
export function toolGroupConversation(context: {
|
|
27
|
+
readonly origin: ToolInvocationOrigin;
|
|
28
|
+
project<T>(feature: typeof adapterFeatureId): T;
|
|
29
|
+
}, groupId?: string): DeliveryTarget {
|
|
30
|
+
if (context.origin.kind !== 'im' || context.origin.scope !== 'group') {
|
|
31
|
+
throw new Error('Group Suite query tools require a group IM origin');
|
|
32
|
+
}
|
|
33
|
+
const origin = context.origin;
|
|
34
|
+
const index = context.project<AdapterIndex>(adapterFeatureId);
|
|
35
|
+
const matches = index.describe().filter(endpoint =>
|
|
36
|
+
(String(endpoint.id) === origin.endpoint || endpoint.name === origin.endpoint)
|
|
37
|
+
&& index.clientAdapter(endpoint.id) === origin.platform);
|
|
38
|
+
if (matches.length !== 1) throw new Error('Group Suite tool endpoint is missing or ambiguous');
|
|
39
|
+
return createDeliveryTarget({ conversation: {
|
|
40
|
+
endpoint: { id: String(matches[0]!.id), adapter: origin.platform },
|
|
41
|
+
kind: 'group', id: groupId ?? origin.sceneId,
|
|
42
|
+
...(origin.threadId !== undefined && (groupId === undefined || groupId === origin.sceneId)
|
|
43
|
+
? { threadId: origin.threadId } : {}),
|
|
44
|
+
} });
|
|
45
|
+
}
|
package/src/index.ts
CHANGED
package/src/keyword-store.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type { MessageBase } from '@zhin.js/core/runtime';
|
|
2
|
+
import { conversationKey } from './conversation-scope.js';
|
|
3
|
+
import type { GroupSuiteRuntime } from './runtime-state.js';
|
|
4
|
+
|
|
1
5
|
export function addKeyword(keyword: string, reply: string, store: Map<string, string>): void {
|
|
2
6
|
store.set(keyword, reply);
|
|
3
7
|
}
|
|
@@ -16,3 +20,21 @@ export function matchKeyword(text: string, store: Map<string, string>): string |
|
|
|
16
20
|
}
|
|
17
21
|
return null;
|
|
18
22
|
}
|
|
23
|
+
|
|
24
|
+
/** Owner is already isolated by the Resource; canonical endpoint and group form the inner scope. */
|
|
25
|
+
export function keywordStore(
|
|
26
|
+
input: Pick<MessageBase, 'conversation'> | undefined,
|
|
27
|
+
runtime: GroupSuiteRuntime,
|
|
28
|
+
): Map<string, string> {
|
|
29
|
+
const conversation = input?.conversation;
|
|
30
|
+
if (conversation?.kind !== 'group' || !conversation.id || !conversation.endpoint.id) {
|
|
31
|
+
throw new Error('Keywords require a group conversation with an endpoint');
|
|
32
|
+
}
|
|
33
|
+
const key = conversationKey(conversation);
|
|
34
|
+
let store = runtime.keywords.get(key);
|
|
35
|
+
if (!store) {
|
|
36
|
+
store = new Map();
|
|
37
|
+
runtime.keywords.set(key, store);
|
|
38
|
+
}
|
|
39
|
+
return store;
|
|
40
|
+
}
|