@max-null/dsh-achievements 0.1.0 → 0.1.1
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/client.js +156 -39
- package/dist/engine.d.ts +4 -0
- package/dist/engine.js +25 -2
- package/dist/rules.d.ts +5 -3
- package/dist/rules.js +44 -23
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -52,16 +52,84 @@ var STRINGS = {
|
|
|
52
52
|
desc: "SSiD \u5168\u5BB6\u6876\u8FDB\u5EA6\uFF1A\u5DE5\u5177/\u8BB0\u5FC6/\u5BA1\u8BA1/GenUI \u4F7F\u7528\u6210\u5C31\uFF0C\u89E3\u9501\u53EF\u9886 toast\u3002",
|
|
53
53
|
refresh: "\u5237\u65B0",
|
|
54
54
|
locked: "\u672A\u89E3\u9501",
|
|
55
|
-
unlocked: "\u5DF2\u89E3\u9501"
|
|
55
|
+
unlocked: "\u5DF2\u89E3\u9501",
|
|
56
|
+
pluginMissing: "\u63D2\u4EF6\u672A\u88C5",
|
|
57
|
+
growing: "\u66F4\u591A\u7EC4\u4EF6\u7684\u6210\u5C31\u6301\u7EED\u589E\u52A0\u4E2D \u2014\u2014 Guardian / Habit / \u6DA6\u8272 / \u5BA1\u67E5\u7B49\u63D2\u4EF6\u5C06\u9646\u7EED\u52A0\u5165\u3002",
|
|
58
|
+
builtin: "\u5185\u7F6E"
|
|
56
59
|
},
|
|
57
60
|
en: {
|
|
58
61
|
title: "Achievements",
|
|
59
62
|
desc: "SSiD family progress: tool/memory/audit/GenUI usage trophies with unlock toasts.",
|
|
60
63
|
refresh: "Refresh",
|
|
61
64
|
locked: "Locked",
|
|
62
|
-
unlocked: "Unlocked"
|
|
65
|
+
unlocked: "Unlocked",
|
|
66
|
+
pluginMissing: "plugin not installed",
|
|
67
|
+
growing: "More component achievements are on the way \u2014 Guardian / Habit / Polish / Review and more will join soon.",
|
|
68
|
+
builtin: "built-in"
|
|
63
69
|
}
|
|
64
70
|
};
|
|
71
|
+
var PLUGIN_SHORT = {
|
|
72
|
+
"@max-null/dsh-memory": "\u8BB0\u5FC6",
|
|
73
|
+
"dsh-context-doctor": "\u5BA1\u8BA1",
|
|
74
|
+
"@changfenhuang/dsh-genui": "GenUI"
|
|
75
|
+
};
|
|
76
|
+
function pluginLabel(plugin) {
|
|
77
|
+
if (plugin === null) return null;
|
|
78
|
+
return PLUGIN_SHORT[plugin] ?? plugin.split("/").pop() ?? plugin;
|
|
79
|
+
}
|
|
80
|
+
var TROPHY_PATHS = [
|
|
81
|
+
"M6 9H4.5a2.5 2.5 0 0 1 0-5H6",
|
|
82
|
+
"M18 9h1.5a2.5 2.5 0 0 0 0-5H18",
|
|
83
|
+
"M4 22h16",
|
|
84
|
+
"M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22",
|
|
85
|
+
"M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22",
|
|
86
|
+
"M18 2H6v7a6 6 0 0 0 12 0V2Z"
|
|
87
|
+
];
|
|
88
|
+
var SETTINGS_NAV_MARKER = "data-dsh-achievements-settings-nav";
|
|
89
|
+
var TROPHY_MASK_SVG = encodeURIComponent(
|
|
90
|
+
`<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'>${TROPHY_PATHS.map((d) => `<path d='${d}'/>`).join("")}</svg>`
|
|
91
|
+
).replace(/'/g, "%27").replace(/\//g, "%2F").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/#/g, "%23");
|
|
92
|
+
var NAV_ICON_CSS = `
|
|
93
|
+
[data-dsh-achievements-settings-nav] > svg:first-child { display: none; }
|
|
94
|
+
[data-dsh-achievements-settings-nav]::before {
|
|
95
|
+
content: '';
|
|
96
|
+
flex: none;
|
|
97
|
+
width: 16px;
|
|
98
|
+
height: 16px;
|
|
99
|
+
background: currentColor;
|
|
100
|
+
-webkit-mask: url("data:image/svg+xml,${TROPHY_MASK_SVG}") center / contain no-repeat;
|
|
101
|
+
mask: url("data:image/svg+xml,${TROPHY_MASK_SVG}") center / contain no-repeat;
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
var navCssInjected = false;
|
|
105
|
+
function injectNavCss() {
|
|
106
|
+
if (navCssInjected || typeof document === "undefined") return;
|
|
107
|
+
navCssInjected = true;
|
|
108
|
+
const style = document.createElement("style");
|
|
109
|
+
style.setAttribute("data-plugin", "@max-null/dsh-achievements");
|
|
110
|
+
style.textContent = NAV_ICON_CSS;
|
|
111
|
+
document.head.append(style);
|
|
112
|
+
}
|
|
113
|
+
function registerSettingsNavIcon(label) {
|
|
114
|
+
let disposed = false;
|
|
115
|
+
const sync = () => {
|
|
116
|
+
if (disposed) return;
|
|
117
|
+
const currentLabel = label().trim();
|
|
118
|
+
const buttons = document.querySelectorAll('[role="dialog"] nav button');
|
|
119
|
+
for (const button of buttons) {
|
|
120
|
+
const matches = currentLabel.length > 0 && button.textContent?.trim() === currentLabel;
|
|
121
|
+
if (matches) button.setAttribute(SETTINGS_NAV_MARKER, "");
|
|
122
|
+
else button.removeAttribute(SETTINGS_NAV_MARKER);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
sync();
|
|
126
|
+
const observer = new MutationObserver(sync);
|
|
127
|
+
observer.observe(document.body, { subtree: true, childList: true, characterData: true });
|
|
128
|
+
return () => {
|
|
129
|
+
disposed = true;
|
|
130
|
+
observer.disconnect();
|
|
131
|
+
};
|
|
132
|
+
}
|
|
65
133
|
var GENUI_LS_KEY = "dsh.genui.achievements";
|
|
66
134
|
function readGenUI() {
|
|
67
135
|
try {
|
|
@@ -103,8 +171,18 @@ function AchievementsView(_props) {
|
|
|
103
171
|
(0, import_react.createElement)(
|
|
104
172
|
"div",
|
|
105
173
|
{ className: "achHead" },
|
|
106
|
-
(0, import_react.createElement)(
|
|
107
|
-
|
|
174
|
+
(0, import_react.createElement)(
|
|
175
|
+
"div",
|
|
176
|
+
{ className: "achHeadLeft" },
|
|
177
|
+
(0, import_react.createElement)("span", { className: "achTitle" }, t.title),
|
|
178
|
+
(0, import_react.createElement)("span", { className: "achDesc" }, t.desc)
|
|
179
|
+
),
|
|
180
|
+
(0, import_react.createElement)(
|
|
181
|
+
"div",
|
|
182
|
+
{ className: "achScore", "data-locked": snapshot === null ? void 0 : String(snapshot.unlocked) },
|
|
183
|
+
(0, import_react.createElement)("span", { className: "achScoreNum" }, snapshot !== null ? `${snapshot.unlocked}` : "\u2026"),
|
|
184
|
+
(0, import_react.createElement)("span", { className: "achScoreTotal" }, snapshot !== null ? `/ ${snapshot.total}` : "")
|
|
185
|
+
),
|
|
108
186
|
(0, import_react.createElement)("button", {
|
|
109
187
|
type: "button",
|
|
110
188
|
className: "achBtn",
|
|
@@ -115,7 +193,6 @@ function AchievementsView(_props) {
|
|
|
115
193
|
}
|
|
116
194
|
}, t.refresh)
|
|
117
195
|
),
|
|
118
|
-
(0, import_react.createElement)("div", { className: "achDesc" }, t.desc),
|
|
119
196
|
(0, import_react.createElement)(
|
|
120
197
|
"div",
|
|
121
198
|
{ className: "achCats" },
|
|
@@ -126,13 +203,23 @@ function AchievementsView(_props) {
|
|
|
126
203
|
onClick: () => setCategory(c)
|
|
127
204
|
}, c))
|
|
128
205
|
),
|
|
206
|
+
(0, import_react.createElement)(
|
|
207
|
+
"div",
|
|
208
|
+
{ className: "achGrowing" },
|
|
209
|
+
(0, import_react.createElement)("span", { className: "achGrowingIcon", "aria-hidden": "" }, "\u{1F9E9}"),
|
|
210
|
+
(0, import_react.createElement)("span", null, t.growing)
|
|
211
|
+
),
|
|
129
212
|
(0, import_react.createElement)(
|
|
130
213
|
"div",
|
|
131
214
|
{ className: "achList" },
|
|
132
215
|
items.map((a) => (0, import_react.createElement)(
|
|
133
216
|
"div",
|
|
134
|
-
{ key: a.id, className: `achRow${a.
|
|
135
|
-
(0, import_react.createElement)(
|
|
217
|
+
{ key: a.id, className: `achRow${a.unlocked ? " achRowOn" : ""}${a.pluginInstalled ? "" : " achRowMuted"}` },
|
|
218
|
+
(0, import_react.createElement)(
|
|
219
|
+
"span",
|
|
220
|
+
{ className: `achIconWrap achIconWrap-${a.rarity}`, "aria-hidden": "" },
|
|
221
|
+
(0, import_react.createElement)("span", { className: "achIcon" }, a.icon)
|
|
222
|
+
),
|
|
136
223
|
(0, import_react.createElement)(
|
|
137
224
|
"div",
|
|
138
225
|
{ className: "achBody" },
|
|
@@ -144,7 +231,8 @@ function AchievementsView(_props) {
|
|
|
144
231
|
"span",
|
|
145
232
|
{ className: `achRarity achRarity-${a.rarity}` },
|
|
146
233
|
a.rarity === "legendary" ? "\u4F20\u8BF4" : a.rarity === "epic" ? "\u53F2\u8BD7" : a.rarity === "rare" ? "\u7A00\u6709" : "\u666E\u901A"
|
|
147
|
-
)
|
|
234
|
+
),
|
|
235
|
+
a.plugin !== null ? (0, import_react.createElement)("span", { className: "achPlugin" }, pluginLabel(a.plugin)) : null
|
|
148
236
|
),
|
|
149
237
|
(0, import_react.createElement)("div", { className: "achDesc2" }, a.desc),
|
|
150
238
|
(0, import_react.createElement)(
|
|
@@ -161,7 +249,9 @@ function AchievementsView(_props) {
|
|
|
161
249
|
(0, import_react.createElement)("span", { className: "achProgNum" }, `${a.progress.current} / ${a.progress.target}`)
|
|
162
250
|
)
|
|
163
251
|
),
|
|
164
|
-
(0, import_react.createElement)("span", {
|
|
252
|
+
(0, import_react.createElement)("span", {
|
|
253
|
+
className: `achState${a.pluginInstalled ? a.unlocked ? " achStateOn" : "" : " achStateOff"}`
|
|
254
|
+
}, a.pluginInstalled ? a.unlocked ? t.unlocked : t.locked : t.pluginMissing)
|
|
165
255
|
))
|
|
166
256
|
),
|
|
167
257
|
note !== "" ? (0, import_react.createElement)("div", { className: "achNote" }, note) : null
|
|
@@ -206,46 +296,73 @@ function apply(ctx) {
|
|
|
206
296
|
label: () => STRINGS[lang()].title
|
|
207
297
|
}, () => (0, import_react.createElement)(AchievementsView, { visible: true })));
|
|
208
298
|
disposers.push(mountToastLayer());
|
|
299
|
+
injectNavCss();
|
|
300
|
+
disposers.push(registerSettingsNavIcon(() => STRINGS[lang()].title));
|
|
209
301
|
return () => {
|
|
210
302
|
for (const dispose of disposers) dispose();
|
|
211
303
|
};
|
|
212
304
|
}
|
|
213
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-dsh-achievements-css]") === null) {
|
|
214
|
-
const tag = document.createElement("style");
|
|
215
|
-
tag.dataset.dshAchievementsCss = "1";
|
|
216
|
-
tag.textContent = CSS;
|
|
217
|
-
document.head.appendChild(tag);
|
|
218
|
-
}
|
|
219
305
|
var CSS = [
|
|
220
|
-
|
|
221
|
-
".
|
|
222
|
-
|
|
223
|
-
".
|
|
224
|
-
".
|
|
225
|
-
".
|
|
306
|
+
/* ---- 视图容器 ---- */
|
|
307
|
+
".ach{display:flex;flex-direction:column;gap:14px;padding:8px 0 4px}",
|
|
308
|
+
/* ---- 头部:标题+进度徽章+刷新 ---- */
|
|
309
|
+
".achHead{display:flex;align-items:center;gap:14px}",
|
|
310
|
+
".achHeadLeft{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}",
|
|
311
|
+
".achTitle{font-size:16px;font-weight:600;letter-spacing:.01em;color:var(--dsw-alias-label-primary,inherit)}",
|
|
312
|
+
".achDesc{font-size:12px;line-height:1.6;color:var(--dsw-alias-label-tertiary,inherit)}",
|
|
313
|
+
".achScore{flex:none;display:flex;align-items:baseline;gap:2px;padding:8px 14px;border-radius:14px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 10%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 25%,transparent)}",
|
|
314
|
+
".achScoreNum{font-size:20px;font-weight:700;color:var(--dsw-alias-state-business-primary,#4f8ef7);font-variant-numeric:tabular-nums}",
|
|
315
|
+
".achScoreTotal{font-size:13px;color:var(--dsw-alias-label-secondary,inherit);font-variant-numeric:tabular-nums}",
|
|
316
|
+
".achBtn{flex:none;padding:5px 14px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.4));border-radius:999px;background:transparent;color:var(--dsw-alias-label-secondary,inherit);font-size:12px;cursor:pointer;transition:background .15s}",
|
|
317
|
+
".achBtn:hover{background:var(--dsw-alias-fill-hover,rgba(127,127,127,.12))}",
|
|
318
|
+
/* ---- 类别 tabs ---- */
|
|
226
319
|
".achCats{display:flex;flex-wrap:wrap;gap:6px}",
|
|
227
|
-
".achCat{padding:3px
|
|
228
|
-
".
|
|
229
|
-
".
|
|
230
|
-
|
|
231
|
-
".
|
|
232
|
-
".
|
|
233
|
-
".
|
|
320
|
+
".achCat{padding:3px 11px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.35));border-radius:999px;background:transparent;color:var(--dsw-alias-label-secondary,inherit);font-size:12px;line-height:1.6;cursor:pointer;transition:all .15s}",
|
|
321
|
+
".achCat:hover{color:var(--dsw-alias-label-primary,inherit)}",
|
|
322
|
+
".achCatOn{border-color:var(--dsw-alias-state-business-primary,#4f8ef7);color:var(--dsw-alias-state-business-primary,#4f8ef7);background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 10%,transparent)}",
|
|
323
|
+
/* ---- 成就列表 ---- */
|
|
324
|
+
".achList{display:flex;flex-direction:column;gap:8px;max-height:480px;overflow:auto;padding-right:2px}",
|
|
325
|
+
".achRow{display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));border-radius:14px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}",
|
|
326
|
+
".achRowOn{border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 40%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 6%,transparent)}",
|
|
327
|
+
/* icon 圆底(稀有度 tint) */
|
|
328
|
+
".achIconWrap{flex:none;width:40px;height:40px;display:flex;align-items:center;justify-content:center;border-radius:12px;font-size:20px;line-height:1;background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 12%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 18%,transparent)}",
|
|
329
|
+
".achIconWrap-rare{background:color-mix(in srgb,#f59e0b 12%,transparent);border-color:color-mix(in srgb,#f59e0b 20%,transparent)}",
|
|
330
|
+
".achIconWrap-epic{background:color-mix(in srgb,#a78bfa 12%,transparent);border-color:color-mix(in srgb,#a78bfa 20%,transparent)}",
|
|
331
|
+
".achIconWrap-legendary{background:color-mix(in srgb,#f43f5e 12%,transparent);border-color:color-mix(in srgb,#f43f5e 20%,transparent)}",
|
|
332
|
+
".achIcon{filter:saturate(1.05)}",
|
|
333
|
+
/* 行正文 */
|
|
334
|
+
".achBody{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}",
|
|
234
335
|
".achName{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,inherit)}",
|
|
235
|
-
".achRarity{font-size:
|
|
236
|
-
".achRarity-rare{background:color-mix(in srgb,#f59e0b
|
|
237
|
-
".achRarity-epic{background:color-mix(in srgb,#a78bfa
|
|
238
|
-
".achRarity-legendary{background:color-mix(in srgb,#f43f5e
|
|
336
|
+
".achRarity{font-size:10px;line-height:1.6;padding:0 7px;border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 12%,transparent);color:var(--dsw-alias-state-business-primary,#4f8ef7);font-weight:500}",
|
|
337
|
+
".achRarity-rare{background:color-mix(in srgb,#f59e0b 12%,transparent);color:#d97706}",
|
|
338
|
+
".achRarity-epic{background:color-mix(in srgb,#a78bfa 12%,transparent);color:#7c3aed}",
|
|
339
|
+
".achRarity-legendary{background:color-mix(in srgb,#f43f5e 12%,transparent);color:#e11d48}",
|
|
340
|
+
/* 所属插件标签 */
|
|
341
|
+
".achPlugin{font-size:10px;line-height:1.6;padding:0 7px;border-radius:999px;background:var(--dsw-alias-fill-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-secondary,inherit);font-weight:500;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25))}",
|
|
342
|
+
/* 成就持续增加中提示卡 */
|
|
343
|
+
".achGrowing{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;border:1px dashed var(--dsw-alias-border-l2,rgba(127,127,127,.35));border-radius:12px;background:var(--dsw-alias-bg-layer-1,transparent);font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary,inherit)}",
|
|
344
|
+
".achGrowingIcon{flex:none;font-size:15px;line-height:1.4}",
|
|
345
|
+
/* 插件未装置灰 */
|
|
346
|
+
".achRowMuted{opacity:.55;filter:grayscale(.6)}",
|
|
347
|
+
".achStateOff{color:var(--dsw-alias-label-tertiary,inherit);background:var(--dsw-alias-fill-hover,rgba(127,127,127,.08))}",
|
|
239
348
|
".achDesc2{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary,inherit)}",
|
|
240
|
-
".achProgRow{display:flex;align-items:center;gap:8px;margin-top:
|
|
241
|
-
".achProg{flex:1;height:
|
|
242
|
-
".achProgFill{height:100%;background:var(--dsw-alias-state-business-primary,#4f8ef7)}",
|
|
243
|
-
".achProgNum{font-size:11px;color:var(--dsw-alias-label-tertiary,inherit)}",
|
|
244
|
-
|
|
245
|
-
".
|
|
246
|
-
".
|
|
349
|
+
".achProgRow{display:flex;align-items:center;gap:8px;margin-top:2px}",
|
|
350
|
+
".achProg{flex:1;height:5px;border-radius:999px;background:var(--dsw-alias-border-l1,rgba(127,127,127,.28));overflow:hidden}",
|
|
351
|
+
".achProgFill{height:100%;border-radius:999px;background:var(--dsw-alias-state-business-primary,#4f8ef7);transition:width .3s}",
|
|
352
|
+
".achProgNum{flex:none;font-size:11px;color:var(--dsw-alias-label-tertiary,inherit);font-variant-numeric:tabular-nums}",
|
|
353
|
+
/* 状态徽章 */
|
|
354
|
+
".achState{flex:none;font-size:11px;line-height:1.6;padding:0 8px;border-radius:999px;color:var(--dsw-alias-label-tertiary,inherit);background:var(--dsw-alias-fill-hover,rgba(127,127,127,.1))}",
|
|
355
|
+
".achStateOn{color:var(--dsw-alias-state-success-primary,#16a34a);background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#16a34a) 12%,transparent)}",
|
|
356
|
+
".achNote{font-size:12px;color:var(--dsw-alias-state-success-primary,#16a34a)}",
|
|
357
|
+
/* toast */
|
|
247
358
|
".at-row{position:fixed;right:16px;bottom:16px;z-index:9999;width:300px;padding:10px 12px;border:1px solid var(--dsw-alias-state-business-primary,#4f8ef7);border-radius:12px;background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.85));color:var(--dsw-alias-state-business-primary,#4f8ef7);font-size:13px;box-shadow:0 8px 24px rgba(0,0,0,.25)}"
|
|
248
359
|
].join("");
|
|
360
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-dsh-achievements-css]") === null) {
|
|
361
|
+
const tag = document.createElement("style");
|
|
362
|
+
tag.dataset.dshAchievementsCss = "1";
|
|
363
|
+
tag.textContent = CSS;
|
|
364
|
+
document.head.appendChild(tag);
|
|
365
|
+
}
|
|
249
366
|
return module.exports;
|
|
250
367
|
},
|
|
251
368
|
});
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
import { type AchieveState } from './rules.ts';
|
|
3
|
+
/** 有成就关联的插件(探测存在性以置灰未装插件的成就)。 */
|
|
4
|
+
export declare const ASSOCIATED_PLUGINS: readonly string[];
|
|
5
|
+
/** 探测已安装的关联插件:SSID_PROFILE_DIR(壳注入)与常见 profile 目录。 */
|
|
6
|
+
export declare function installedPlugins(): Set<string>;
|
|
3
7
|
export interface EngineSnapshot {
|
|
4
8
|
state: AchieveState;
|
|
5
9
|
distinct: Record<string, number>;
|
package/dist/engine.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 隐私纪律:只读叶级标量(工具名/成功标志/session 事件类型/token 数/
|
|
6
6
|
* 当天时刻),绝不读消息正文/文件内容/错误详情。
|
|
7
7
|
*/
|
|
8
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
@@ -101,6 +101,28 @@ const MEMORY_TOOLS = new Set([
|
|
|
101
101
|
const CONFIRM_TOOLS = new Set(['memory_confirm']);
|
|
102
102
|
const TEMPLATE_TOOLS = new Set(['prompt_search', 'prompt_get', 'prompt_list', 'prompt_add']);
|
|
103
103
|
const AUDIT_TOOLS = new Set(['context_audit']);
|
|
104
|
+
/** 有成就关联的插件(探测存在性以置灰未装插件的成就)。 */
|
|
105
|
+
export const ASSOCIATED_PLUGINS = [
|
|
106
|
+
'@max-null/dsh-memory',
|
|
107
|
+
'dsh-context-doctor',
|
|
108
|
+
'@changfenhuang/dsh-genui',
|
|
109
|
+
];
|
|
110
|
+
/** 探测已安装的关联插件:SSID_PROFILE_DIR(壳注入)与常见 profile 目录。 */
|
|
111
|
+
export function installedPlugins() {
|
|
112
|
+
const candidates = [
|
|
113
|
+
process.env.SSID_PROFILE_DIR,
|
|
114
|
+
process.env.DSH_HOME ? join(process.env.DSH_HOME, 'profiles', 'ssid') : undefined,
|
|
115
|
+
join(homedir(), '.dsh', 'profiles', 'ssid'),
|
|
116
|
+
].filter((dir) => typeof dir === 'string' && dir !== '');
|
|
117
|
+
const found = new Set();
|
|
118
|
+
for (const dir of candidates) {
|
|
119
|
+
for (const name of ASSOCIATED_PLUGINS) {
|
|
120
|
+
if (existsSync(join(dir, 'node_modules', ...name.split('/'))))
|
|
121
|
+
found.add(name);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return found;
|
|
125
|
+
}
|
|
104
126
|
/** 引擎(带事件源的宿主注入)。 */
|
|
105
127
|
export class AchievementsEngine {
|
|
106
128
|
store = new StateStore();
|
|
@@ -169,8 +191,9 @@ export class AchievementsEngine {
|
|
|
169
191
|
return queue;
|
|
170
192
|
}
|
|
171
193
|
fullSnapshot() {
|
|
194
|
+
const installed = installedPlugins();
|
|
172
195
|
return {
|
|
173
|
-
...snapshot(this.count, this.store.unlockMap),
|
|
196
|
+
...snapshot(this.count, this.store.unlockMap, installed),
|
|
174
197
|
recent: this.snapshot().recent,
|
|
175
198
|
distinct: this.store.distinctCounts,
|
|
176
199
|
};
|
package/dist/rules.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface AchievementDef {
|
|
|
21
21
|
counter: string;
|
|
22
22
|
target: number;
|
|
23
23
|
};
|
|
24
|
+
/** 所属插件(未装对应插件时成就置灰);null = 引擎内置(工具/行为/隐藏)。 */
|
|
25
|
+
plugin: string | null;
|
|
24
26
|
}
|
|
25
27
|
/** 计数键(leaf scalars only)。 */
|
|
26
28
|
export declare const KEYS: {
|
|
@@ -49,7 +51,7 @@ export interface AchieveState {
|
|
|
49
51
|
export declare function emptyState(): AchieveState;
|
|
50
52
|
/** 检查新解锁(不看 hidden——hidden 仅影响展示)。 */
|
|
51
53
|
export declare function checkUnlocks(state: AchieveState, unlocked: Record<string, number>): AchievementDef[];
|
|
52
|
-
/** 视图行(client
|
|
53
|
-
export declare function viewOf(achievement: AchievementDef, state: AchieveState, unlocked: Record<string, number
|
|
54
|
+
/** 视图行(client 渲染用;pluginInstalled 由 host 探测注入)。 */
|
|
55
|
+
export declare function viewOf(achievement: AchievementDef, state: AchieveState, unlocked: Record<string, number>, pluginInstalled?: boolean): Record<string, unknown>;
|
|
54
56
|
/** 快照(HTTP / 工具共用)。 */
|
|
55
|
-
export declare function snapshot(state: AchieveState, unlocked: Record<string, number>): Record<string, unknown>;
|
|
57
|
+
export declare function snapshot(state: AchieveState, unlocked: Record<string, number>, installed?: Set<string>): Record<string, unknown>;
|
package/dist/rules.js
CHANGED
|
@@ -27,31 +27,50 @@ export const KEYS = {
|
|
|
27
27
|
/** SSiD 全家桶成就表。 */
|
|
28
28
|
export const ACHIEVEMENTS = [
|
|
29
29
|
// ── 启程 ──
|
|
30
|
-
{ id: 'first-session', name: '启程', desc: '第一个会话开跑。', icon: '🚀', category: '启程', rarity: 'common', threshold: { counter: KEYS.sessions, target: 1 }
|
|
31
|
-
|
|
32
|
-
{ id: 'first-
|
|
30
|
+
{ id: 'first-session', name: '启程', desc: '第一个会话开跑。', icon: '🚀', category: '启程', rarity: 'common', threshold: { counter: KEYS.sessions, target: 1 },
|
|
31
|
+
plugin: null },
|
|
32
|
+
{ id: 'first-turn', name: '初试身手', desc: '完成第一轮对话。', icon: '👋', category: '启程', rarity: 'common', threshold: { counter: KEYS.turns, target: 1 },
|
|
33
|
+
plugin: null },
|
|
34
|
+
{ id: 'first-tool', name: '工具初体验', desc: '第一次调用工具。', icon: '🛠️', category: '启程', rarity: 'common', threshold: { counter: KEYS.tools, target: 1 },
|
|
35
|
+
plugin: null },
|
|
33
36
|
// ── 记忆 ──
|
|
34
|
-
{ id: 'memory-first-save', name: '落笔成忆', desc: '第一次使用记忆工具(memory_*)。', icon: '📝', category: '记忆', rarity: 'common', threshold: { counter: KEYS.memoryTools, target: 1 }
|
|
35
|
-
|
|
36
|
-
{ id: 'memory-
|
|
37
|
-
|
|
37
|
+
{ id: 'memory-first-save', name: '落笔成忆', desc: '第一次使用记忆工具(memory_*)。', icon: '📝', category: '记忆', rarity: 'common', threshold: { counter: KEYS.memoryTools, target: 1 },
|
|
38
|
+
plugin: '@max-null/dsh-memory' },
|
|
39
|
+
{ id: 'memory-10', name: '记忆织工', desc: '累计 10 次记忆工具调用。', icon: '🧵', category: '记忆', rarity: 'rare', threshold: { counter: KEYS.memoryTools, target: 10 },
|
|
40
|
+
plugin: '@max-null/dsh-memory' },
|
|
41
|
+
{ id: 'memory-first-confirm', name: '审核人', desc: '第一次人工确认(memory_confirm)。', icon: '✅', category: '记忆', rarity: 'rare', threshold: { counter: KEYS.memoryConfirmed, target: 1 },
|
|
42
|
+
plugin: '@max-null/dsh-memory' },
|
|
43
|
+
{ id: 'template-user', name: '模板常客', desc: '第一次使用模板库工具(prompt_*)。', icon: '🗂️', category: '记忆', rarity: 'rare', threshold: { counter: KEYS.templateTools, target: 1 },
|
|
44
|
+
plugin: '@max-null/dsh-memory' },
|
|
38
45
|
// ── 审计 ──
|
|
39
|
-
{ id: 'audit-first', name: '上下文审计官', desc: '第一次跑 context_audit。', icon: '🔍', category: '审计', rarity: 'rare', threshold: { counter: KEYS.audits, target: 1 }
|
|
40
|
-
|
|
46
|
+
{ id: 'audit-first', name: '上下文审计官', desc: '第一次跑 context_audit。', icon: '🔍', category: '审计', rarity: 'rare', threshold: { counter: KEYS.audits, target: 1 },
|
|
47
|
+
plugin: 'dsh-context-doctor' },
|
|
48
|
+
{ id: 'audit-10', name: '注审老手', desc: '累计 10 次上下文审计。', icon: '🕵️', category: '审计', rarity: 'epic', threshold: { counter: KEYS.audits, target: 10 },
|
|
49
|
+
plugin: 'dsh-context-doctor' },
|
|
41
50
|
// ── GenUI(client 侧合并 genui 计数)──
|
|
42
|
-
{ id: 'genui-first', name: '第一块面板', desc: 'GenUI 渲染了第一块界面。', icon: '🎨', category: 'GenUI', rarity: 'common', threshold: { counter: KEYS.genuiUnlocked, target: 1 }
|
|
43
|
-
|
|
44
|
-
{ id: 'genui-
|
|
51
|
+
{ id: 'genui-first', name: '第一块面板', desc: 'GenUI 渲染了第一块界面。', icon: '🎨', category: 'GenUI', rarity: 'common', threshold: { counter: KEYS.genuiUnlocked, target: 1 },
|
|
52
|
+
plugin: '@changfenhuang/dsh-genui' },
|
|
53
|
+
{ id: 'genui-5', name: '界面收藏家', desc: 'GenUI 解锁 5 个成就。', icon: '🖼️', category: 'GenUI', rarity: 'rare', threshold: { counter: KEYS.genuiUnlocked, target: 5 },
|
|
54
|
+
plugin: '@changfenhuang/dsh-genui' },
|
|
55
|
+
{ id: 'genui-25', name: '界面编织者', desc: 'GenUI 渲染过 25 个界面。', icon: '🧶', category: 'GenUI', rarity: 'epic', threshold: { counter: KEYS.genuiFences, target: 25 },
|
|
56
|
+
plugin: '@changfenhuang/dsh-genui' },
|
|
45
57
|
// ── 工具 ──
|
|
46
|
-
{ id: 'tool-10', name: '工具新手', desc: '累计调用 10 次工具。', icon: '🔧', category: '工具', rarity: 'common', threshold: { counter: KEYS.tools, target: 10 }
|
|
47
|
-
|
|
48
|
-
{ id: '
|
|
58
|
+
{ id: 'tool-10', name: '工具新手', desc: '累计调用 10 次工具。', icon: '🔧', category: '工具', rarity: 'common', threshold: { counter: KEYS.tools, target: 10 },
|
|
59
|
+
plugin: null },
|
|
60
|
+
{ id: 'tool-50', name: '工具达人', desc: '累计调用 50 次工具。', icon: '⚙️', category: '工具', rarity: 'rare', threshold: { counter: KEYS.tools, target: 50 },
|
|
61
|
+
plugin: null },
|
|
62
|
+
{ id: 'five-tools', name: '多面手', desc: '使用过 5 种不同工具。', icon: '🧰', category: '工具', rarity: 'rare', threshold: { counter: KEYS.toolsUsed, target: 5 },
|
|
63
|
+
plugin: null },
|
|
49
64
|
// ── 行为 ──
|
|
50
|
-
{ id: 'marathon', name: '马拉松', desc: '单回合内调用 10 次工具。', icon: '🏃', category: '行为', rarity: 'rare', hidden: true, threshold: { counter: KEYS.marathonTurns, target: 1 }
|
|
51
|
-
|
|
65
|
+
{ id: 'marathon', name: '马拉松', desc: '单回合内调用 10 次工具。', icon: '🏃', category: '行为', rarity: 'rare', hidden: true, threshold: { counter: KEYS.marathonTurns, target: 1 },
|
|
66
|
+
plugin: null },
|
|
67
|
+
{ id: 'night-owl', name: '夜猫子', desc: '在凌晨 0-5 点发过消息。', icon: '🦉', category: '行为', rarity: 'rare', hidden: true, threshold: { counter: KEYS.midnightTurns, target: 1 },
|
|
68
|
+
plugin: null },
|
|
52
69
|
// ── 隐藏 ──
|
|
53
|
-
{ id: 'self-ref', name: '自我指涉', desc: '用成就工具查询自己。', icon: '🪞', category: '隐藏', rarity: 'rare', hidden: true, threshold: { counter: KEYS.selfQueries, target: 1 }
|
|
54
|
-
|
|
70
|
+
{ id: 'self-ref', name: '自我指涉', desc: '用成就工具查询自己。', icon: '🪞', category: '隐藏', rarity: 'rare', hidden: true, threshold: { counter: KEYS.selfQueries, target: 1 },
|
|
71
|
+
plugin: null },
|
|
72
|
+
{ id: 'billionaire', name: '亿万富翁', desc: '累计消耗一亿 token。', icon: '💰', category: '隐藏', rarity: 'legendary', hidden: true, threshold: { counter: KEYS.tokens, target: 100000000 },
|
|
73
|
+
plugin: null },
|
|
55
74
|
];
|
|
56
75
|
export function emptyState() {
|
|
57
76
|
const state = {};
|
|
@@ -64,8 +83,8 @@ export function checkUnlocks(state, unlocked) {
|
|
|
64
83
|
return ACHIEVEMENTS.filter(achievement => unlocked[achievement.id] === undefined
|
|
65
84
|
&& (state[achievement.threshold.counter] ?? 0) >= achievement.threshold.target);
|
|
66
85
|
}
|
|
67
|
-
/** 视图行(client
|
|
68
|
-
export function viewOf(achievement, state, unlocked) {
|
|
86
|
+
/** 视图行(client 渲染用;pluginInstalled 由 host 探测注入)。 */
|
|
87
|
+
export function viewOf(achievement, state, unlocked, pluginInstalled = true) {
|
|
69
88
|
const current = Math.min(state[achievement.threshold.counter] ?? 0, achievement.threshold.target);
|
|
70
89
|
return {
|
|
71
90
|
id: achievement.id,
|
|
@@ -77,13 +96,15 @@ export function viewOf(achievement, state, unlocked) {
|
|
|
77
96
|
hidden: achievement.hidden === true,
|
|
78
97
|
unlocked: unlocked[achievement.id] !== undefined,
|
|
79
98
|
progress: { current, target: achievement.threshold.target },
|
|
99
|
+
plugin: achievement.plugin,
|
|
100
|
+
pluginInstalled: achievement.plugin === null || pluginInstalled,
|
|
80
101
|
};
|
|
81
102
|
}
|
|
82
103
|
/** 快照(HTTP / 工具共用)。 */
|
|
83
|
-
export function snapshot(state, unlocked) {
|
|
104
|
+
export function snapshot(state, unlocked, installed = new Set()) {
|
|
84
105
|
return {
|
|
85
106
|
total: ACHIEVEMENTS.length,
|
|
86
107
|
unlocked: Object.keys(unlocked).length,
|
|
87
|
-
achievements: ACHIEVEMENTS.map(achievement => viewOf(achievement, state, unlocked)),
|
|
108
|
+
achievements: ACHIEVEMENTS.map(achievement => viewOf(achievement, state, unlocked, achievement.plugin === null || installed.has(achievement.plugin))),
|
|
88
109
|
};
|
|
89
110
|
}
|
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.1",
|
|
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",
|