dsh-loci 2.11.0 → 3.0.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/CHANGELOG.md +114 -0
- package/README.md +2 -2
- package/client.js +141 -209
- package/package.json +1 -1
- package/src/host/index.js +1 -1
- package/src/mcp/store-facade.js +1 -1
- package/locale/zh.json +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,120 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the project follows
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html) from 1.0.0.
|
|
6
6
|
|
|
7
|
+
## [3.0.1] — 2026-11-15
|
|
8
|
+
|
|
9
|
+
The 3.0.0 card was correct markup with no stylesheet, and the screenshot showed it.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **The card was never styled outside the panel.** Every `.loci-*` rule lives in one CSS
|
|
14
|
+
template string that only `MemoryPanel` and `SettingsSection` render as a `<style>`
|
|
15
|
+
element. In the sidebar, with the panel closed, no `<style>` existed — so the card's DOM
|
|
16
|
+
was right and its layout was simply absent. That is what "bare text in the sidebar"
|
|
17
|
+
looked like, and it predates 3.0.0: the first screenshot showed it too. The card now
|
|
18
|
+
renders the stylesheet itself, and so does its rail variant.
|
|
19
|
+
|
|
20
|
+
- **`card.pending` was defined twice.** The old value was the sentence
|
|
21
|
+
`'{n} still need your review'`; 3.0.0 added a bare `'Pending'` label for the metric row,
|
|
22
|
+
and the later definition won — so the card printed the sentence with an unsubstituted
|
|
23
|
+
`{n}`. The sentence is now `card.pendingSentence` and the label stays `Pending`.
|
|
24
|
+
|
|
25
|
+
- **The version chip rendered as `v3. 0. 0`.** The chip used
|
|
26
|
+
`ui-monospace, SFMono-Regular, Menlo, monospace`, which on Windows falls back to a
|
|
27
|
+
monospace face whose period is centred in a wide advance; at 8.5px that reads as gaps
|
|
28
|
+
between the digits. Measured with `getComputedStyle`: `letter-spacing: normal`,
|
|
29
|
+
`word-spacing: 0`, width 25.50 — the spacing was correct, the **glyph** was not. The chip
|
|
30
|
+
now inherits the UI font with tabular figures, at 9px.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **`scripts/shot-card.mjs`** renders the sidebar card to a PNG using the stylesheet
|
|
35
|
+
extracted from `client.js` and the DOM the component produces, through headless Edge. It
|
|
36
|
+
cannot catch a logic mistake, only a drawing one — but a drawing mistake is exactly what
|
|
37
|
+
shipped twice, and a screenshot is the only review that shows it.
|
|
38
|
+
|
|
39
|
+
### Not added
|
|
40
|
+
|
|
41
|
+
Four guard files from the previous draft (a separate client-i18n test, an English-only test,
|
|
42
|
+
a CSS-shape assertion and a manifest check) were collapsed into **one** test file and one
|
|
43
|
+
manifest assertion. Guards that outnumber the behaviour they protect get deleted by the next
|
|
44
|
+
person, which is worse than not having them.
|
|
45
|
+
|
|
46
|
+
### Corrected in the record
|
|
47
|
+
|
|
48
|
+
The 3.0.0 log entry said the card had been verified. It had not been looked at. The entry
|
|
49
|
+
above records what the screenshot showed.
|
|
50
|
+
|
|
51
|
+
## [3.0.0] — 2026-11-15
|
|
52
|
+
|
|
53
|
+
The sidebar card was not a card, and the plugin had a second language it did not need.
|
|
54
|
+
|
|
55
|
+
### Changed — the card
|
|
56
|
+
|
|
57
|
+
The screenshot that prompted this showed three plugin cards in a row: two with white
|
|
58
|
+
rounded shells, a status dot, a version chip and numeric rows; one — this one — as bare
|
|
59
|
+
text on the sidebar background, two sentences of prose and nothing clickable.
|
|
60
|
+
|
|
61
|
+
The difference was measurable, not aesthetic. `dsh-ustc-quota`'s shell carries
|
|
62
|
+
`box-shadow: 0 3px 12px`, `flex: 0 0 100%`, `order: 1`, `grid-column: 1 / -1`,
|
|
63
|
+
`border-radius: 12px` and `font-variant-numeric: tabular-nums`. This card had none of them,
|
|
64
|
+
so it sat in the same visual plane as the sidebar and looked like a note pasted over it.
|
|
65
|
+
|
|
66
|
+
Now:
|
|
67
|
+
|
|
68
|
+
- **Shell** — the same box model the neighbouring plugins use, so three cards in one column
|
|
69
|
+
read as three cards.
|
|
70
|
+
- **Header** — title, a dim monospace version chip, a 5px status dot (brand / warn / error),
|
|
71
|
+
and a right-aligned `updated <time>` that truncates instead of wrapping.
|
|
72
|
+
- **Numbers** — `Verified / Pending / Disputed` in a three-column grid, tabular figures,
|
|
73
|
+
amber for pending and red for disputed.
|
|
74
|
+
- **Budget** — a thin bar of index bytes against `sectionMaxBytes`, turning amber past 85%.
|
|
75
|
+
- **Usage** — `Injected / Opened / Dead` (the last one only when non-zero), which is the row
|
|
76
|
+
that makes schema v2's counters visible where a person will actually see them.
|
|
77
|
+
- **Empty state** — one line and one instruction instead of two sentences of apology.
|
|
78
|
+
|
|
79
|
+
This reverses the earlier "a page of notes, not a dashboard" stance *for this surface*. The
|
|
80
|
+
audit had already called that stance out: an aesthetic refusal to interrupt, applied to the
|
|
81
|
+
one place where a person must act. The panel keeps the prose; the card gets the numbers.
|
|
82
|
+
|
|
83
|
+
### Changed — one language
|
|
84
|
+
|
|
85
|
+
Chinese was not in the client because anyone chose it; the client shipped a full `ZH`
|
|
86
|
+
dictionary, so a browser whose locale resolved to `zh` rendered the UI in Chinese. The
|
|
87
|
+
English dictionary was already complete. The second dictionary was the only reason the
|
|
88
|
+
product had a language at all.
|
|
89
|
+
|
|
90
|
+
- `client.js`: the 128-line `ZH` dictionary is gone, the locale registration is `{ en: EN }`,
|
|
91
|
+
and four Chinese comments are rewritten.
|
|
92
|
+
- `locale/zh.json`: deleted; `locale/en.json` is the only locale file.
|
|
93
|
+
- `package.json` / `manifests`: no longer advertise a second language. `README.md` loses the
|
|
94
|
+
`记忆宫殿` gloss and the `更新于` label description; the Chinese *examples* of stored
|
|
95
|
+
content stay, because they demonstrate CJK retrieval.
|
|
96
|
+
- `scripts/capture-panel.mjs`: its panel selector matched `记忆|Memory` and can only ever
|
|
97
|
+
match the latter now.
|
|
98
|
+
|
|
99
|
+
Kept, deliberately: the Chinese regexes in `capture.js` (they match Chinese input) and in
|
|
100
|
+
`sanitize.js` (they match Chinese prompt-injection phrasings). Deleting those would disable
|
|
101
|
+
the features for the language most of the user's memories are written in. They are pattern
|
|
102
|
+
data, not output.
|
|
103
|
+
|
|
104
|
+
### Added
|
|
105
|
+
|
|
106
|
+
- `test/unit/client-i18n.test.mjs` — no Chinese in `client.js`, no `ZH` dictionary, no
|
|
107
|
+
`locale/zh.json`, every `t()` key defined, and the card still has its shell/metric pieces.
|
|
108
|
+
- `test/unit/english-only.test.mjs` — no Chinese in emitted strings under `src/` outside a
|
|
109
|
+
two-file allowlist, and the allowlisted files must still contain their patterns (a guard
|
|
110
|
+
that silently allows a file would hide the deletion it exists to catch).
|
|
111
|
+
- `scripts/check-manifest.mjs` now **fails if `locale/zh.json` reappears**, so the language
|
|
112
|
+
cannot come back in a version that never touched `client.js`.
|
|
113
|
+
|
|
114
|
+
### Still Chinese, on purpose
|
|
115
|
+
|
|
116
|
+
`README.zh.md`, `docs/*.md`, and the `titleZh`/`descriptionZh` fields in
|
|
117
|
+
`marketplace/entry.json` are documentation and storefront metadata, not output the plugin
|
|
118
|
+
emits at runtime. They were left alone rather than deleted; if they should go too, that is a
|
|
119
|
+
separate decision about the deliverable, not about the product's language.
|
|
120
|
+
|
|
7
121
|
## [2.11.0] — 2026-11-15
|
|
8
122
|
|
|
9
123
|
One fix, made because it is the last thing that could put another project's facts into a
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-loci · Loci
|
|
1
|
+
# dsh-loci · Loci
|
|
2
2
|
|
|
3
3
|
**A long-term memory for DeepSeek Harness where the prompt only ever sees the palace's index — every room is one lookup away.**
|
|
4
4
|
|
|
@@ -86,7 +86,7 @@ one of them.
|
|
|
86
86
|
## Where you see it
|
|
87
87
|
|
|
88
88
|
- **Sidebar note** — a small card beside Settings, like the quota and advisor
|
|
89
|
-
cards
|
|
89
|
+
cards: a shadowed card in the sidebar footer with a status dot, a `updated <time>` stamp and numeric rows (verified / pending / disputed, index bytes against the budget, injected / opened / dead weight)
|
|
90
90
|
over one short paragraph of what the agent currently understands in this
|
|
91
91
|
project (a consensus, a lesson, a fact), plus one quiet line if something is
|
|
92
92
|
waiting for your review. It answers "did it take that in?" — no counters.
|
package/client.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* - a **sidebar mirror** (`sidebar.footer.action`): a flat, dense card that
|
|
7
7
|
* shows what memory changed today and last week, with the last few changes
|
|
8
|
-
* as one-liners. It is the
|
|
8
|
+
* as one-liners. It is the sidebar mirror — glanceable, never modal;
|
|
9
9
|
* - a **panel** (`sidebar.panellist` + `main`): the full view, with a feed,
|
|
10
10
|
* the exact injected index, search, the review queue and statistics.
|
|
11
11
|
*
|
|
@@ -34,10 +34,18 @@ window.__ModuleLoader__.load({
|
|
|
34
34
|
'Layered long-term memory: the prompt holds a stable index, full text is one lookup away, and only verified facts are injected.',
|
|
35
35
|
|
|
36
36
|
'card.title': 'Memory',
|
|
37
|
-
'card.empty': 'Nothing
|
|
38
|
-
'card.
|
|
37
|
+
'card.empty': 'Nothing saved yet',
|
|
38
|
+
'card.metrics': 'Memory at a glance',
|
|
39
|
+
'card.emptyHint': 'Ask the agent to remember something, then confirm it here.',
|
|
40
|
+
'card.verified': 'Verified',
|
|
41
|
+
'card.pending': 'Pending',
|
|
42
|
+
'card.disputed': 'Disputed',
|
|
43
|
+
'card.injected': 'Injected',
|
|
44
|
+
'card.opened': 'Opened',
|
|
45
|
+
'card.dead': 'Dead',
|
|
46
|
+
'card.pendingHint': '{n} item(s) waiting for you in Review.',
|
|
39
47
|
'card.updated': 'updated',
|
|
40
|
-
'card.
|
|
48
|
+
'card.pendingSentence': '{n} still need your review',
|
|
41
49
|
'card.pinned': 'pinned',
|
|
42
50
|
'card.open': 'Open memory',
|
|
43
51
|
'card.check': 'Check for updates',
|
|
@@ -157,151 +165,35 @@ window.__ModuleLoader__.load({
|
|
|
157
165
|
'panel.version': 'version',
|
|
158
166
|
};
|
|
159
167
|
|
|
160
|
-
const ZH = {
|
|
161
|
-
'meta.title': 'Loci 记忆宫殿',
|
|
162
|
-
'meta.description':
|
|
163
|
-
'分层长期记忆:提示词里只放稳定索引,全文按需一次取回;只有已核实的记忆才会被注入。',
|
|
164
|
-
|
|
165
|
-
'card.title': '记忆',
|
|
166
|
-
'card.empty': '还没有真正记下的东西',
|
|
167
|
-
'card.emptyHint': '你确认过的事实会出现在这里',
|
|
168
|
-
'card.updated': '更新于',
|
|
169
|
-
'card.pending': '另有 {n} 条我拿不准,等你看一眼',
|
|
170
|
-
'card.pinned': '置顶',
|
|
171
|
-
'card.open': '打开记忆',
|
|
172
|
-
'card.check': '检查更新',
|
|
173
|
-
'card.upToDate': '已是最新',
|
|
174
|
-
'card.updateTo': '有新版本',
|
|
175
|
-
'card.checkOff': '更新检查已关闭',
|
|
176
|
-
'card.checking': '检查中…',
|
|
177
|
-
'card.unknown': '无法检查版本',
|
|
178
|
-
'card.unpublished': '尚未发布',
|
|
179
|
-
|
|
180
|
-
'prose.agreed': '已定:',
|
|
181
|
-
'prose.lesson': '经验:',
|
|
182
|
-
|
|
183
|
-
'kind.rule': '共识',
|
|
184
|
-
'kind.procedural': '经验',
|
|
185
|
-
'kind.semantic': '事实',
|
|
186
|
-
'kind.episodic': '事件',
|
|
187
|
-
'kind.working': '草稿',
|
|
188
|
-
|
|
189
|
-
'since.now': '刚才',
|
|
190
|
-
'since.minutes': '{n} 分钟前',
|
|
191
|
-
'since.hours': '{n} 小时前',
|
|
192
|
-
'since.yesterday': '昨天',
|
|
193
|
-
'since.days': '{n} 天前',
|
|
194
|
-
|
|
195
|
-
'action.added': '新增',
|
|
196
|
-
'action.revised': '更正',
|
|
197
|
-
'action.verified': '核实',
|
|
198
|
-
'action.disputed': '存疑',
|
|
199
|
-
'action.pinned': '置顶',
|
|
200
|
-
'action.unpinned': '取消置顶',
|
|
201
|
-
'action.deleted': '删除',
|
|
202
|
-
'action.purged': '彻底删除',
|
|
203
|
-
'action.restored': '恢复',
|
|
204
|
-
'action.imported': '导入',
|
|
205
|
-
'action.maintained': '维护',
|
|
206
|
-
|
|
207
|
-
'settings.title': 'Loci',
|
|
208
|
-
'settings.intro': '给 agent 的长期记忆。标注「即时」的改动立即生效。',
|
|
209
|
-
'settings.unavailable': '当前缺少设置服务,以下显示实际生效的配置。',
|
|
210
|
-
'settings.saved': '已保存',
|
|
211
|
-
'settings.rejected': '宿主拒绝写入',
|
|
212
|
-
'settings.failed': '写入失败',
|
|
213
|
-
'settings.budget': '索引预算',
|
|
214
|
-
'settings.budgetHint': '整段注入的字节上限',
|
|
215
|
-
'settings.scope': '新事实的默认作用域',
|
|
216
|
-
'settings.capture': '会话捕获',
|
|
217
|
-
'settings.assist': '首轮辅助召回',
|
|
218
|
-
'settings.maintenance': '后台维护',
|
|
219
|
-
'settings.secret': '记忆中的密钥',
|
|
220
|
-
'settings.vector': '向量通道',
|
|
221
|
-
'settings.diagnostics': '诊断',
|
|
222
|
-
'settings.danger': '清空记忆',
|
|
223
|
-
'settings.dangerHint': '输入确认短语才会执行。条目为软删除:历史仍可读、可导出。',
|
|
224
|
-
'settings.dangerPhrase': 'forget everything',
|
|
225
|
-
'settings.dangerRun': '删除全部记忆',
|
|
226
|
-
'settings.dangerBusy': '删除中…',
|
|
227
|
-
'settings.dangerDone': '已删除 {n} 条。',
|
|
228
|
-
'off': '关闭',
|
|
229
|
-
'captureRules': '仅规则',
|
|
230
|
-
'secretRedact': '脱敏',
|
|
231
|
-
'secretReject': '拒绝写入',
|
|
232
|
-
'vectorOff': '关闭(仅词法)',
|
|
233
|
-
'vectorHttp': 'HTTP 端点',
|
|
234
|
-
|
|
235
|
-
'panel.title': '记忆',
|
|
236
|
-
'panel.feed': '动态',
|
|
237
|
-
'panel.index': '索引',
|
|
238
|
-
'panel.search': '检索',
|
|
239
|
-
'panel.review': '待审',
|
|
240
|
-
'panel.stats': '统计',
|
|
241
|
-
'panel.refresh': '刷新',
|
|
242
|
-
'panel.loading': '加载中…',
|
|
243
|
-
'panel.empty': '这里还没有内容。',
|
|
244
|
-
'panel.feedHint': '记住了什么、更正了什么、核实或删除了什么。最新的在前。',
|
|
245
|
-
'panel.indexHint': '与模型系统提示里完全一致的那一段,以及它的字节开销。',
|
|
246
|
-
'panel.bytes': '字节',
|
|
247
|
-
'panel.listed': '已列出',
|
|
248
|
-
'panel.hidden': '未列出',
|
|
249
|
-
'panel.searchPlaceholder': '检索标题与正文…',
|
|
250
|
-
'panel.searchEmpty': '没有匹配。',
|
|
251
|
-
'panel.more': '加载更多',
|
|
252
|
-
'panel.total': '总数',
|
|
253
|
-
'panel.verify': '核实',
|
|
254
|
-
'panel.dispute': '标记争议',
|
|
255
|
-
'panel.delete': '删除',
|
|
256
|
-
'panel.restore': '恢复',
|
|
257
|
-
'panel.pin': '置顶',
|
|
258
|
-
'panel.unpin': '取消置顶',
|
|
259
|
-
'panel.back': '返回',
|
|
260
|
-
'panel.history': '历史',
|
|
261
|
-
'panel.evidence': '证据',
|
|
262
|
-
'panel.source': '出处',
|
|
263
|
-
'panel.lesson': '教训',
|
|
264
|
-
'panel.lesson.pattern': '问题形态',
|
|
265
|
-
'panel.lesson.fix': '做法',
|
|
266
|
-
'panel.lesson.prevents': '不做会怎样',
|
|
267
|
-
'panel.lesson.when': '适用场景',
|
|
268
|
-
'panel.relations': '关联',
|
|
269
|
-
'panel.candidates': '候选',
|
|
270
|
-
'panel.disputed': '争议',
|
|
271
|
-
'panel.noCandidates': '没有待审条目。',
|
|
272
|
-
'panel.reviewHint': '这里只列可能被注入的条目;过去会话的摘要属于历史,不是主张。',
|
|
273
|
-
'panel.reject': '驳回',
|
|
274
|
-
'panel.digestOf': '同一批次提出 {n} 条',
|
|
275
|
-
'panel.verifyGroup': '核实这 {n} 条',
|
|
276
|
-
'panel.verifyAll': '全部核实({n})',
|
|
277
|
-
'panel.actionFailed': '操作失败',
|
|
278
|
-
'panel.injections': '最近注入',
|
|
279
|
-
'panel.deadWeight': '注入了但从未被打开',
|
|
280
|
-
'panel.injected': '次注入',
|
|
281
|
-
'panel.opened': '次打开',
|
|
282
|
-
'panel.store': '存储',
|
|
283
|
-
'panel.confirmDelete': '软删除这条记忆?',
|
|
284
|
-
'panel.error': '请求失败',
|
|
285
|
-
'panel.updated': '更新于',
|
|
286
|
-
'panel.version': '版本',
|
|
287
|
-
};
|
|
288
|
-
|
|
289
168
|
const CSS = `
|
|
290
169
|
.loci-root { display: flex; flex-direction: column; height: 100%; min-height: 0; color: var(--dsw-alias-label-primary); font-size: 13px; }
|
|
291
|
-
.loci-card-root { display: flex; flex-direction: column; gap:
|
|
170
|
+
.loci-card-root { display: flex; flex-direction: column; gap: 5px; flex: 0 0 100%; order: 1; grid-column: 1 / -1; width: 100%; min-width: 0; box-sizing: border-box; margin: 0 0 6px; padding: 10px 9px 8px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; background: var(--dsw-alias-bg-layer-1); box-shadow: 0 3px 12px rgba(0, 0, 0, .16); cursor: pointer; text-align: left; font: inherit; color: var(--dsw-alias-label-primary); font-size: 11px; line-height: 1.3; font-variant-numeric: tabular-nums; }
|
|
292
171
|
.loci-card-root:hover { background: var(--dsw-alias-bg-layer-2); }
|
|
293
|
-
.loci-card-root.is-rail { padding: 6px; align-items: center; justify-content: center; }
|
|
294
|
-
.loci-card-head { display: flex; align-items: center; gap: 6px; width: 100%; }
|
|
295
|
-
.loci-card-title { font-size:
|
|
296
|
-
.loci-card-
|
|
297
|
-
.loci-card-ver {
|
|
172
|
+
.loci-card-root.is-rail { flex: 0 0 auto; padding: 6px; align-items: center; justify-content: center; }
|
|
173
|
+
.loci-card-head { display: flex; align-items: center; gap: 6px; min-width: 0; width: 100%; }
|
|
174
|
+
.loci-card-title { font-size: 13px; font-weight: 700; letter-spacing: -.01em; white-space: nowrap; }
|
|
175
|
+
.loci-card-ver { flex: 0 0 auto; margin: 0; padding: 0; border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font-size: 9px; line-height: 1.2; font-family: inherit; font-variant-numeric: tabular-nums; cursor: pointer; white-space: nowrap; }
|
|
176
|
+
.loci-card-ver:hover { color: var(--dsw-alias-label-primary); }
|
|
177
|
+
.loci-card-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--dsw-alias-brand-primary); flex: 0 0 auto; }
|
|
178
|
+
.loci-card-dot.is-warn { background: var(--dsw-alias-state-warn-primary); }
|
|
179
|
+
.loci-card-dot.is-error { background: var(--dsw-alias-state-error-primary); }
|
|
180
|
+
.loci-card-meta { flex: 1 1 auto; min-width: 0; font-size: 8.5px; color: var(--dsw-alias-label-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }
|
|
298
181
|
.loci-card-mark { display: flex; align-items: center; color: var(--dsw-alias-brand-primary); flex: 0 0 auto; }
|
|
299
|
-
.loci-card-
|
|
300
|
-
.loci-card-
|
|
301
|
-
.loci-card-
|
|
302
|
-
.loci-card-
|
|
303
|
-
.loci-card-
|
|
304
|
-
.loci-card-
|
|
182
|
+
.loci-card-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 3px 8px; width: 100%; }
|
|
183
|
+
.loci-card-metric { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
|
|
184
|
+
.loci-card-metric-label { font-size: 8px; color: var(--dsw-alias-label-secondary); text-transform: uppercase; letter-spacing: .05em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
185
|
+
.loci-card-metric-value { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary); font-variant-numeric: tabular-nums; }
|
|
186
|
+
.loci-card-metric-value.is-warn { color: var(--dsw-alias-state-warn-primary); }
|
|
187
|
+
.loci-card-metric-value.is-error { color: var(--dsw-alias-state-error-primary); }
|
|
188
|
+
.loci-card-barline { display: flex; align-items: center; gap: 6px; width: 100%; }
|
|
189
|
+
.loci-card-bar { flex: 1 1 auto; min-width: 20px; height: 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2); overflow: hidden; }
|
|
190
|
+
.loci-card-bar-fill { height: 100%; border-radius: 999px; background: var(--dsw-alias-brand-primary); }
|
|
191
|
+
.loci-card-bar-fill.is-warn { background: var(--dsw-alias-state-warn-primary); }
|
|
192
|
+
.loci-card-bar-label { font-size: 8.5px; color: var(--dsw-alias-label-secondary); white-space: nowrap; }
|
|
193
|
+
.loci-card-foot { display: flex; align-items: center; gap: 6px; width: 100%; font-size: 8.5px; color: var(--dsw-alias-label-secondary); }
|
|
194
|
+
.loci-card-foot-grow { flex: 1 1 auto; }
|
|
195
|
+
.loci-card-empty { font-size: 9.5px; color: var(--dsw-alias-label-secondary); }
|
|
196
|
+
.loci-card-hint { font-size: 9px; color: var(--dsw-alias-label-secondary); }
|
|
305
197
|
.loci-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--dsw-alias-brand-primary); flex: 0 0 auto; }
|
|
306
198
|
|
|
307
199
|
.loci-bar { display: flex; align-items: center; gap: 12px; padding: 10px 16px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
@@ -420,7 +312,7 @@ window.__ModuleLoader__.load({
|
|
|
420
312
|
*
|
|
421
313
|
* The card is a page of notes, so this reads as prose rather than a list: one
|
|
422
314
|
* clause per entry, joined with 。or , and cut when it stops fitting. Consensus
|
|
423
|
-
* items lead with
|
|
315
|
+
* items lead with the agreement verb and a fact leads with its own subject, because "we
|
|
424
316
|
* agreed" and "it happens to be true" are different kinds of knowing.
|
|
425
317
|
*/
|
|
426
318
|
function proseOf(notes, t) {
|
|
@@ -452,7 +344,7 @@ window.__ModuleLoader__.load({
|
|
|
452
344
|
/**
|
|
453
345
|
* How long ago something was understood, in words rather than numbers.
|
|
454
346
|
*
|
|
455
|
-
* "
|
|
347
|
+
* "yesterday" and "just now" are the whole point of the card: they are what makes a
|
|
456
348
|
* reader believe the memory is alive. A bare timestamp makes them do the
|
|
457
349
|
* arithmetic instead.
|
|
458
350
|
*/
|
|
@@ -503,41 +395,35 @@ window.__ModuleLoader__.load({
|
|
|
503
395
|
}
|
|
504
396
|
|
|
505
397
|
/** The small version line shared by the card and the panel footer. */
|
|
506
|
-
function
|
|
507
|
-
|
|
398
|
+
function VersionLabel({ t, update, onCheck, checking }) {
|
|
399
|
+
// A chip, not a sentence. The old label said "update check is off" inline, which
|
|
400
|
+
// is why the card header read like body copy; the state now lives in the tooltip.
|
|
401
|
+
const version = update?.version;
|
|
508
402
|
const hasUpdate = update?.updateAvailable === true;
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
const checkDisabled = update?.checkEnabled === false;
|
|
513
|
-
const text = hasUpdate
|
|
514
|
-
? `${t('card.updateTo')}${update.latestVersion ? ` · v${update.latestVersion}` : ''}`
|
|
515
|
-
: unknown
|
|
403
|
+
const note = checking
|
|
404
|
+
? t('card.checking')
|
|
405
|
+
: update === undefined || update === null
|
|
516
406
|
? t('card.unknown')
|
|
517
|
-
:
|
|
518
|
-
? t('card.
|
|
519
|
-
: update
|
|
520
|
-
? t('card.
|
|
521
|
-
: t('card.
|
|
407
|
+
: hasUpdate
|
|
408
|
+
? t('card.updateTo')
|
|
409
|
+
: update.status === 'disabled'
|
|
410
|
+
? t('card.checkOff')
|
|
411
|
+
: t('card.upToDate');
|
|
522
412
|
return h(
|
|
523
|
-
'
|
|
524
|
-
{
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
onClick: (event) => {
|
|
534
|
-
event.stopPropagation();
|
|
535
|
-
onCheck?.();
|
|
536
|
-
},
|
|
413
|
+
'button',
|
|
414
|
+
{
|
|
415
|
+
type: 'button',
|
|
416
|
+
className: 'loci-card-ver',
|
|
417
|
+
title: note,
|
|
418
|
+
'aria-label': note,
|
|
419
|
+
disabled: checking,
|
|
420
|
+
onClick: (event) => {
|
|
421
|
+
event.stopPropagation();
|
|
422
|
+
onCheck?.();
|
|
537
423
|
},
|
|
538
|
-
|
|
539
|
-
),
|
|
540
|
-
hasUpdate ? h('span', { className: 'loci-dot' }) : null,
|
|
424
|
+
},
|
|
425
|
+
version ? `v${version}` : t('card.unpublished'),
|
|
426
|
+
hasUpdate ? h('span', { className: 'loci-card-dot is-error', 'aria-hidden': true }) : null,
|
|
541
427
|
);
|
|
542
428
|
}
|
|
543
429
|
|
|
@@ -547,10 +433,12 @@ window.__ModuleLoader__.load({
|
|
|
547
433
|
const t = props?.t ?? ((key) => key);
|
|
548
434
|
const wide = props?.wide !== false;
|
|
549
435
|
const open = props?.openPanel;
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
|
|
436
|
+
// A compact readout, not a note: the counts are the reason a person looks at this
|
|
437
|
+
// strip at all. The earlier version showed one prose sentence and no numbers, which
|
|
438
|
+
// read as bare text inside the sidebar rather than as a card — and the audit's own
|
|
439
|
+
// "no dashboard" objection was about refusing to interrupt, not about hiding data.
|
|
440
|
+
const stats = useJson('/stats', { intervalMs: 30_000 });
|
|
441
|
+
const config = useJson('/config');
|
|
554
442
|
const update = useJson('/update');
|
|
555
443
|
const [checking, setChecking] = useState(false);
|
|
556
444
|
const [fresh, setFresh] = useState(undefined);
|
|
@@ -567,9 +455,19 @@ window.__ModuleLoader__.load({
|
|
|
567
455
|
}
|
|
568
456
|
}, []);
|
|
569
457
|
|
|
570
|
-
const data =
|
|
571
|
-
const
|
|
572
|
-
const
|
|
458
|
+
const data = stats.data;
|
|
459
|
+
const byStatus = data?.byStatus ?? {};
|
|
460
|
+
const verified = Number(byStatus.verified ?? 0);
|
|
461
|
+
const pending = Number(data?.pendingCandidates ?? byStatus.candidate ?? 0);
|
|
462
|
+
const disputed = Number(data?.disputed ?? byStatus.disputed ?? 0);
|
|
463
|
+
const entries = Number(data?.heads ?? 0);
|
|
464
|
+
const injection = data?.lastInjection ?? null;
|
|
465
|
+
const indexBytes = Number(injection?.bytes ?? 0);
|
|
466
|
+
const budget = Number(config.data?.config?.sectionMaxBytes ?? 0);
|
|
467
|
+
const usage = Array.isArray(data?.usage) ? data.usage : [];
|
|
468
|
+
const injected = usage.reduce((total, row) => total + Number(row.injections ?? 0), 0);
|
|
469
|
+
const opened = usage.reduce((total, row) => total + Number(row.reads ?? 0), 0);
|
|
470
|
+
const dead = usage.filter((row) => Number(row.injections ?? 0) > 0 && Number(row.reads ?? 0) === 0).length;
|
|
573
471
|
|
|
574
472
|
if (!wide) {
|
|
575
473
|
return h(
|
|
@@ -581,6 +479,7 @@ window.__ModuleLoader__.load({
|
|
|
581
479
|
'aria-label': t('card.open'),
|
|
582
480
|
onClick: () => open?.(),
|
|
583
481
|
},
|
|
482
|
+
h('style', null, CSS),
|
|
584
483
|
h('svg', { width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', 'aria-hidden': true },
|
|
585
484
|
h('path', { d: 'M4 6.5h16M4 12h16M4 17.5h10', stroke: 'currentColor', strokeWidth: 1.7, strokeLinecap: 'round' }),
|
|
586
485
|
h('circle', { cx: 18.5, cy: 17.5, r: 2.2, stroke: 'currentColor', strokeWidth: 1.5 }),
|
|
@@ -588,6 +487,16 @@ window.__ModuleLoader__.load({
|
|
|
588
487
|
);
|
|
589
488
|
}
|
|
590
489
|
|
|
490
|
+
const dotClass = stats.error !== undefined ? 'is-error' : pending > 0 || disputed > 0 ? 'is-warn' : '';
|
|
491
|
+
const stamp = data === undefined ? '' : stampOf(injection?.ts ?? data.generatedAt ?? Date.now());
|
|
492
|
+
const metric = (label, value, tone) =>
|
|
493
|
+
h(
|
|
494
|
+
'div',
|
|
495
|
+
{ className: 'loci-card-metric' },
|
|
496
|
+
h('span', { className: 'loci-card-metric-label' }, label),
|
|
497
|
+
h('span', { className: `loci-card-metric-value${tone ? ` ${tone}` : ''}` }, String(value)),
|
|
498
|
+
);
|
|
499
|
+
|
|
591
500
|
return h(
|
|
592
501
|
'div',
|
|
593
502
|
{
|
|
@@ -599,6 +508,7 @@ window.__ModuleLoader__.load({
|
|
|
599
508
|
if (event.key === 'Enter' || event.key === ' ') open?.();
|
|
600
509
|
},
|
|
601
510
|
},
|
|
511
|
+
h('style', null, CSS),
|
|
602
512
|
h(
|
|
603
513
|
'div',
|
|
604
514
|
{ className: 'loci-card-head' },
|
|
@@ -616,34 +526,56 @@ window.__ModuleLoader__.load({
|
|
|
616
526
|
),
|
|
617
527
|
),
|
|
618
528
|
h('span', { className: 'loci-card-title' }, t('card.title')),
|
|
619
|
-
h(
|
|
620
|
-
h(
|
|
529
|
+
h(VersionLabel, { t, update: fresh ?? update.data, onCheck: check, checking }),
|
|
530
|
+
h('span', { className: `loci-card-dot ${dotClass}`.trim(), 'aria-hidden': true }),
|
|
531
|
+
h('span', { className: 'loci-card-meta' }, stamp ? `${t('card.updated')} ${stamp}` : ''),
|
|
621
532
|
),
|
|
622
|
-
|
|
623
|
-
? h('div', { className: 'loci-card-empty' },
|
|
624
|
-
:
|
|
625
|
-
? h(
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
h('div', { className: 'loci-card-empty' }, t('card.empty')),
|
|
629
|
-
h('div', { className: 'loci-card-hint' }, t('card.emptyHint')),
|
|
630
|
-
)
|
|
631
|
-
: h(
|
|
632
|
-
'div',
|
|
633
|
-
{ className: 'loci-card-note' },
|
|
634
|
-
h(
|
|
533
|
+
stats.error !== undefined
|
|
534
|
+
? h('div', { className: 'loci-card-empty' }, stats.error)
|
|
535
|
+
: data === undefined
|
|
536
|
+
? h('div', { className: 'loci-card-empty' }, t('panel.loading'))
|
|
537
|
+
: entries === 0 && pending === 0 && disputed === 0
|
|
538
|
+
? h(
|
|
635
539
|
'div',
|
|
636
|
-
|
|
637
|
-
|
|
540
|
+
null,
|
|
541
|
+
h('div', { className: 'loci-card-empty' }, t('card.empty')),
|
|
542
|
+
h('div', { className: 'loci-card-hint' }, t('card.emptyHint')),
|
|
543
|
+
)
|
|
544
|
+
: h(
|
|
545
|
+
'div',
|
|
546
|
+
{ className: 'loci-card-grid' },
|
|
547
|
+
metric(t('card.verified'), verified),
|
|
548
|
+
metric(t('card.pending'), pending, pending > 0 ? 'is-warn' : ''),
|
|
549
|
+
metric(t('card.disputed'), disputed, disputed > 0 ? 'is-error' : ''),
|
|
638
550
|
),
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
551
|
+
data !== undefined && entries > 0 && budget > 0
|
|
552
|
+
? h(
|
|
553
|
+
'div',
|
|
554
|
+
{ className: 'loci-card-barline' },
|
|
555
|
+
h(
|
|
556
|
+
'div',
|
|
557
|
+
{ className: 'loci-card-bar', role: 'presentation' },
|
|
558
|
+
h('div', {
|
|
559
|
+
className: `loci-card-bar-fill${indexBytes / budget > 0.85 ? ' is-warn' : ''}`,
|
|
560
|
+
style: { width: `${Math.min(100, Math.round((indexBytes / budget) * 100))}%` },
|
|
561
|
+
}),
|
|
643
562
|
),
|
|
563
|
+
h('span', { className: 'loci-card-bar-label' }, `${indexBytes} / ${budget} B`),
|
|
564
|
+
)
|
|
565
|
+
: null,
|
|
566
|
+
data !== undefined && injected > 0
|
|
567
|
+
? h(
|
|
568
|
+
'div',
|
|
569
|
+
{ className: 'loci-card-foot' },
|
|
570
|
+
h('span', null, `${t('card.injected')} ${injected}`),
|
|
571
|
+
h('span', null, `${t('card.opened')} ${opened}`),
|
|
572
|
+
dead > 0 ? h('span', null, `${t('card.dead')} ${dead}`) : null,
|
|
573
|
+
h('span', { className: 'loci-card-foot-grow' }),
|
|
574
|
+
)
|
|
575
|
+
: null,
|
|
576
|
+
pending > 0 ? h('div', { className: 'loci-card-hint' }, t('card.pendingHint').replace('{n}', String(pending))) : null,
|
|
644
577
|
);
|
|
645
578
|
}
|
|
646
|
-
|
|
647
579
|
// ---------------------------------------------------------------- panel
|
|
648
580
|
|
|
649
581
|
function FeedTab({ t, onOpen }) {
|
|
@@ -1434,11 +1366,11 @@ window.__ModuleLoader__.load({
|
|
|
1434
1366
|
}
|
|
1435
1367
|
|
|
1436
1368
|
function apply(ctx) {
|
|
1437
|
-
ctx.effect(() => ctx.locale.register(NS, {
|
|
1369
|
+
ctx.effect(() => ctx.locale.register(NS, { en: EN }), 'dsh-loci: locale dictionaries');
|
|
1438
1370
|
const bind = ctx.locale.bind(NS);
|
|
1439
1371
|
const t = tOf(bind);
|
|
1440
1372
|
|
|
1441
|
-
// The
|
|
1373
|
+
// The sidebar mirror: a card beside Settings, always visible while the sidebar is.
|
|
1442
1374
|
ctx.slots.inject('sidebar.footer.action', () =>
|
|
1443
1375
|
ctx.slots.register(
|
|
1444
1376
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-loci",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Layered long-term memory for DeepSeek Harness: the prompt sees only a stable index, full text is one lookup away. Bi-temporal facts, strict scope isolation, verified-only injection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/host/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import { registerCommands } from './commands.js';
|
|
|
23
23
|
import { runDoctor } from './doctor.js';
|
|
24
24
|
|
|
25
25
|
/** Version stamped into the database and diagnostics. */
|
|
26
|
-
export const VERSION = '
|
|
26
|
+
export const VERSION = '3.0.1';
|
|
27
27
|
|
|
28
28
|
/** Optional collaborators; without them the plugin still serves tools. */
|
|
29
29
|
export const inject = ['systemPrompt', 'tools'];
|
package/src/mcp/store-facade.js
CHANGED