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/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
- /** An executable check compiled from a memory statement. */
21
- export interface Guard {
22
- command: string;
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
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Context memory",
4
+ "description": "Remembers what you decided across sessions, injects the relevant parts before each step, and re-checks the claims it can check."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "跨会话记忆",
4
+ "description": "记住你在不同会话里定过的事,每次回复前自动注入相关的那几条,并重新核对能核对的部分。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context-memory",
3
- "version": "0.7.1",
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.4",
66
- "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
67
- "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
68
- "@deepseek-ai/dsh-session": "0.2.0-rc.2"
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.4",
72
- "@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
73
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
74
- "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2",
75
- "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
76
- "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
77
- "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
78
- "@deepseek-ai/dsh-brand": "0.2.0-rc.2",
79
- "@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
80
- "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
81
- "@deepseek-ai/dsh-session": "0.2.0-rc.2",
82
- "@deepseek-ai/dsh-timeout": "0.2.0-rc.2",
83
- "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
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",