dsh-context-memory 0.7.1 → 0.10.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 +312 -0
- package/README.md +151 -24
- package/icon.svg +16 -0
- package/lib/candidates.d.ts +71 -0
- package/lib/candidates.js +150 -0
- package/lib/capture.d.ts +21 -0
- package/lib/capture.js +86 -0
- package/lib/cli.js +88 -9
- package/lib/client/locales.d.ts +6 -0
- package/lib/client/locales.js +12 -0
- package/lib/client/view.d.ts +2 -0
- package/lib/client/view.js +18 -3
- package/lib/guard.d.ts +92 -30
- package/lib/guard.js +539 -73
- package/lib/identity.d.ts +16 -5
- package/lib/identity.js +36 -18
- package/lib/index.d.ts +35 -2
- package/lib/index.js +206 -16
- package/lib/memory.d.ts +131 -9
- package/lib/memory.js +296 -46
- package/lib/memory.web.js +48 -7
- package/lib/memory.web.js.map +2 -2
- package/lib/recall.d.ts +62 -8
- package/lib/recall.js +176 -16
- package/lib/routes.d.ts +1 -0
- package/lib/routes.js +50 -1
- package/lib/scope.d.ts +45 -0
- package/lib/scope.js +75 -0
- package/lib/store.d.ts +25 -2
- package/lib/store.js +45 -7
- package/lib/tools.d.ts +44 -0
- package/lib/tools.js +241 -0
- package/lib/types.d.ts +182 -3
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +25 -18
package/lib/tools.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing tools: the pull half of memory.
|
|
3
|
+
*
|
|
4
|
+
* Recall injection pushes what the plugin thinks is relevant. That is one direction only,
|
|
5
|
+
* and it leaves the model unable to ask the question it most often needs answered:
|
|
6
|
+
* "what did we already decide about this?" This module registers that question as two
|
|
7
|
+
* tools, so memory is something the model can consult rather than only something that
|
|
8
|
+
* arrives unasked.
|
|
9
|
+
*
|
|
10
|
+
* The two directions deliberately answer differently. The push path may only offer
|
|
11
|
+
* guidance — active and not contradicted — because unsolicited text influences the turn.
|
|
12
|
+
* The pull path is answering a question someone actually asked, so it can widen to
|
|
13
|
+
* contradicted or superseded records when asked to by name. The difference is carried by
|
|
14
|
+
* the `include` argument rather than by a second implementation, and `guidance` is still
|
|
15
|
+
* the default: a model that does not think about populations gets the same conservatism
|
|
16
|
+
* the push path applies.
|
|
17
|
+
*
|
|
18
|
+
* @module context-memory/tools
|
|
19
|
+
*/
|
|
20
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
21
|
+
import { describeContradiction, describeGuard } from './guard.js';
|
|
22
|
+
/** Populations a caller may search, in the order the schema lists them. */
|
|
23
|
+
const TARGETS = ['guidance', 'contradicted', 'superseded', 'all'];
|
|
24
|
+
/** Kinds a memory may be recorded as. Mirrors `MemoryKind`. */
|
|
25
|
+
const KINDS = ['fact', 'decision', 'preference', 'pitfall'];
|
|
26
|
+
/** Scopes a memory may be written to. Mirrors `MemoryScope`. */
|
|
27
|
+
const SCOPES = ['project', 'user'];
|
|
28
|
+
/** Bounds on one search, so a model cannot ask for the whole log by accident. */
|
|
29
|
+
const MIN_LIMIT = 1;
|
|
30
|
+
const MAX_LIMIT = 20;
|
|
31
|
+
const DEFAULT_LIMIT = 5;
|
|
32
|
+
/** Clamp a requested result count into the range one call may return. */
|
|
33
|
+
function clampLimit(requested) {
|
|
34
|
+
if (requested === undefined || !Number.isFinite(requested))
|
|
35
|
+
return DEFAULT_LIMIT;
|
|
36
|
+
const whole = Math.trunc(requested);
|
|
37
|
+
return Math.min(Math.max(whole, MIN_LIMIT), MAX_LIMIT);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The verification word for one memory, as the model sees it.
|
|
41
|
+
*
|
|
42
|
+
* `unverified` and `broken` are kept apart from `violated` for the same reason the guard
|
|
43
|
+
* module keeps them apart: a check that could not run is not evidence about the memory.
|
|
44
|
+
* Collapsing them into "failed" would teach the model to distrust true statements whose
|
|
45
|
+
* guard happens to be broken.
|
|
46
|
+
*/
|
|
47
|
+
function verificationOf(memory) {
|
|
48
|
+
const outcome = memory.verification?.outcome;
|
|
49
|
+
if (outcome === undefined)
|
|
50
|
+
return 'unverified';
|
|
51
|
+
return outcome;
|
|
52
|
+
}
|
|
53
|
+
/** The first line of a failed check, for a caller that wants to know why. */
|
|
54
|
+
function contradictionOf(memory) {
|
|
55
|
+
return describeContradiction(memory.guard, memory.verification?.output);
|
|
56
|
+
}
|
|
57
|
+
/** One result row in the tool's output shape. */
|
|
58
|
+
function hitOf(memory) {
|
|
59
|
+
return {
|
|
60
|
+
id: memory.id,
|
|
61
|
+
text: memory.text,
|
|
62
|
+
kind: memory.kind,
|
|
63
|
+
scope: memory.scope,
|
|
64
|
+
verification: verificationOf(memory),
|
|
65
|
+
// Only an actual contradiction gets a reason. `broken` says the check is unusable,
|
|
66
|
+
// and attaching its message here would read as evidence against the memory.
|
|
67
|
+
...(memory.verification?.outcome === 'violated' ? { detail: contradictionOf(memory) } : {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Register both tools, once the host's tool registry exists.
|
|
72
|
+
*
|
|
73
|
+
* Registered through `ctx.inject` rather than a static `inject` entry for the same reason
|
|
74
|
+
* the working directory is read through `ctx.get`: this plugin must keep working on a host
|
|
75
|
+
* that mounts no tool registry (the CLI, a headless profile without tools), and a declared
|
|
76
|
+
* dependency on `tools` would leave the whole plugin pending — including the pre-step
|
|
77
|
+
* injection and the panel — on such a host. The tools are an addition, not a precondition.
|
|
78
|
+
*/
|
|
79
|
+
export function registerMemoryTools(ctx, options) {
|
|
80
|
+
ctx.inject(['tools'], (toolCtx) => {
|
|
81
|
+
toolCtx.tools.register(defineTool({
|
|
82
|
+
name: 'memory_recall',
|
|
83
|
+
description: 'Search this project\'s stored memories (conventions, decisions, preferences, pitfalls recorded in earlier sessions) and return the strongest matches with their verification state. By default only memories that are active and not contradicted are returned; pass include to widen. Omit query to list the newest memories instead.',
|
|
84
|
+
parameters: {
|
|
85
|
+
query: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
description: 'What to look for. Latin words match as words, Chinese text matches on two-character runs. Omit to list the newest memories.',
|
|
88
|
+
},
|
|
89
|
+
limit: {
|
|
90
|
+
type: 'integer',
|
|
91
|
+
description: `How many memories to return. ${MIN_LIMIT}-${MAX_LIMIT}, default ${DEFAULT_LIMIT}.`,
|
|
92
|
+
},
|
|
93
|
+
include: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
enum: [...TARGETS],
|
|
96
|
+
description: 'Which memories to search: guidance (default) is active and not contradicted; contradicted is active but disproved by a check; superseded was retired; all is everything recorded.',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
output: {
|
|
100
|
+
schema: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
additionalProperties: false,
|
|
103
|
+
properties: {
|
|
104
|
+
hits: {
|
|
105
|
+
type: 'array',
|
|
106
|
+
required: true,
|
|
107
|
+
description: 'Matches, strongest first.',
|
|
108
|
+
items: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
additionalProperties: false,
|
|
111
|
+
properties: {
|
|
112
|
+
id: { type: 'string', required: true, description: 'Reference for this memory.' },
|
|
113
|
+
text: { type: 'string', required: true, description: 'The remembered statement.' },
|
|
114
|
+
kind: { type: 'string', required: true, description: 'fact, decision, preference, or pitfall.' },
|
|
115
|
+
scope: { type: 'string', required: true, description: 'project or user.' },
|
|
116
|
+
verification: { type: 'string', required: true, description: 'passed, violated, broken, or unverified.' },
|
|
117
|
+
detail: { type: 'string', description: 'Why a check contradicted it, when one did.' },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
include: { type: 'string', required: true, description: 'The population actually searched.' },
|
|
122
|
+
note: { type: 'string', description: 'Present when the search returned nothing, or when it fell back to newest-first.' },
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
render: (_args, value) => {
|
|
126
|
+
const lines = [];
|
|
127
|
+
if (value.hits.length === 0) {
|
|
128
|
+
lines.push(`No stored memory matched (searched: ${value.include}).`);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
lines.push(`${value.hits.length} memory${value.hits.length === 1 ? '' : 'ies'} (searched: ${value.include}):`);
|
|
132
|
+
for (const hit of value.hits) {
|
|
133
|
+
const detail = hit.detail === undefined ? '' : ` — contradicted by ${hit.detail}`;
|
|
134
|
+
lines.push(`- [${hit.kind}] ${hit.text} (id ${hit.id}, ${hit.scope}, ${hit.verification})${detail}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (value.note !== undefined)
|
|
138
|
+
lines.push('', value.note);
|
|
139
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
isConcurrencySafe: () => true,
|
|
143
|
+
async execute(args, exec) {
|
|
144
|
+
const memory = await options.resolve(exec.agent, exec.signal);
|
|
145
|
+
const target = (args.include ?? 'guidance');
|
|
146
|
+
const limit = clampLimit(args.limit);
|
|
147
|
+
const query = args.query?.trim() ?? '';
|
|
148
|
+
// An empty query is a question about nothing, and a lexical search would answer it
|
|
149
|
+
// with nothing. "What do you remember" is a real question, so it gets the newest
|
|
150
|
+
// entries of the requested population instead.
|
|
151
|
+
if (query.length === 0) {
|
|
152
|
+
const hits = memory.recent(limit, target);
|
|
153
|
+
return {
|
|
154
|
+
hits: hits.map(hitOf),
|
|
155
|
+
include: target,
|
|
156
|
+
...(hits.length > 0 ? { note: 'No query was given, so these are the newest memories rather than the most relevant.' } : {}),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
hits: memory.recall(query, limit, Date.now(), target).map((hit) => hitOf(hit.memory)),
|
|
161
|
+
include: target,
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
}));
|
|
165
|
+
toolCtx.tools.register(defineTool({
|
|
166
|
+
name: 'memory_remember',
|
|
167
|
+
description: 'Record a durable memory for this project or for the user, so later sessions see it. Use it for conventions, decisions, preferences, and pitfalls the user states or that the work establishes — not for transient task state. The statement is stored as text; only a few narrow statement shapes get a filesystem check that can later verify or contradict it.',
|
|
168
|
+
parameters: {
|
|
169
|
+
text: {
|
|
170
|
+
type: 'string',
|
|
171
|
+
required: true,
|
|
172
|
+
description: 'The statement to remember, in the user\'s own words. One claim per call.',
|
|
173
|
+
},
|
|
174
|
+
kind: {
|
|
175
|
+
type: 'string',
|
|
176
|
+
enum: [...KINDS],
|
|
177
|
+
description: 'fact (default), decision, preference, or pitfall.',
|
|
178
|
+
},
|
|
179
|
+
subject: {
|
|
180
|
+
type: 'string',
|
|
181
|
+
description: 'What the claim is about. Two different statements with the same subject are reported as a conflict; omit to use the statement itself.',
|
|
182
|
+
},
|
|
183
|
+
scope: {
|
|
184
|
+
type: 'string',
|
|
185
|
+
enum: [...SCOPES],
|
|
186
|
+
description: 'project (default) travels with this checkout; user applies everywhere this user works.',
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
output: {
|
|
190
|
+
schema: {
|
|
191
|
+
type: 'object',
|
|
192
|
+
additionalProperties: false,
|
|
193
|
+
properties: {
|
|
194
|
+
id: { type: 'string', required: true, description: 'Reference for the recorded memory.' },
|
|
195
|
+
text: { type: 'string', required: true, description: 'The statement as stored (trimmed).' },
|
|
196
|
+
kind: { type: 'string', required: true, description: 'The kind it was recorded as.' },
|
|
197
|
+
scope: { type: 'string', required: true, description: 'Which log it was written to.' },
|
|
198
|
+
check: { type: 'string', description: 'The filesystem check that will re-verify it. Absent when none could be compiled.' },
|
|
199
|
+
updated: { type: 'boolean', required: true, description: 'True when this statement was already recorded and only its timestamp was refreshed.' },
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
render: (_args, value) => [{
|
|
203
|
+
type: 'text',
|
|
204
|
+
text: [
|
|
205
|
+
`${value.updated ? 'Already recorded' : 'Remembered'} ${value.id} [${value.scope}, ${value.kind}]: ${value.text}`,
|
|
206
|
+
value.check === undefined
|
|
207
|
+
? 'No check could be compiled from this statement, so nothing will re-verify it.'
|
|
208
|
+
: `Check: ${value.check}.`,
|
|
209
|
+
].join('\n'),
|
|
210
|
+
}],
|
|
211
|
+
},
|
|
212
|
+
// Writes append one line to a file. Two of them commute, and the registry already
|
|
213
|
+
// serializes nothing else about them, so they may share a parallel group.
|
|
214
|
+
isConcurrencySafe: () => true,
|
|
215
|
+
async execute(args, exec) {
|
|
216
|
+
const memory = await options.resolve(exec.agent, exec.signal);
|
|
217
|
+
const kind = (args.kind ?? 'fact');
|
|
218
|
+
const scope = (args.scope ?? 'project');
|
|
219
|
+
const { memory: stored, updated } = memory.record({
|
|
220
|
+
text: args.text,
|
|
221
|
+
kind,
|
|
222
|
+
scope,
|
|
223
|
+
// Provenance is not decoration: the panel distinguishes what the user said from
|
|
224
|
+
// what the model decided to write down, and a memory is only as trustworthy as
|
|
225
|
+
// its source is.
|
|
226
|
+
source: 'agent',
|
|
227
|
+
...(args.subject === undefined || args.subject.trim().length === 0 ? {} : { subject: args.subject }),
|
|
228
|
+
...(exec.agent === undefined ? {} : { sessionId: exec.agent.id }),
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
id: stored.id,
|
|
232
|
+
text: stored.text,
|
|
233
|
+
kind: stored.kind,
|
|
234
|
+
scope,
|
|
235
|
+
...(stored.guard === undefined ? {} : { check: describeGuard(stored.guard) }),
|
|
236
|
+
updated,
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
}));
|
|
240
|
+
});
|
|
241
|
+
}
|
package/lib/types.d.ts
CHANGED
|
@@ -13,15 +13,129 @@
|
|
|
13
13
|
export type MemoryKind = 'fact' | 'decision' | 'preference' | 'pitfall';
|
|
14
14
|
/** Who wrote the memory. Provenance, so "why does the agent think it knows this" has an answer. */
|
|
15
15
|
export type MemorySource = 'user' | 'agent' | 'plugin';
|
|
16
|
+
/**
|
|
17
|
+
* Where the memory lives.
|
|
18
|
+
*
|
|
19
|
+
* `project` is a convention of one checkout; `user` is a working preference that holds
|
|
20
|
+
* everywhere. The distinction is scope, not importance — both are equally worth
|
|
21
|
+
* recalling, but they belong to different populations and must not leak into each other.
|
|
22
|
+
*/
|
|
23
|
+
export type MemoryScope = 'project' | 'user';
|
|
16
24
|
/** A memory is active until something explicitly supersedes it. */
|
|
17
25
|
export type MemoryStatus = 'active' | 'superseded';
|
|
26
|
+
/**
|
|
27
|
+
* Which populations a search may draw from.
|
|
28
|
+
*
|
|
29
|
+
* `guidance` is the only one the push path may use: active and not contradicted. The
|
|
30
|
+
* others exist for questions that are *about* the log rather than for guidance —
|
|
31
|
+
* "what did we decide before", "what did the check disprove" — which only a reader who
|
|
32
|
+
* asked for them by name should receive.
|
|
33
|
+
*/
|
|
34
|
+
export type MemoryTarget = 'guidance' | 'contradicted' | 'superseded' | 'all';
|
|
18
35
|
/** The same three-outcome model dsh-verdict uses: inability to check is not failure. */
|
|
19
36
|
export type GuardOutcome = 'passed' | 'violated' | 'broken';
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
/**
|
|
38
|
+
* An executable check compiled from a memory statement.
|
|
39
|
+
*
|
|
40
|
+
* A check names one predicate and its operands — never a program. That is what keeps
|
|
41
|
+
* memory text from becoming something to execute: the statement contributes paths,
|
|
42
|
+
* keys, and search strings, and the evaluation is `fs` plus `JSON.parse`, not a shell.
|
|
43
|
+
*
|
|
44
|
+
* The `kind` discriminates the predicate. Check `kind` before reading an operand: the
|
|
45
|
+
* operands each kind needs differ, so a field that exists on one is absent on another.
|
|
46
|
+
*
|
|
47
|
+
* The file predicates (`exists` / `absent` / `contains` / `lacks`) answer "is this text in
|
|
48
|
+
* this file". The structured predicates (`depends` / `script` / `option` / `lineLimit`)
|
|
49
|
+
* answer questions about a *parsed* file, which is a different thing: `contains` would say
|
|
50
|
+
* `"yarn"` is in `package.json` because the word appears in a comment, a description, or
|
|
51
|
+
* inside some other key. Parsing is still in-process and still has no program in it.
|
|
52
|
+
*/
|
|
53
|
+
export type Guard = {
|
|
54
|
+
kind: 'exists';
|
|
55
|
+
path: string;
|
|
56
|
+
source: 'template';
|
|
57
|
+
} | {
|
|
58
|
+
kind: 'absent';
|
|
59
|
+
path: string;
|
|
60
|
+
source: 'template';
|
|
61
|
+
} | {
|
|
62
|
+
kind: 'contains';
|
|
63
|
+
path: string;
|
|
64
|
+
needle: string;
|
|
65
|
+
source: 'template';
|
|
66
|
+
} | {
|
|
67
|
+
kind: 'lacks';
|
|
68
|
+
path: string;
|
|
69
|
+
needle: string;
|
|
23
70
|
source: 'template';
|
|
24
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* `package.json` declares `name` in any of `dependencies` / `devDependencies` /
|
|
74
|
+
* `peerDependencies` / `optionalDependencies`. All four count: a memory saying "we use
|
|
75
|
+
* X" is not wrong because X arrived as a peer.
|
|
76
|
+
*/
|
|
77
|
+
| {
|
|
78
|
+
kind: 'depends';
|
|
79
|
+
name: string;
|
|
80
|
+
source: 'template';
|
|
81
|
+
absent?: false;
|
|
82
|
+
}
|
|
83
|
+
/** `package.json` declares no such dependency. */
|
|
84
|
+
| {
|
|
85
|
+
kind: 'depends';
|
|
86
|
+
name: string;
|
|
87
|
+
source: 'template';
|
|
88
|
+
absent: true;
|
|
89
|
+
}
|
|
90
|
+
/** `package.json` has a `scripts` entry `name`, and `cmd` (when given) is its value. */
|
|
91
|
+
| {
|
|
92
|
+
kind: 'script';
|
|
93
|
+
name: string;
|
|
94
|
+
cmd?: string;
|
|
95
|
+
source: 'template';
|
|
96
|
+
absent?: false;
|
|
97
|
+
}
|
|
98
|
+
/** `package.json` has no such script. */
|
|
99
|
+
| {
|
|
100
|
+
kind: 'script';
|
|
101
|
+
name: string;
|
|
102
|
+
source: 'template';
|
|
103
|
+
absent: true;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A JSON config file sets `option`, and `value` (when given) is what it is set to.
|
|
107
|
+
*
|
|
108
|
+
* `value: true` plus `absent: true` is the negation of "the option is on" — it is
|
|
109
|
+
* satisfied by the key being absent *and* by it being explicitly `false`, because
|
|
110
|
+
* "strict is not enabled" is true in both cases.
|
|
111
|
+
*/
|
|
112
|
+
| {
|
|
113
|
+
kind: 'option';
|
|
114
|
+
path: string;
|
|
115
|
+
option: string;
|
|
116
|
+
value?: boolean;
|
|
117
|
+
source: 'template';
|
|
118
|
+
absent?: false;
|
|
119
|
+
}
|
|
120
|
+
/** The same config option is not set to `value` (or is not set at all). */
|
|
121
|
+
| {
|
|
122
|
+
kind: 'option';
|
|
123
|
+
path: string;
|
|
124
|
+
option: string;
|
|
125
|
+
value?: boolean;
|
|
126
|
+
source: 'template';
|
|
127
|
+
absent: true;
|
|
128
|
+
}
|
|
129
|
+
/** The file has at most `max` lines, or at least `min`. */
|
|
130
|
+
| {
|
|
131
|
+
kind: 'lineLimit';
|
|
132
|
+
path: string;
|
|
133
|
+
max?: number;
|
|
134
|
+
min?: number;
|
|
135
|
+
source: 'template';
|
|
136
|
+
};
|
|
137
|
+
/** The predicates above, as a value to switch on. */
|
|
138
|
+
export type GuardKind = Guard['kind'];
|
|
25
139
|
/** The immutable payload recorded by a `remember` event. */
|
|
26
140
|
export interface Memory {
|
|
27
141
|
id: string;
|
|
@@ -30,6 +144,13 @@ export interface Memory {
|
|
|
30
144
|
text: string;
|
|
31
145
|
kind: MemoryKind;
|
|
32
146
|
source: MemorySource;
|
|
147
|
+
/**
|
|
148
|
+
* Which log this memory was written to.
|
|
149
|
+
*
|
|
150
|
+
* Absent in logs written before 0.9.0, which were project-only by construction; the
|
|
151
|
+
* fold reads a missing scope as `project`, since that is what those logs meant.
|
|
152
|
+
*/
|
|
153
|
+
scope?: MemoryScope;
|
|
33
154
|
createdAt: number;
|
|
34
155
|
sessionId?: string;
|
|
35
156
|
step?: number;
|
|
@@ -37,6 +158,8 @@ export interface Memory {
|
|
|
37
158
|
}
|
|
38
159
|
/** A memory folded together with every later event about it. */
|
|
39
160
|
export interface MemoryState extends Memory {
|
|
161
|
+
/** Always known here: the fold derives it from the log the memory was read out of. */
|
|
162
|
+
scope: MemoryScope;
|
|
40
163
|
status: MemoryStatus;
|
|
41
164
|
supersededReason?: string;
|
|
42
165
|
supersededBy?: string;
|
|
@@ -83,10 +206,47 @@ export interface Conflict {
|
|
|
83
206
|
subject: string;
|
|
84
207
|
memories: MemoryState[];
|
|
85
208
|
}
|
|
209
|
+
/** A statement the capture heuristics noticed but did not have permission to keep. */
|
|
210
|
+
export interface Candidate {
|
|
211
|
+
id: string;
|
|
212
|
+
text: string;
|
|
213
|
+
kind: MemoryKind;
|
|
214
|
+
proposedAt: number;
|
|
215
|
+
sessionId?: string;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Where a candidate stands.
|
|
219
|
+
*
|
|
220
|
+
* `dismissed` is kept rather than deleted: the question was asked and answered, and a
|
|
221
|
+
* queue that forgets the answer asks it again on the next step.
|
|
222
|
+
*/
|
|
223
|
+
export type CandidateStatus = 'pending' | 'accepted' | 'dismissed';
|
|
224
|
+
export interface CandidateState extends Candidate {
|
|
225
|
+
status: CandidateStatus;
|
|
226
|
+
}
|
|
227
|
+
/** The candidate log, folded the same way the memory log is. */
|
|
228
|
+
export type CandidateEvent = {
|
|
229
|
+
type: 'propose';
|
|
230
|
+
at: number;
|
|
231
|
+
candidate: Candidate;
|
|
232
|
+
} | {
|
|
233
|
+
type: 'decide';
|
|
234
|
+
at: number;
|
|
235
|
+
id: string;
|
|
236
|
+
status: Exclude<CandidateStatus, 'pending'>;
|
|
237
|
+
};
|
|
86
238
|
/** One verification result per memory that was considered. */
|
|
87
239
|
export interface VerifyResult {
|
|
88
240
|
id: string;
|
|
89
241
|
text: string;
|
|
242
|
+
/**
|
|
243
|
+
* The check that produced this outcome, when there was one.
|
|
244
|
+
*
|
|
245
|
+
* Reported rather than left to the caller to look up: `verify` already holds the guard
|
|
246
|
+
* it just ran, and a caller that wanted to describe the outcome would otherwise fold
|
|
247
|
+
* the whole log once per result to fetch what it was already handed.
|
|
248
|
+
*/
|
|
249
|
+
guard?: Guard;
|
|
90
250
|
/** `uncheckable` means no guard could be compiled — not that the memory is wrong. */
|
|
91
251
|
outcome: GuardOutcome | 'uncheckable';
|
|
92
252
|
exitCode: number | null;
|
|
@@ -103,12 +263,31 @@ export interface VerifySummary {
|
|
|
103
263
|
export interface PanelPayload {
|
|
104
264
|
active: MemoryState[];
|
|
105
265
|
superseded: MemoryState[];
|
|
266
|
+
/**
|
|
267
|
+
* Active memories a check contradicted.
|
|
268
|
+
*
|
|
269
|
+
* Listed apart from `active` because they are a different kind of statement: still
|
|
270
|
+
* recorded, still visible, but no longer offered as guidance. The panel must show them
|
|
271
|
+
* — a memory the world disproved is the most informative record in the log — while the
|
|
272
|
+
* injection path must not.
|
|
273
|
+
*/
|
|
274
|
+
contradicted: MemoryState[];
|
|
106
275
|
conflicts: Conflict[];
|
|
276
|
+
/**
|
|
277
|
+
* Statements the capture heuristics noticed but did not have permission to keep.
|
|
278
|
+
*
|
|
279
|
+
* In the panel payload rather than folded into `active`, because they are a question
|
|
280
|
+
* for the user, not a memory: a candidate has no scope, no guard, and no claim on
|
|
281
|
+
* recall until someone accepts it.
|
|
282
|
+
*/
|
|
283
|
+
candidates: CandidateState[];
|
|
107
284
|
totals: {
|
|
108
285
|
active: number;
|
|
109
286
|
superseded: number;
|
|
287
|
+
contradicted: number;
|
|
110
288
|
withGuard: number;
|
|
111
289
|
verified: number;
|
|
112
290
|
unverified: number;
|
|
291
|
+
candidates: number;
|
|
113
292
|
};
|
|
114
293
|
}
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Cross-session memory for your dsh agent: remembered, recalled, and re-verified instead of assumed.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -17,14 +17,19 @@
|
|
|
17
17
|
"types": "./lib/client/index.d.ts",
|
|
18
18
|
"default": "./lib/memory.web.js"
|
|
19
19
|
},
|
|
20
|
+
"./locale/*.json": "./locale/*.json",
|
|
21
|
+
"./icon": "./icon.svg",
|
|
20
22
|
"./package.json": "./package.json"
|
|
21
23
|
},
|
|
22
24
|
"files": [
|
|
23
25
|
"lib",
|
|
24
26
|
"cordis.patch.yml",
|
|
27
|
+
"locale/*.json",
|
|
28
|
+
"icon.svg",
|
|
25
29
|
"README.md",
|
|
26
30
|
"CHANGELOG.md"
|
|
27
31
|
],
|
|
32
|
+
"icon": "./icon.svg",
|
|
28
33
|
"keywords": [
|
|
29
34
|
"dsh",
|
|
30
35
|
"dsh-plugin",
|
|
@@ -62,25 +67,27 @@
|
|
|
62
67
|
"node": ">=20"
|
|
63
68
|
},
|
|
64
69
|
"peerDependencies": {
|
|
65
|
-
"@deepseek-ai/cordis": "~4.0.
|
|
66
|
-
"@deepseek-ai/dsh-agent": "0.2.
|
|
67
|
-
"@deepseek-ai/dsh-
|
|
68
|
-
"@deepseek-ai/dsh-
|
|
70
|
+
"@deepseek-ai/cordis": "~4.0.5",
|
|
71
|
+
"@deepseek-ai/dsh-agent": "^0.2.1-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-home-paths": "^0.2.1-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-llm": "^0.2.1-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-session": "^0.2.1-rc.2",
|
|
75
|
+
"@deepseek-ai/dsh-tools": "^0.2.1-rc.2"
|
|
69
76
|
},
|
|
70
77
|
"devDependencies": {
|
|
71
|
-
"@deepseek-ai/cordis": "~4.0.
|
|
72
|
-
"@deepseek-ai/dsh-
|
|
73
|
-
"@deepseek-ai/dsh-client-
|
|
74
|
-
"@deepseek-ai/dsh-client-ui-
|
|
75
|
-
"@deepseek-ai/dsh-client-ui-
|
|
76
|
-
"@deepseek-ai/dsh-
|
|
77
|
-
"@deepseek-ai/dsh-
|
|
78
|
-
"@deepseek-ai/dsh-
|
|
79
|
-
"@deepseek-ai/dsh-
|
|
80
|
-
"@deepseek-ai/dsh-
|
|
81
|
-
"@deepseek-ai/dsh-session": "0.2.
|
|
82
|
-
"@deepseek-ai/dsh-
|
|
83
|
-
"@deepseek-ai/dsh-
|
|
78
|
+
"@deepseek-ai/cordis": "~4.0.5",
|
|
79
|
+
"@deepseek-ai/dsh-agent": "0.2.1-rc.2",
|
|
80
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.2",
|
|
81
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.2",
|
|
82
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.2",
|
|
83
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.2",
|
|
84
|
+
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.2",
|
|
85
|
+
"@deepseek-ai/dsh-llm": "0.2.1-rc.2",
|
|
86
|
+
"@deepseek-ai/dsh-sandbox": "0.2.1-rc.2",
|
|
87
|
+
"@deepseek-ai/dsh-scope": "0.2.1-rc.2",
|
|
88
|
+
"@deepseek-ai/dsh-session": "0.2.1-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-tools": "0.2.1-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-working-directory": "0.2.1-rc.2",
|
|
84
91
|
"@types/node": "^22.20.3",
|
|
85
92
|
"@types/react": "^19.3.0",
|
|
86
93
|
"esbuild": "^0.28.2",
|