@max-null/dsh-achievements 0.1.2 → 0.1.4

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 CHANGED
@@ -1,18 +1,28 @@
1
1
  # dsh-achievements 路 SSiD 全家桶成就插件
2
2
 
3
- [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSS)全局进度成就系统:监听 session / tool 事件,把工具调用、记忆/模板库工具、上下文审计、token 用量、GenUI 渲染等行为折成成就进度,解锁时 toast + 设置页「成就」面板(全景:含 GenUI 类别的融合视图)。
3
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)全局进度成就系统:监听 session / tool 事件,把工具调用、记忆/模板库工具、上下文审计、token 用量、GenUI 渲染、消息收藏等行为折成成就进度,解锁时 toast + 设置页「成就」面板(全景:含 GenUI 类别的融合视图)。
4
4
 
5
5
  ## 特性
6
6
 
7
- - **18 个成就**、7 类别(启程 / 记忆 / 审计 / GenUI / 工具 / 行为 / 隐藏),稀有度覆盖 普通 → 传说;隐藏成就(如 夜猫子、自我指涉、亿万富翁)解锁前显示「??」
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
+ | ![设置 → 成就](docs/shots/achievements-1.png) |
24
+
25
+ ## 接入
16
26
  ```bash
17
27
  dsh plugin add @max-null/dsh-achievements
18
28
  ```
package/dist/engine.d.ts CHANGED
@@ -21,12 +21,24 @@ 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;
package/dist/engine.js CHANGED
@@ -138,6 +138,8 @@ export class AchievementsEngine {
138
138
  queue = [];
139
139
  /** 回合中间状态(agent key → 工具计数)。 */
140
140
  turnTools = new Map();
141
+ /** 已计入 `sessions` 的会话 id,保证一个会话只记一次。 */
142
+ countedSessions = new Set();
141
143
  get count() { return this.store.count; }
142
144
  recordTool(name) {
143
145
  this.store.bump('tools');
@@ -166,6 +168,25 @@ export class AchievementsEngine {
166
168
  this.turnTools.delete(agentKey);
167
169
  }
168
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
+ }
169
190
  recordTurn() { this.store.bump('turns'); }
170
191
  recordMidnight() { this.store.bump('midnightTurns'); }
171
192
  recordTokens(amount) { if (amount > 0)
@@ -256,8 +277,15 @@ export function attachListeners(ctx, engine) {
256
277
  return;
257
278
  }
258
279
  if (event?.type === 'session/start') {
259
- engine.recordSession();
260
- engine.recordTurn();
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);
261
289
  engine.flush();
262
290
  }
263
291
  }));
@@ -269,13 +297,13 @@ function agentKeyOf(exec) {
269
297
  /** HTTP 路由(/achievements/api,POST)——仿 dsh-memory 的 /memory/api 信任围栏。 */
270
298
  export function registerRoutes(ctx, engine) {
271
299
  const webServer = ctx.webServer;
272
- const webRuntime = ctx.webRuntime;
300
+ const webStartup = ctx.webStartup;
273
301
  ctx.effect(() => webServer.register({
274
302
  kind: 'prefix',
275
303
  path: '/achievements/api',
276
304
  handler: async (req, res) => {
277
305
  const request = req;
278
- if (!isTrusted(request, webRuntime.trustedHosts)) {
306
+ if (!isTrusted(request, webStartup.trustedHosts)) {
279
307
  writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'forbidden' } });
280
308
  return;
281
309
  }
package/dist/index.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import { AchievementsEngine, attachListeners, registerRoutes, registerTool } from "./engine.js";
2
2
  export const name = '@max-null/dsh-achievements';
3
- export const inject = ['tools', 'webServer', 'webRuntime'];
3
+ export const inject = ['tools', 'webServer', 'webStartup'];
4
4
  export function apply(ctx) {
5
5
  const engine = new AchievementsEngine();
6
6
  attachListeners(ctx, engine);
7
7
  registerRoutes(ctx, engine);
8
8
  registerTool(ctx, engine);
9
- ctx.on('agent/session-start', (() => {
10
- engine.recordSession();
11
- engine.flush();
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();
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@max-null/dsh-achievements",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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,7 +21,8 @@
21
21
  "dist",
22
22
  "client.js",
23
23
  "cordis.patch.yml",
24
- "README.md"
24
+ "README.md",
25
+ "docs/shots"
25
26
  ],
26
27
  "dsh": {
27
28
  "bundle": {
@@ -43,21 +44,25 @@
43
44
  },
44
45
  "peerDependencies": {
45
46
  "@deepseek-ai/cordis": "^4.0.1",
46
- "@deepseek-ai/dsh-session": "^0.1.1-rc.1",
47
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.1"
47
+ "@deepseek-ai/dsh-session": ">=0.1.1-rc.1 <0.3.0",
48
+ "@deepseek-ai/dsh-tools": ">=0.1.1-rc.1 <0.3.0"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@deepseek-ai/cordis": "^4.0.1",
51
52
  "@deepseek-ai/cordis-plugin-loader": "^1.0.2",
52
- "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.1",
53
- "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.1",
54
- "@deepseek-ai/dsh-session": "^0.1.1-rc.1",
55
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
53
+ "@deepseek-ai/dsh-client-locale": ">=0.1.1-rc.1 <0.3.0",
54
+ "@deepseek-ai/dsh-client-runtime": ">=0.1.1-rc.1 <0.3.0",
55
+ "@deepseek-ai/dsh-session": ">=0.1.1-rc.1 <0.3.0",
56
+ "@deepseek-ai/dsh-tools": ">=0.1.1-rc.1 <0.3.0",
56
57
  "@types/node": "^26.2.0",
57
58
  "esbuild": "^0.25.0",
58
59
  "react": "^18.3.1",
59
60
  "tsx": "^4.0.0",
60
61
  "typescript": "^5.5.0",
61
62
  "vitest": "^2.1.0"
63
+ },
64
+ "repository": {
65
+ "type": "git",
66
+ "url": "git+https://github.com/Max-Null/dsh-achievements.git"
62
67
  }
63
68
  }