dsh-loci 3.1.0 → 3.2.0
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 +35 -0
- package/client.js +1 -0
- package/package.json +1 -1
- package/src/host/index.js +1 -1
- package/src/host/store/store.js +30 -6
- package/src/mcp/store-facade.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,41 @@ 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.2.0] — 2026-11-15
|
|
8
|
+
|
|
9
|
+
The sidebar was sending people to an empty room. Found in the live store, not in a test.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **"N item(s) waiting for you in Review" counted rows Review hides.** The card read
|
|
14
|
+
`stats.pendingCandidates`, which counted every candidate and disputed row in scope; the
|
|
15
|
+
Review tab lists only the kinds that can reach the prompt (`rule`, `semantic`,
|
|
16
|
+
`procedural`). Episodic session summaries are candidates *by design* — they are history,
|
|
17
|
+
not claims — so on a normal day the entire count was composed of rows Review deliberately
|
|
18
|
+
excludes.
|
|
19
|
+
|
|
20
|
+
Live evidence, after one afternoon of use: **3 pending rows, all `episodic`**, and the
|
|
21
|
+
sidebar offering to review them somewhere it would not. The count is now `0` and Review
|
|
22
|
+
shows `0`.
|
|
23
|
+
|
|
24
|
+
`REVIEWABLE_KINDS` is one exported list. `countPending`, `stats.pendingCandidates`,
|
|
25
|
+
`stats.disputed` and the panel's request all use it, so the badge and the list cannot drift
|
|
26
|
+
apart again. `byStatus` stays complete — that is a data view, not a to-do.
|
|
27
|
+
|
|
28
|
+
- **`stats.pendingCandidates` ignored `valid_to`** while `countPending` honoured it, so a
|
|
29
|
+
candidate closed by maintenance stayed in the badge. Both now exclude expired rows.
|
|
30
|
+
|
|
31
|
+
### Why this matters beyond the cosmetic
|
|
32
|
+
|
|
33
|
+
The card was telling the user to do something that could not be done, which reads as "the
|
|
34
|
+
tool is broken" rather than "there is nothing to do". A number with a door is a promise.
|
|
35
|
+
|
|
36
|
+
### Not fixed, recorded
|
|
37
|
+
|
|
38
|
+
Episodic summaries still accumulate as candidates and are only removed by the 30-day
|
|
39
|
+
candidate TTL. They are visible in the Feed tab, which is where history belongs, but nothing
|
|
40
|
+
says so. That is part of R24 (the panel explains almost nothing about itself).
|
|
41
|
+
|
|
7
42
|
## [3.1.0] — 2026-11-15
|
|
8
43
|
|
|
9
44
|
Four things from looking at the live sidebar and panel.
|
package/client.js
CHANGED
|
@@ -955,6 +955,7 @@ window.__ModuleLoader__.load({
|
|
|
955
955
|
// Only the kinds that could actually reach the prompt are reviewable. An
|
|
956
956
|
// episodic session summary is history: it is never injected, so parking it
|
|
957
957
|
// here forever is how the queue became a wall nobody reads.
|
|
958
|
+
// The same three kinds the store counts as pending, so the badge and the list agree.
|
|
958
959
|
const reviewable = 'kinds=rule,semantic,procedural';
|
|
959
960
|
const candidates = useJson(`/list?status=candidate&limit=50&${reviewable}`);
|
|
960
961
|
const disputed = useJson(`/list?status=disputed&limit=50&${reviewable}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-loci",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
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 = '3.
|
|
26
|
+
export const VERSION = '3.2.0';
|
|
27
27
|
|
|
28
28
|
/** Optional collaborators; without them the plugin still serves tools. */
|
|
29
29
|
export const inject = ['systemPrompt', 'tools'];
|
package/src/host/store/store.js
CHANGED
|
@@ -118,6 +118,13 @@ function tierOf(kind, pinned) {
|
|
|
118
118
|
/** Statuses that may be injected or returned by default. */
|
|
119
119
|
export const INJECTABLE_STATUSES = Object.freeze(['verified']);
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* The kinds a person can review. Everything else — episodic summaries, working notes — is
|
|
123
|
+
* either history or scratch, and Review hides it. One list, used by the counter and the UI,
|
|
124
|
+
* so the number and the list cannot drift apart again.
|
|
125
|
+
*/
|
|
126
|
+
export const REVIEWABLE_KINDS = Object.freeze(['rule', 'semantic', 'procedural']);
|
|
127
|
+
|
|
121
128
|
/** The phrase a caller must echo before the whole store is forgotten. */
|
|
122
129
|
export const CONFIRM_ALL = 'forget everything';
|
|
123
130
|
|
|
@@ -877,18 +884,25 @@ export class Store {
|
|
|
877
884
|
* @param scopeKeys - explicit scope keys (never a wildcard).
|
|
878
885
|
* @returns the count of candidate and disputed entries.
|
|
879
886
|
*/
|
|
880
|
-
countPending(scopeKeys,
|
|
887
|
+
countPending(scopeKeys, options = {}) {
|
|
881
888
|
if (scopeKeys.length === 0) return 0;
|
|
889
|
+
const now = options.now ?? this.now();
|
|
890
|
+
// Only rows a person can actually act on. Episodic summaries are candidates by design —
|
|
891
|
+
// they are history, not claims, and Review hides them — so counting them made the
|
|
892
|
+
// sidebar announce work with nowhere to do it.
|
|
893
|
+
const kinds = Array.isArray(options.kinds) && options.kinds.length > 0 ? options.kinds : REVIEWABLE_KINDS;
|
|
882
894
|
const marks = scopeKeys.map(() => '?').join(', ');
|
|
895
|
+
const kindMarks = kinds.map(() => '?').join(', ');
|
|
883
896
|
return Number(
|
|
884
897
|
this.db
|
|
885
898
|
.prepare(
|
|
886
899
|
`SELECT COUNT(*) AS n FROM memory m
|
|
887
900
|
WHERE m.status IN ('candidate', 'disputed') AND m.deleted_at IS NULL
|
|
888
901
|
AND (m.valid_to IS NULL OR m.valid_to > ?)
|
|
902
|
+
AND m.kind IN (${kindMarks})
|
|
889
903
|
AND m.scope_key IN (${marks})`,
|
|
890
904
|
)
|
|
891
|
-
.get(now, ...scopeKeys)?.n ?? 0,
|
|
905
|
+
.get(now, ...kinds, ...scopeKeys)?.n ?? 0,
|
|
892
906
|
);
|
|
893
907
|
}
|
|
894
908
|
|
|
@@ -1095,18 +1109,28 @@ export class Store {
|
|
|
1095
1109
|
.all(...keys)) {
|
|
1096
1110
|
byScope[row.scope] = Number(row.n);
|
|
1097
1111
|
}
|
|
1112
|
+
// "Pending" and "disputed" are questions for a person, so they count only the kinds a
|
|
1113
|
+
// person can act on. Episodic summaries are candidates by design and Review hides them;
|
|
1114
|
+
// counting them here made the sidebar announce work with nowhere to do it. The byStatus
|
|
1115
|
+
// view below stays complete, because that is data rather than a to-do.
|
|
1116
|
+
const kindMarks = REVIEWABLE_KINDS.map(() => '?').join(', ');
|
|
1098
1117
|
const pending = Number(
|
|
1099
1118
|
this.db
|
|
1100
1119
|
.prepare(
|
|
1101
1120
|
`SELECT COUNT(*) AS n FROM memory m JOIN memory_head h ON h.ord = m.ord
|
|
1102
|
-
WHERE h.status = 'candidate' AND m.deleted_at IS NULL${
|
|
1121
|
+
WHERE h.status = 'candidate' AND m.deleted_at IS NULL AND m.kind IN (${kindMarks})
|
|
1122
|
+
AND (m.valid_to IS NULL OR m.valid_to > ?)${marks}`,
|
|
1103
1123
|
)
|
|
1104
|
-
.get(...keys)?.n ?? 0,
|
|
1124
|
+
.get(...REVIEWABLE_KINDS, this.now(), ...keys)?.n ?? 0,
|
|
1105
1125
|
);
|
|
1106
1126
|
const disputed = Number(
|
|
1107
1127
|
this.db
|
|
1108
|
-
.prepare(
|
|
1109
|
-
|
|
1128
|
+
.prepare(
|
|
1129
|
+
`SELECT COUNT(*) AS n FROM memory m JOIN memory_head h ON h.ord = m.ord
|
|
1130
|
+
WHERE h.status = 'disputed' AND m.kind IN (${kindMarks})
|
|
1131
|
+
AND (m.valid_to IS NULL OR m.valid_to > ?)${marks}`,
|
|
1132
|
+
)
|
|
1133
|
+
.get(...REVIEWABLE_KINDS, this.now(), ...keys)?.n ?? 0,
|
|
1110
1134
|
);
|
|
1111
1135
|
const lastInjection = this.db
|
|
1112
1136
|
.prepare('SELECT ts, session_id, scope_key, section_bytes, entries_listed, entries_hidden, source FROM request_stat ORDER BY id DESC LIMIT 1')
|