@max-null/dsh-achievements 0.1.1 → 0.1.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.md +14 -4
- package/client.js +19 -1
- package/dist/engine.d.ts +14 -0
- package/dist/engine.js +52 -4
- package/dist/index.js +5 -4
- package/dist/rules.d.ts +2 -1
- package/dist/rules.js +10 -0
- package/docs/shots/achievements-1.png +0 -0
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
# dsh-achievements 路 SSiD 全家桶成就插件
|
|
2
2
|
|
|
3
|
-
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(
|
|
3
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)全局进度成就系统:监听 session / tool 事件,把工具调用、记忆/模板库工具、上下文审计、token 用量、GenUI 渲染、消息收藏等行为折成成就进度,解锁时 toast + 设置页「成就」面板(全景:含 GenUI 类别的融合视图)。
|
|
4
4
|
|
|
5
5
|
## 特性
|
|
6
6
|
|
|
7
|
-
- **
|
|
7
|
+
- **23 个成就**、8 类别(启程 / 记忆 / 审计 / GenUI / 收藏 / 工具 / 行为 / 隐藏),稀有度覆盖 普通 → 传说;隐藏成就(如 夜猫子、自我指涉、亿万富翁)解锁前显示「??」
|
|
8
8
|
- **分层面板**:工具名、事件类型、token 计数、当天时刻等叶级标量——**绝不读取消息正文、文件内容或错误详情**
|
|
9
9
|
- **GenUI 融合**:client 读取 genui 插件自身的计数存储(`dsh.genui.achievements`),上报合并——全景页与 genui 成就页数据同源、互不重复计数
|
|
10
|
+
- **收藏融合**:client 读 dsh-chat-rail 的 `/chat-rail/api/favorites`(只读计数)上报合并——host 侧 `setMax` 取绝对值,重复上报不虚增
|
|
10
11
|
- **本地持久化**:`$DSH_HOME/achievements/state.json`,重启保留
|
|
11
|
-
- **HTTP 通道**:`/achievements/api`(list / recent / clear / genui-merge),信任围栏同 dsh-memory 的 `/memory/api`
|
|
12
|
+
- **HTTP 通道**:`/achievements/api`(list / recent / clear / genui-merge / chat-rail-merge),信任围栏同 dsh-memory 的 `/memory/api`
|
|
12
13
|
- **模型工具**:`list_achievements`(只读进度查询)
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## 截图
|
|
16
|
+
|
|
17
|
+
装完后在设置里多出「成就」一项,展示 SSiD 全家桶使用进度:
|
|
18
|
+
|
|
19
|
+
**入口:** 设置 → 成就
|
|
15
20
|
|
|
21
|
+
| 设置入口与面板 |
|
|
22
|
+
|---|
|
|
23
|
+
|  |
|
|
24
|
+
|
|
25
|
+
## 接入
|
|
16
26
|
```bash
|
|
17
27
|
dsh plugin add @max-null/dsh-achievements
|
|
18
28
|
```
|
package/client.js
CHANGED
|
@@ -71,12 +71,29 @@ var STRINGS = {
|
|
|
71
71
|
var PLUGIN_SHORT = {
|
|
72
72
|
"@max-null/dsh-memory": "\u8BB0\u5FC6",
|
|
73
73
|
"dsh-context-doctor": "\u5BA1\u8BA1",
|
|
74
|
-
"@changfenhuang/dsh-genui": "GenUI"
|
|
74
|
+
"@changfenhuang/dsh-genui": "GenUI",
|
|
75
|
+
"@max-null/dsh-chat-rail": "\u6536\u85CF"
|
|
75
76
|
};
|
|
76
77
|
function pluginLabel(plugin) {
|
|
77
78
|
if (plugin === null) return null;
|
|
78
79
|
return PLUGIN_SHORT[plugin] ?? plugin.split("/").pop() ?? plugin;
|
|
79
80
|
}
|
|
81
|
+
async function readChatRail() {
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch("/chat-rail/api/favorites", { headers: { Accept: "application/json" } });
|
|
84
|
+
if (!res.ok) return 0;
|
|
85
|
+
const data = await res.json();
|
|
86
|
+
const map = data.value;
|
|
87
|
+
if (map === null || typeof map !== "object" || Array.isArray(map)) return 0;
|
|
88
|
+
let total = 0;
|
|
89
|
+
for (const value of Object.values(map)) {
|
|
90
|
+
if (Array.isArray(value)) total += value.filter((id) => typeof id === "string" && id !== "").length;
|
|
91
|
+
}
|
|
92
|
+
return total;
|
|
93
|
+
} catch {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
80
97
|
var TROPHY_PATHS = [
|
|
81
98
|
"M6 9H4.5a2.5 2.5 0 0 1 0-5H6",
|
|
82
99
|
"M18 9h1.5a2.5 2.5 0 0 0 0-5H18",
|
|
@@ -155,6 +172,7 @@ function AchievementsView(_props) {
|
|
|
155
172
|
const reload = (0, import_react.useCallback)(async () => {
|
|
156
173
|
const genui = readGenUI();
|
|
157
174
|
await api("genui-merge", genui);
|
|
175
|
+
await api("chat-rail-merge", { total: await readChatRail() });
|
|
158
176
|
const data = await api("list");
|
|
159
177
|
if (data !== null) setSnapshot(data);
|
|
160
178
|
}, []);
|
package/dist/engine.d.ts
CHANGED
|
@@ -21,17 +21,31 @@ export declare class AchievementsEngine {
|
|
|
21
21
|
private queue;
|
|
22
22
|
/** 回合中间状态(agent key → 工具计数)。 */
|
|
23
23
|
private turnTools;
|
|
24
|
+
/** 已计入 `sessions` 的会话 id,保证一个会话只记一次。 */
|
|
25
|
+
private countedSessions;
|
|
24
26
|
get count(): AchieveState;
|
|
25
27
|
recordTool(name: string): void;
|
|
26
28
|
recordTurnStart(agentKey: string): void;
|
|
27
29
|
recordTurnTool(agentKey: string): void;
|
|
28
30
|
recordTurnEnd(agentKey: string): void;
|
|
29
31
|
recordSession(): void;
|
|
32
|
+
/**
|
|
33
|
+
* 按会话 id 计一次会话。
|
|
34
|
+
*
|
|
35
|
+
* 为什么不用 `agent/session-start`:该事件在 DSH 0.1.7 已不存在(0.1.5 在
|
|
36
|
+
* `core/agent/src/runtime-types.ts`),cordis 对未知事件名静默——监听它不会报错,
|
|
37
|
+
* 只是永不触发,于是 `sessions` 恒为 0,`first-session` 永久不解锁。现在改由
|
|
38
|
+
* 会话的首个 `turn/start` 记账(见 `attachListeners` 的 `session/event` 分支)。
|
|
39
|
+
* @param sessionId - 会话 id;缺id 时不做去重,直接记一次。
|
|
40
|
+
*/
|
|
41
|
+
recordSessionOnce(sessionId: string): void;
|
|
30
42
|
recordTurn(): void;
|
|
31
43
|
recordMidnight(): void;
|
|
32
44
|
recordTokens(amount: number): void;
|
|
33
45
|
/** GenUI 计数合并(client 写入;host 侧直接写计数键)。 */
|
|
34
46
|
mergeGenUI(unlockedCount: number, fences: number): void;
|
|
47
|
+
/** chat-rail 收藏绝对值(localStorage 上报;setMax 防重复虚增)。 */
|
|
48
|
+
mergeChatRail(total: number): void;
|
|
35
49
|
/** 检查解锁;返回新解锁(进队列 + 持久化;计数每次落盘)。 */
|
|
36
50
|
flush(): void;
|
|
37
51
|
snapshot(): EngineSnapshot;
|
package/dist/engine.js
CHANGED
|
@@ -71,6 +71,14 @@ class StateStore {
|
|
|
71
71
|
bump(key, by = 1) {
|
|
72
72
|
this.counters[key] = (this.counters[key] ?? 0) + by;
|
|
73
73
|
}
|
|
74
|
+
/** merge 上报语义:取 max(外部系统计数为绝对值,重复上报不虚增)。 */
|
|
75
|
+
setMax(key, value) {
|
|
76
|
+
if (value <= 0)
|
|
77
|
+
return;
|
|
78
|
+
if ((this.counters[key] ?? 0) >= value)
|
|
79
|
+
return;
|
|
80
|
+
this.counters[key] = value;
|
|
81
|
+
}
|
|
74
82
|
addDistinct(key, value) {
|
|
75
83
|
let set = this.distinct[key];
|
|
76
84
|
if (set === undefined) {
|
|
@@ -106,6 +114,7 @@ export const ASSOCIATED_PLUGINS = [
|
|
|
106
114
|
'@max-null/dsh-memory',
|
|
107
115
|
'dsh-context-doctor',
|
|
108
116
|
'@changfenhuang/dsh-genui',
|
|
117
|
+
'@max-null/dsh-chat-rail',
|
|
109
118
|
];
|
|
110
119
|
/** 探测已安装的关联插件:SSID_PROFILE_DIR(壳注入)与常见 profile 目录。 */
|
|
111
120
|
export function installedPlugins() {
|
|
@@ -129,6 +138,8 @@ export class AchievementsEngine {
|
|
|
129
138
|
queue = [];
|
|
130
139
|
/** 回合中间状态(agent key → 工具计数)。 */
|
|
131
140
|
turnTools = new Map();
|
|
141
|
+
/** 已计入 `sessions` 的会话 id,保证一个会话只记一次。 */
|
|
142
|
+
countedSessions = new Set();
|
|
132
143
|
get count() { return this.store.count; }
|
|
133
144
|
recordTool(name) {
|
|
134
145
|
this.store.bump('tools');
|
|
@@ -157,14 +168,37 @@ export class AchievementsEngine {
|
|
|
157
168
|
this.turnTools.delete(agentKey);
|
|
158
169
|
}
|
|
159
170
|
recordSession() { this.store.bump('sessions'); }
|
|
171
|
+
/**
|
|
172
|
+
* 按会话 id 计一次会话。
|
|
173
|
+
*
|
|
174
|
+
* 为什么不用 `agent/session-start`:该事件在 DSH 0.1.7 已不存在(0.1.5 在
|
|
175
|
+
* `core/agent/src/runtime-types.ts`),cordis 对未知事件名静默——监听它不会报错,
|
|
176
|
+
* 只是永不触发,于是 `sessions` 恒为 0,`first-session` 永久不解锁。现在改由
|
|
177
|
+
* 会话的首个 `turn/start` 记账(见 `attachListeners` 的 `session/event` 分支)。
|
|
178
|
+
* @param sessionId - 会话 id;缺id 时不做去重,直接记一次。
|
|
179
|
+
*/
|
|
180
|
+
recordSessionOnce(sessionId) {
|
|
181
|
+
if (sessionId === '') {
|
|
182
|
+
this.recordSession();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (this.countedSessions.has(sessionId))
|
|
186
|
+
return;
|
|
187
|
+
this.countedSessions.add(sessionId);
|
|
188
|
+
this.recordSession();
|
|
189
|
+
}
|
|
160
190
|
recordTurn() { this.store.bump('turns'); }
|
|
161
191
|
recordMidnight() { this.store.bump('midnightTurns'); }
|
|
162
192
|
recordTokens(amount) { if (amount > 0)
|
|
163
193
|
this.store.bump('tokens', amount); }
|
|
164
194
|
/** GenUI 计数合并(client 写入;host 侧直接写计数键)。 */
|
|
165
195
|
mergeGenUI(unlockedCount, fences) {
|
|
166
|
-
this.store.
|
|
167
|
-
this.store.
|
|
196
|
+
this.store.setMax('genuiUnlocked', Math.max(0, unlockedCount));
|
|
197
|
+
this.store.setMax('genuiFences', Math.max(0, fences));
|
|
198
|
+
}
|
|
199
|
+
/** chat-rail 收藏绝对值(localStorage 上报;setMax 防重复虚增)。 */
|
|
200
|
+
mergeChatRail(total) {
|
|
201
|
+
this.store.setMax('chatRailFavorites', Math.max(0, total));
|
|
168
202
|
}
|
|
169
203
|
/** 检查解锁;返回新解锁(进队列 + 持久化;计数每次落盘)。 */
|
|
170
204
|
flush() {
|
|
@@ -243,8 +277,15 @@ export function attachListeners(ctx, engine) {
|
|
|
243
277
|
return;
|
|
244
278
|
}
|
|
245
279
|
if (event?.type === 'session/start') {
|
|
246
|
-
|
|
247
|
-
|
|
280
|
+
// 该事件类型在 0.1.5 / 0.1.7 都不存在(Session 公共面没有它),此分支
|
|
281
|
+
// 从来没有触发过。保留会让人误以为会话计数有两条路径,删掉。
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (event?.type === 'turn/start') {
|
|
285
|
+
// 会话记账落在首个 turn/start:这是 0.1.7 上确定到达的会话级信号,
|
|
286
|
+
// 且不像 `agent/created` 那样是可否决的 serial 监听(抛错会挡住 agent 创建)。
|
|
287
|
+
const id = typeof session?.id === 'string' ? session.id : '';
|
|
288
|
+
engine.recordSessionOnce(id);
|
|
248
289
|
engine.flush();
|
|
249
290
|
}
|
|
250
291
|
}));
|
|
@@ -295,6 +336,13 @@ export function registerRoutes(ctx, engine) {
|
|
|
295
336
|
writeJson(res, 200, { ok: true, value: engine.fullSnapshot() });
|
|
296
337
|
return;
|
|
297
338
|
}
|
|
339
|
+
case 'chat-rail-merge': {
|
|
340
|
+
const body = (await readJsonBody(req));
|
|
341
|
+
engine.mergeChatRail(typeof body.total === 'number' ? body.total : 0);
|
|
342
|
+
engine.flush();
|
|
343
|
+
writeJson(res, 200, { ok: true, value: engine.fullSnapshot() });
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
298
346
|
default:
|
|
299
347
|
writeJson(res, 404, { ok: false, error: { code: 'not-found', message: `unknown method "${method}"` } });
|
|
300
348
|
}
|
package/dist/index.js
CHANGED
|
@@ -6,10 +6,11 @@ export function apply(ctx) {
|
|
|
6
6
|
attachListeners(ctx, engine);
|
|
7
7
|
registerRoutes(ctx, engine);
|
|
8
8
|
registerTool(ctx, engine);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
// 会话计数不再在此处订阅 `agent/session-start`:该事件在 DSH 0.1.7 已不存在
|
|
10
|
+
// (0.1.5 在 `core/agent/src/runtime-types.ts`),而 cordis 对未知事件名静默,
|
|
11
|
+
// 监听它不会报错、只是永不触发——`sessions` 因此恒为 0、`first-session` 永久
|
|
12
|
+
// 不解锁。现改由 `attachListeners` 里的 `session/event` 按每会话首个 `turn/start`
|
|
13
|
+
// 记账(不选 `agent/created`:那是可否决的 serial 监听,抛错会挡住 agent 创建)。
|
|
13
14
|
ctx.on('agent/turn-stopping', (() => {
|
|
14
15
|
engine.recordTurn();
|
|
15
16
|
engine.flush();
|
package/dist/rules.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 文件内容或错误详情。GenUI 类别由 client 侧读取 genui 插件的计数
|
|
7
7
|
* localStorage 合并(见 client/index.tsx)。
|
|
8
8
|
*/
|
|
9
|
-
export type Category = '启程' | '记忆' | '审计' | 'GenUI' | '工具' | '行为' | '隐藏';
|
|
9
|
+
export type Category = '启程' | '记忆' | '审计' | 'GenUI' | '收藏' | '工具' | '行为' | '隐藏';
|
|
10
10
|
export type Rarity = 'common' | 'rare' | 'epic' | 'legendary';
|
|
11
11
|
export interface AchievementDef {
|
|
12
12
|
id: string;
|
|
@@ -37,6 +37,7 @@ export declare const KEYS: {
|
|
|
37
37
|
readonly audits: "audits";
|
|
38
38
|
readonly genuiUnlocked: "genuiUnlocked";
|
|
39
39
|
readonly genuiFences: "genuiFences";
|
|
40
|
+
readonly chatRailFavorites: "chatRailFavorites";
|
|
40
41
|
readonly marathonTurns: "marathonTurns";
|
|
41
42
|
readonly midnightTurns: "midnightTurns";
|
|
42
43
|
readonly selfQueries: "selfQueries";
|
package/dist/rules.js
CHANGED
|
@@ -19,6 +19,7 @@ export const KEYS = {
|
|
|
19
19
|
audits: 'audits',
|
|
20
20
|
genuiUnlocked: 'genuiUnlocked',
|
|
21
21
|
genuiFences: 'genuiFences',
|
|
22
|
+
chatRailFavorites: 'chatRailFavorites',
|
|
22
23
|
marathonTurns: 'marathonTurns',
|
|
23
24
|
midnightTurns: 'midnightTurns',
|
|
24
25
|
selfQueries: 'selfQueries',
|
|
@@ -54,6 +55,15 @@ export const ACHIEVEMENTS = [
|
|
|
54
55
|
plugin: '@changfenhuang/dsh-genui' },
|
|
55
56
|
{ id: 'genui-25', name: '界面编织者', desc: 'GenUI 渲染过 25 个界面。', icon: '🧶', category: 'GenUI', rarity: 'epic', threshold: { counter: KEYS.genuiFences, target: 25 },
|
|
56
57
|
plugin: '@changfenhuang/dsh-genui' },
|
|
58
|
+
// ── 收藏(dsh-chat-rail 消息收藏;client 侧合并 localStorage 计数)──
|
|
59
|
+
{ id: 'fav-first', name: '初见倾心', desc: '收藏第一条消息。', icon: '⭐', category: '收藏', rarity: 'common', threshold: { counter: KEYS.chatRailFavorites, target: 1 },
|
|
60
|
+
plugin: '@max-null/dsh-chat-rail' },
|
|
61
|
+
{ id: 'fav-10', name: '收藏发烧友', desc: '累计收藏 10 条消息。', icon: '📚', category: '收藏', rarity: 'rare', threshold: { counter: KEYS.chatRailFavorites, target: 10 },
|
|
62
|
+
plugin: '@max-null/dsh-chat-rail' },
|
|
63
|
+
{ id: 'fav-30', name: '收藏大师', desc: '累计收藏 30 条消息。', icon: '👑', category: '收藏', rarity: 'epic', threshold: { counter: KEYS.chatRailFavorites, target: 30 },
|
|
64
|
+
plugin: '@max-null/dsh-chat-rail' },
|
|
65
|
+
{ id: 'fav-100', name: '千禧藏家', desc: '累计收藏 100 条消息。', icon: '💎', category: '收藏', rarity: 'legendary', threshold: { counter: KEYS.chatRailFavorites, target: 100 },
|
|
66
|
+
plugin: '@max-null/dsh-chat-rail' },
|
|
57
67
|
// ── 工具 ──
|
|
58
68
|
{ id: 'tool-10', name: '工具新手', desc: '累计调用 10 次工具。', icon: '🔧', category: '工具', rarity: 'common', threshold: { counter: KEYS.tools, target: 10 },
|
|
59
69
|
plugin: null },
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@max-null/dsh-achievements",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "SSiD family progress achievements — listens to session/tool events, folds activity into durable counters, unlocks trophies with toasts and an achievements page; the GenUI category merges the genui plugin's own counter data.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,8 +21,7 @@
|
|
|
21
21
|
"dist",
|
|
22
22
|
"client.js",
|
|
23
23
|
"cordis.patch.yml",
|
|
24
|
-
"README.md"
|
|
25
|
-
],
|
|
24
|
+
"README.md", "docs/shots"],
|
|
26
25
|
"dsh": {
|
|
27
26
|
"bundle": {
|
|
28
27
|
"patch": "./cordis.patch.yml"
|
|
@@ -43,16 +42,16 @@
|
|
|
43
42
|
},
|
|
44
43
|
"peerDependencies": {
|
|
45
44
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
46
|
-
"@deepseek-ai/dsh-session": "
|
|
47
|
-
"@deepseek-ai/dsh-tools": "
|
|
45
|
+
"@deepseek-ai/dsh-session": ">=0.1.1-rc.1 <0.3.0",
|
|
46
|
+
"@deepseek-ai/dsh-tools": ">=0.1.1-rc.1 <0.3.0"
|
|
48
47
|
},
|
|
49
48
|
"devDependencies": {
|
|
50
49
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
51
50
|
"@deepseek-ai/cordis-plugin-loader": "^1.0.2",
|
|
52
|
-
"@deepseek-ai/dsh-client-locale": "
|
|
53
|
-
"@deepseek-ai/dsh-client-runtime": "
|
|
54
|
-
"@deepseek-ai/dsh-session": "
|
|
55
|
-
"@deepseek-ai/dsh-tools": "
|
|
51
|
+
"@deepseek-ai/dsh-client-locale": ">=0.1.1-rc.1 <0.3.0",
|
|
52
|
+
"@deepseek-ai/dsh-client-runtime": ">=0.1.1-rc.1 <0.3.0",
|
|
53
|
+
"@deepseek-ai/dsh-session": ">=0.1.1-rc.1 <0.3.0",
|
|
54
|
+
"@deepseek-ai/dsh-tools": ">=0.1.1-rc.1 <0.3.0",
|
|
56
55
|
"@types/node": "^26.2.0",
|
|
57
56
|
"esbuild": "^0.25.0",
|
|
58
57
|
"react": "^18.3.1",
|