dsh-context-memory 0.6.1 → 0.7.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 CHANGED
@@ -1,106 +1,120 @@
1
- # Changelog
2
-
3
- ## 0.5.1
4
-
5
- **Fixed: the previous release only covered `ui.tsx`.**
6
-
7
- The 0.5.0 fix replaced the invented tokens in `ui.tsx` but missed
8
- `view.tsx`, which still read `--error`, `--warning`, `--success` and
9
- `--accent` with hardcoded fallbacks. dsh defines none of those, so the
10
- fallbacks applied in every theme.
11
-
12
- Fixed here, 17 sites across the seven plugin panels:
13
-
14
- - `var(--error, #e53935)` → `var(--dsw-alias-state-error-primary)`
15
- - `var(--warning, #ed6c02)` → `var(--dsw-alias-state-warn-primary)`
16
- - `var(--success, #2e7d32)` → `var(--dsw-alias-state-success-primary)`
17
- - `var(--accent, #4B8BBE)` → `var(--dsw-alias-brand-primary)`
18
-
19
- Also replaced literal colours in the same files (alert card tints, dimmed text,
20
- code-block background) with the matching dsh tokens.
21
-
22
- The guard test now scans **every `.ts`/`.tsx` under `src/client/`** instead of
23
- only `ui.tsx` — that narrow scope is exactly how these 17 sites slipped through.
24
- An added assertion fails if `view.tsx` stops being scanned.
25
-
26
- ## 0.5.0
27
-
28
- **Fixed: panels did not follow the host theme.**
29
-
30
- ui.tsx read invented custom properties — `--accent`, `--border`, `--bg-primary`,
31
- `--text-primary` and friends. dsh defines none of them, so the hardcoded
32
- fallbacks applied in every theme: each panel rendered its own fixed palette
33
- instead of following the host.
34
-
35
- The visible symptom was `surface: 'var(--bg-primary, #fff)'` on the modal,
36
- input, select and textarea backgrounds — **white panels in dark mode**, with text
37
- in the host's near-white label color.
38
-
39
- Every name is now a real dsh token, pinned in `tests/ui-tokens.test.mjs`:
40
-
41
- - accent → `--dsw-alias-brand-primary`
42
- - surfaces → `--dsw-alias-bg-layer-1` / `-2`
43
- - border → `--dsw-alias-border-l2`
44
- - text / muted → `--dsw-alias-label-primary` / `--dsw-alias-label-tertiary`
45
- - state colors → `--dsw-alias-state-*-primary` with `-tertiary` / `-secondary` tints
46
- - modal scrim → `--dsw-alias-bg-mask-1`
47
- - shadows → `--dsw-elevation-panel` / `--dsw-elevation-prominent`
48
-
49
- `#fff` on the primary button became `--dsw-alias-label-primary-inverted`: dsh's
50
- brand color is near-black in light mode and near-white in dark, so the literal
51
- white would have disappeared against the fill.
52
-
53
- Fallbacks are gone on purpose. If a token were ever missing, the declaration
54
- becomes invalid at computed-value time and the property inherits, which degrades
55
- gracefully — a hardcoded fallback instead bakes in a color that is wrong in one
56
- of the two themes. The guard test fails on any fallback for exactly that reason.
57
-
58
- ## 0.2.0
59
-
60
- Ecosystem sync: every plugin in this suite shares one version, so a version number
61
- identifies a set that was tested together rather than one plugin's own history.
62
-
63
- - Fix the Model Arena panel rendering "Cannot read properties of undefined": the host
64
- route returned the bare run list while the view reads `payload.recentRuns`.
65
- - The client `inject` field now names services (`slots`, `connection`) instead of the
66
- packages that provide them.
67
- - README badges cover version, downloads, CI, license, Node requirement, stars, and the
68
- dsh plugin topic.
69
-
70
- ## 0.1.2
71
-
72
- - **Fix the client half never activating.** The browser half declared package names
73
- (`@deepseek-ai/dsh-client-ui-settings`) as cordis services, but services are named by
74
- their runtime identity — the settings shell is `slots`, the host bridge is
75
- `connection`. The fiber therefore never resolved and the plugin stayed pending, which
76
- the loader reported as "did not activate". It now injects `slots` and registers into
77
- the `settings.section` slot the way the official client artifacts do.
78
-
79
- ## 0.1.1
80
-
81
- - **Fix the browser half never registering.** The client bundle was emitted as plain ESM
82
- with `react` external, which prints a top-level `import` — invalid inside the loader's
83
- concatenated script bundle, so evaluation aborted and every module after it failed with
84
- "...loaded without registering ... via __ModuleLoader__.load". The bundle is now emitted
85
- in the loader's lazy-CJS factory form (`window.__ModuleLoader__.load({ id, factory })`).
86
- - Point the `./client` export's `types` at `lib/client/index.d.ts`.
87
-
88
- ## 0.1.0
89
-
90
- Initial release.
91
-
92
- - **Remember** — append-only `.memory/events.jsonl`; every memory carries provenance
93
- (session, step, author) so the question "why does the agent think it knows this" has
94
- an answer.
95
- - **Recall** — relevance-ranked retrieval (token overlap, recency, verification
96
- strength) instead of dumping every stored memory into context.
97
- - **Forget with a reason** — a memory is superseded, never silently deleted, and the
98
- supersede event keeps the reason on the record.
99
- - **Conflict detection** — two active memories about the same subject are reported as a
100
- conflict; the plugin does not pick a winner for you.
101
- - **Verification** — a checkable memory is compiled into a platform-aware guard and
102
- re-checked on demand. `dsh-context-memory verify` exits non-zero only for a memory
103
- whose guard ran and failed; a guard that cannot run is reported separately and is
104
- never counted as a broken memory.
105
- - **Panel** — a `Context memory` settings page listing memory, verification status,
106
- conflicts, and provenance.
1
+ # Changelog
2
+
3
+ ## 0.7.0
4
+
5
+ **dsh 0.2.0-rc.2 alignment: host-provided packages move to pinned peer dependencies.**
6
+
7
+ Every `@deepseek-ai/*` package the host supplies — `dsh-llm`, `dsh-agent`, `dsh-session`,
8
+ `dsh-tools` and friends — is now declared in `peerDependencies`, pinned to `0.2.0-rc.2`, with
9
+ `@deepseek-ai/cordis` at `~4.0.4`. That is how the official plugins declare them. The previous
10
+ declarations were older ranges in the wrong section, which let a stale core copy be installed
11
+ into a dsh profile and stop the host from starting.
12
+
13
+ The recall message this plugin injects now declares its own `MessageSourceMap` kind
14
+ (`dsh-context-memory`, form `recall`). 0.2.0-rc.2 removed the shared catch-all `plugin` kind:
15
+ a producer declares its own kind by augmenting the map in its own module, as the official
16
+ `dsh-agent-loop` does for `runtime-context`.
17
+ ## 0.5.1
18
+
19
+ **Fixed: the previous release only covered `ui.tsx`.**
20
+
21
+ The 0.5.0 fix replaced the invented tokens in `ui.tsx` but missed
22
+ `view.tsx`, which still read `--error`, `--warning`, `--success` and
23
+ `--accent` with hardcoded fallbacks. dsh defines none of those, so the
24
+ fallbacks applied in every theme.
25
+
26
+ Fixed here, 17 sites across the seven plugin panels:
27
+
28
+ - `var(--error, #e53935)` → `var(--dsw-alias-state-error-primary)`
29
+ - `var(--warning, #ed6c02)` → `var(--dsw-alias-state-warn-primary)`
30
+ - `var(--success, #2e7d32)` → `var(--dsw-alias-state-success-primary)`
31
+ - `var(--accent, #4B8BBE)` → `var(--dsw-alias-brand-primary)`
32
+
33
+ Also replaced literal colours in the same files (alert card tints, dimmed text,
34
+ code-block background) with the matching dsh tokens.
35
+
36
+ The guard test now scans **every `.ts`/`.tsx` under `src/client/`** instead of
37
+ only `ui.tsx` — that narrow scope is exactly how these 17 sites slipped through.
38
+ An added assertion fails if `view.tsx` stops being scanned.
39
+
40
+ ## 0.5.0
41
+
42
+ **Fixed: panels did not follow the host theme.**
43
+
44
+ ui.tsx read invented custom properties — `--accent`, `--border`, `--bg-primary`,
45
+ `--text-primary` and friends. dsh defines none of them, so the hardcoded
46
+ fallbacks applied in every theme: each panel rendered its own fixed palette
47
+ instead of following the host.
48
+
49
+ The visible symptom was `surface: 'var(--bg-primary, #fff)'` on the modal,
50
+ input, select and textarea backgrounds — **white panels in dark mode**, with text
51
+ in the host's near-white label color.
52
+
53
+ Every name is now a real dsh token, pinned in `tests/ui-tokens.test.mjs`:
54
+
55
+ - accent → `--dsw-alias-brand-primary`
56
+ - surfaces → `--dsw-alias-bg-layer-1` / `-2`
57
+ - border → `--dsw-alias-border-l2`
58
+ - text / muted → `--dsw-alias-label-primary` / `--dsw-alias-label-tertiary`
59
+ - state colors → `--dsw-alias-state-*-primary` with `-tertiary` / `-secondary` tints
60
+ - modal scrim → `--dsw-alias-bg-mask-1`
61
+ - shadows → `--dsw-elevation-panel` / `--dsw-elevation-prominent`
62
+
63
+ `#fff` on the primary button became `--dsw-alias-label-primary-inverted`: dsh's
64
+ brand color is near-black in light mode and near-white in dark, so the literal
65
+ white would have disappeared against the fill.
66
+
67
+ Fallbacks are gone on purpose. If a token were ever missing, the declaration
68
+ becomes invalid at computed-value time and the property inherits, which degrades
69
+ gracefully — a hardcoded fallback instead bakes in a color that is wrong in one
70
+ of the two themes. The guard test fails on any fallback for exactly that reason.
71
+
72
+ ## 0.2.0
73
+
74
+ Ecosystem sync: every plugin in this suite shares one version, so a version number
75
+ identifies a set that was tested together rather than one plugin's own history.
76
+
77
+ - Fix the Model Arena panel rendering "Cannot read properties of undefined": the host
78
+ route returned the bare run list while the view reads `payload.recentRuns`.
79
+ - The client `inject` field now names services (`slots`, `connection`) instead of the
80
+ packages that provide them.
81
+ - README badges cover version, downloads, CI, license, Node requirement, stars, and the
82
+ dsh plugin topic.
83
+
84
+ ## 0.1.2
85
+
86
+ - **Fix the client half never activating.** The browser half declared package names
87
+ (`@deepseek-ai/dsh-client-ui-settings`) as cordis services, but services are named by
88
+ their runtime identity — the settings shell is `slots`, the host bridge is
89
+ `connection`. The fiber therefore never resolved and the plugin stayed pending, which
90
+ the loader reported as "did not activate". It now injects `slots` and registers into
91
+ the `settings.section` slot the way the official client artifacts do.
92
+
93
+ ## 0.1.1
94
+
95
+ - **Fix the browser half never registering.** The client bundle was emitted as plain ESM
96
+ with `react` external, which prints a top-level `import` — invalid inside the loader's
97
+ concatenated script bundle, so evaluation aborted and every module after it failed with
98
+ "...loaded without registering ... via __ModuleLoader__.load". The bundle is now emitted
99
+ in the loader's lazy-CJS factory form (`window.__ModuleLoader__.load({ id, factory })`).
100
+ - Point the `./client` export's `types` at `lib/client/index.d.ts`.
101
+
102
+ ## 0.1.0
103
+
104
+ Initial release.
105
+
106
+ - **Remember** — append-only `.memory/events.jsonl`; every memory carries provenance
107
+ (session, step, author) so the question "why does the agent think it knows this" has
108
+ an answer.
109
+ - **Recall** — relevance-ranked retrieval (token overlap, recency, verification
110
+ strength) instead of dumping every stored memory into context.
111
+ - **Forget with a reason** — a memory is superseded, never silently deleted, and the
112
+ supersede event keeps the reason on the record.
113
+ - **Conflict detection** — two active memories about the same subject are reported as a
114
+ conflict; the plugin does not pick a winner for you.
115
+ - **Verification** — a checkable memory is compiled into a platform-aware guard and
116
+ re-checked on demand. `dsh-context-memory verify` exits non-zero only for a memory
117
+ whose guard ran and failed; a guard that cannot run is reported separately and is
118
+ never counted as a broken memory.
119
+ - **Panel** — a `Context memory` settings page listing memory, verification status,
120
+ conflicts, and provenance.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 hj01857655
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hj01857655
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,133 +1,152 @@
1
- # dsh-context-memory
2
-
3
- [![npm version](https://img.shields.io/npm/v/dsh-context-memory?color=cb3837&logo=npm&logoColor=white)](https://www.npmjs.com/package/dsh-context-memory)
4
- [![npm downloads](https://img.shields.io/npm/dm/dsh-context-memory?color=cb3837)](https://www.npmjs.com/package/dsh-context-memory)
5
- [![CI](https://github.com/hj01857655/dsh-context-memory/actions/workflows/ci.yml/badge.svg)](https://github.com/hj01857655/dsh-context-memory/actions/workflows/ci.yml)
6
- [![license](https://img.shields.io/npm/l/dsh-context-memory?color=blue)](LICENSE)
7
- [![node](https://img.shields.io/node/v/dsh-context-memory?color=339933&logo=node.js&logoColor=white)](package.json)
8
- [![GitHub stars](https://img.shields.io/github/stars/hj01857655/dsh-context-memory?color=yellow)](https://github.com/hj01857655/dsh-context-memory/stargazers)
9
- [![dsh plugin](https://img.shields.io/badge/dsh-plugin-4B8BBE)](https://github.com/topics/dsh-plugin)
10
-
11
- Cross-session memory for your dsh agent — remembered, recalled, and re-verified instead of assumed.
12
-
13
- ## Install
14
-
15
- ```sh
16
- dsh plugin --profile web add dsh-context-memory
17
- ```
18
-
19
- ## What it does
20
-
21
- Every session with your agent starts from nothing. What you decided yesterday, which
22
- approach failed, the constraint you explained once — all of it is gone, and you explain
23
- it again.
24
-
25
- This plugin keeps it, and — unlike a note file — re-checks the parts that can be checked.
26
-
27
- - **Remember.** A memory carries provenance: which session, which step, whether it came
28
- from you, the agent, or a plugin. "Why does the agent think it knows this" has an
29
- answer.
30
- - **Recall.** Retrieval is ranked (token overlap, recency, verification strength) instead
31
- of pasting every memory into the context window. Each hit reports the breakdown of its
32
- score, so a ranking is never something you have to trust.
33
- - **Forget with a reason.** A memory is superseded, never deleted. The wrong memories are
34
- the most valuable records in the log — they are the only thing that can explain a past
35
- decision.
36
- - **Conflict detection.** Two active memories about the same subject are reported as a
37
- conflict. The plugin does not pick a winner; the log does not say which one is right,
38
- and guessing would be the unfounded assertion this plugin exists to remove.
39
- - **Verification.** A memory whose statement has a checkable shape is compiled into a
40
- platform-aware guard and re-checked on demand. This is the part a notes file cannot do.
41
-
42
- ## The point
43
-
44
- A memory is a claim about the world. Storing it makes it *available*; it does not make it
45
- *true*. Statements like "the file README.md exists" or "config.json contains pnpm" were
46
- true when you said them and stop being true quietly.
47
-
48
- So `verify` turns each checkable memory back into a check and runs it:
49
-
50
- ```sh
51
- dsh-context-memory verify
52
- ```
53
-
54
- ```
55
- passed 4f2a9c1b8e30 the file package.json exists
56
- violated 7d1e0a55c9f2 the file README.md contains Installation
57
- uncheckable 2b8f31aa0456 we settled on pnpm because yarn hoisting broke the build
58
- broken a91c4e77b0d3 the file scripts/release.sh exists
59
-
60
- passed 1, violated 1, broken 1, uncheckable 1
61
- ```
62
-
63
- Four outcomes, and the distinction between them is the whole point:
64
-
65
- | Outcome | Meaning |
66
- |---|---|
67
- | `passed` | The check ran and the memory holds. |
68
- | `violated` | The check ran and the world disagrees with the memory. |
69
- | `broken` | The check itself could not run — a typo, a missing binary. **Not** evidence about the memory. |
70
- | `uncheckable` | No check could be compiled. The memory is still stored and recalled; it is just not verified. |
71
-
72
- `verify` exits non-zero **only** for `violated`. A broken guard is a defect in the check,
73
- and letting it turn a pipeline red is how a typo would get a true memory marked false —
74
- after which you learn to ignore the output.
75
-
76
- Nothing is ever marked as verified because it was stored, and a memory supplied by the
77
- agent is never executed as a command: guards come from templates that only ever
78
- interpolate quoted file paths and literal search strings.
79
-
80
- ## CLI
81
-
82
- ```sh
83
- dsh-context-memory remember "the file package.json exists"
84
- dsh-context-memory list
85
- dsh-context-memory recall "how do we install"
86
- dsh-context-memory conflicts
87
- dsh-context-memory forget 4f2a9c1b8e30 "moved to bun"
88
- dsh-context-memory verify # exit 1 only on a real violation
89
- ```
90
-
91
- ## Panel
92
-
93
- A `Context memory` page inside Settings: active and superseded memories, verification
94
- status per memory, conflicts, and provenance. The host registers
95
- `GET /api/context-memory.panel` and rebuilds the payload from the log on every request,
96
- so the panel shows what is on disk rather than what was in memory at startup. A headless
97
- host has no web connection and simply skips the route.
98
-
99
- ## Layout
100
-
101
- | Path | Role |
102
- |---|---|
103
- | `src/index.ts` | Host half — `apply(ctx)`, exposes the `contextMemory` service |
104
- | `src/memory.ts` | Fold the log, remember, supersede, conflicts, verify |
105
- | `src/store.ts` | Append-only `.memory/events.jsonl` |
106
- | `src/guard.ts` | Statement → platform-aware check; run it; classify pass / violated / broken |
107
- | `src/recall.ts` | Relevance ranking with an explainable score |
108
- | `src/identity.ts` | Id derived from the normalised subject, so repeats fold |
109
- | `src/cli.ts` / `src/bin.ts` | Command-line entry, usable without dsh |
110
- | `src/routes.ts` | Panel and verify routes on the host's web connection |
111
- | `src/client/view.tsx` | Browser half — pure rendering, statically tested in Node |
112
- | `src/client/index.tsx` | Browser half — fetch and settings registration |
113
-
114
- ## Why the log is append-only
115
-
116
- Current state is always a fold over the events, recomputed on each read. The
117
- alternative — a mutable store — lets the last writer decide what was always true, and
118
- "what did the agent believe on Tuesday, and why" becomes unanswerable. The log is the
119
- evidence, so a torn trailing line after a crash is dropped (recoverable) while damage
120
- anywhere else is reported rather than skipped.
121
-
122
- ## Develop
123
-
124
- ```sh
125
- npm ci
126
- npm run typecheck
127
- npm run build
128
- npm test
129
- ```
130
-
131
- ## License
132
-
133
- MIT
1
+ # dsh-context-memory
2
+
3
+ [![npm version](https://img.shields.io/npm/v/dsh-context-memory?color=cb3837&logo=npm&logoColor=white)](https://www.npmjs.com/package/dsh-context-memory)
4
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-context-memory?color=cb3837)](https://www.npmjs.com/package/dsh-context-memory)
5
+ [![CI](https://github.com/hj01857655/dsh-context-memory/actions/workflows/ci.yml/badge.svg)](https://github.com/hj01857655/dsh-context-memory/actions/workflows/ci.yml)
6
+ [![license](https://img.shields.io/npm/l/dsh-context-memory?color=blue)](LICENSE)
7
+ [![node](https://img.shields.io/node/v/dsh-context-memory?color=339933&logo=node.js&logoColor=white)](package.json)
8
+ [![GitHub stars](https://img.shields.io/github/stars/hj01857655/dsh-context-memory?color=yellow)](https://github.com/hj01857655/dsh-context-memory/stargazers)
9
+ [![dsh plugin](https://img.shields.io/badge/dsh-plugin-4B8BBE)](https://github.com/topics/dsh-plugin)
10
+
11
+ Cross-session memory for your dsh agent — remembered, recalled, and re-verified instead of assumed.
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ dsh plugin --profile web add dsh-context-memory
17
+ ```
18
+
19
+ ## What it does
20
+
21
+ Every session with your agent starts from nothing. What you decided yesterday, which
22
+ approach failed, the constraint you explained once — all of it is gone, and you explain
23
+ it again.
24
+
25
+ This plugin keeps it, and — unlike a note file — re-checks the parts that can be checked.
26
+
27
+ - **Remember.** A memory carries provenance: which session, which step, whether it came
28
+ from you, the agent, or a plugin. "Why does the agent think it knows this" has an
29
+ answer.
30
+ - **Recall.** Retrieval is ranked (token overlap, recency, verification strength) instead
31
+ of pasting every memory into the context window. Each hit reports the breakdown of its
32
+ score, so a ranking is never something you have to trust.
33
+ - **Forget with a reason.** A memory is superseded, never deleted. The wrong memories are
34
+ the most valuable records in the log — they are the only thing that can explain a past
35
+ decision.
36
+ - **Conflict detection.** Two active memories about the same subject are reported as a
37
+ conflict. The plugin does not pick a winner; the log does not say which one is right,
38
+ and guessing would be the unfounded assertion this plugin exists to remove.
39
+ - **Verification.** A memory whose statement has a checkable shape is compiled into a
40
+ platform-aware guard and re-checked on demand. This is the part a notes file cannot do.
41
+
42
+ ## The point
43
+
44
+ A memory is a claim about the world. Storing it makes it *available*; it does not make it
45
+ *true*. Statements like "the file README.md exists" or "config.json contains pnpm" were
46
+ true when you said them and stop being true quietly.
47
+
48
+ So `verify` turns each checkable memory back into a check and runs it:
49
+
50
+ ```sh
51
+ dsh-context-memory verify
52
+ ```
53
+
54
+ ```
55
+ passed 4f2a9c1b8e30 the file package.json exists
56
+ violated 7d1e0a55c9f2 the file README.md contains Installation
57
+ uncheckable 2b8f31aa0456 we settled on pnpm because yarn hoisting broke the build
58
+ broken a91c4e77b0d3 the file scripts/release.sh exists
59
+
60
+ passed 1, violated 1, broken 1, uncheckable 1
61
+ ```
62
+
63
+ Four outcomes, and the distinction between them is the whole point:
64
+
65
+ | Outcome | Meaning |
66
+ |---|---|
67
+ | `passed` | The check ran and the memory holds. |
68
+ | `violated` | The check ran and the world disagrees with the memory. |
69
+ | `broken` | The check itself could not run — a typo, a missing binary. **Not** evidence about the memory. |
70
+ | `uncheckable` | No check could be compiled. The memory is still stored and recalled; it is just not verified. |
71
+
72
+ `verify` exits non-zero **only** for `violated`. A broken guard is a defect in the check,
73
+ and letting it turn a pipeline red is how a typo would get a true memory marked false —
74
+ after which you learn to ignore the output.
75
+
76
+ Nothing is ever marked as verified because it was stored, and a memory supplied by the
77
+ agent is never executed as a command: guards come from templates that only ever
78
+ interpolate quoted file paths and literal search strings.
79
+
80
+ ## CLI
81
+
82
+ ```sh
83
+ dsh-context-memory remember "the file package.json exists"
84
+ dsh-context-memory list
85
+ dsh-context-memory recall "how do we install"
86
+ dsh-context-memory conflicts
87
+ dsh-context-memory forget 4f2a9c1b8e30 "moved to bun"
88
+ dsh-context-memory verify # exit 1 only on a real violation
89
+ ```
90
+
91
+ ## Auto capture and recall injection (0.6.0+)
92
+
93
+ The plugin hooks into `agent/pre-step` to close the loop automatically:
94
+
95
+ - **Auto capture.** When the user's message contains an explicit memory statement
96
+ ("记住…", "remember that…"), the plugin distills and stores it before the agent sees
97
+ the request. Each captured memory carries session provenance — which agent, which
98
+ session — so "why does the agent think it knows this" always has an answer.
99
+
100
+ - **Recall injection.** Before each agent step, the top-5 memories whose subject
101
+ overlaps the user's text are folded into the request as a collapsible context message
102
+ (source `plugin/dsh-context-memory`, form `recall`). Unresolved conflicts are included
103
+ with a warning. The injection position is right after the claimed batch — the same
104
+ slot the host's own context plugins use.
105
+
106
+ Both features can be toggled from the panel's 「集成」 card. When auto capture is off,
107
+ memories are still stored via the CLI and service API. When recall injection is off,
108
+ the plugin still records but does not inject.
109
+
110
+ ## Panel
111
+
112
+ A `Context memory` page inside Settings: active and superseded memories, verification
113
+ status per memory, conflicts, provenance, and integration toggles. The host registers
114
+ `GET /api/context-memory.panel` and rebuilds the payload from the log on every request,
115
+ so the panel shows what is on disk rather than what was in memory at startup. A headless
116
+ host has no web connection and simply skips the route.
117
+
118
+ ## Layout
119
+
120
+ | Path | Role |
121
+ |---|---|
122
+ | `src/index.ts` | Host half — `apply(ctx)`, exposes the `contextMemory` service |
123
+ | `src/memory.ts` | Fold the log, remember, supersede, conflicts, verify |
124
+ | `src/store.ts` | Append-only `.memory/events.jsonl` |
125
+ | `src/guard.ts` | Statement → platform-aware check; run it; classify pass / violated / broken |
126
+ | `src/recall.ts` | Relevance ranking with an explainable score |
127
+ | `src/identity.ts` | Id derived from the normalised subject, so repeats fold |
128
+ | `src/cli.ts` / `src/bin.ts` | Command-line entry, usable without dsh |
129
+ | `src/routes.ts` | Panel and verify routes on the host's web connection |
130
+ | `src/client/view.tsx` | Browser half — pure rendering, statically tested in Node |
131
+ | `src/client/index.tsx` | Browser half — fetch and settings registration |
132
+
133
+ ## Why the log is append-only
134
+
135
+ Current state is always a fold over the events, recomputed on each read. The
136
+ alternative — a mutable store — lets the last writer decide what was always true, and
137
+ "what did the agent believe on Tuesday, and why" becomes unanswerable. The log is the
138
+ evidence, so a torn trailing line after a crash is dropped (recoverable) while damage
139
+ anywhere else is reported rather than skipped.
140
+
141
+ ## Develop
142
+
143
+ ```sh
144
+ npm ci
145
+ npm run typecheck
146
+ npm run build
147
+ npm test
148
+ ```
149
+
150
+ ## License
151
+
152
+ MIT
package/cordis.patch.yml CHANGED
@@ -1,6 +1,6 @@
1
- # The host half loads through this patch. `insert` appends the plugin to the profile's
2
- # bundle graph; the id must be unique within the profile or the loader reports a
3
- # duplicate entry id and refuses to start.
4
- - insert:
5
- - id: dsh-context-memory
6
- name: 'dsh-context-memory'
1
+ # The host half loads through this patch. `insert` appends the plugin to the profile's
2
+ # bundle graph; the id must be unique within the profile or the loader reports a
3
+ # duplicate entry id and refuses to start.
4
+ - insert:
5
+ - id: dsh-context-memory
6
+ name: 'dsh-context-memory'
package/lib/client/ui.js CHANGED
@@ -48,14 +48,14 @@ function ensureStyles() {
48
48
  return;
49
49
  injected = true;
50
50
  const el = document.createElement('style');
51
- el.textContent = `
52
- @keyframes dsh-fade-in{from{opacity:0}to{opacity:1}}
53
- @keyframes dsh-slide-up{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
54
- @keyframes dsh-spin{to{transform:rotate(360deg)}}
55
- .dsh-modal-overlay{animation:dsh-fade-in .15s ease}
56
- .dsh-modal-body{animation:dsh-slide-up .2s ease}
57
- .dsh-toast{animation:dsh-slide-up .2s ease}
58
- .dsh-spin{animation:dsh-spin .6s linear infinite}
51
+ el.textContent = `
52
+ @keyframes dsh-fade-in{from{opacity:0}to{opacity:1}}
53
+ @keyframes dsh-slide-up{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
54
+ @keyframes dsh-spin{to{transform:rotate(360deg)}}
55
+ .dsh-modal-overlay{animation:dsh-fade-in .15s ease}
56
+ .dsh-modal-body{animation:dsh-slide-up .2s ease}
57
+ .dsh-toast{animation:dsh-slide-up .2s ease}
58
+ .dsh-spin{animation:dsh-spin .6s linear infinite}
59
59
  `;
60
60
  document.head.appendChild(el);
61
61
  }
package/lib/index.js CHANGED
@@ -12,6 +12,7 @@ import { createUserMessage } from '@deepseek-ai/dsh-llm';
12
12
  import { ContextMemory } from './memory.js';
13
13
  import { distill } from './capture.js';
14
14
  import { registerMemoryRoutes } from './routes.js';
15
+ import { isRecallMessage, recallSource } from './source.js';
15
16
  export const name = 'dsh-context-memory';
16
17
  export function apply(ctx) {
17
18
  const root = resolve(process.cwd());
@@ -49,7 +50,6 @@ export function apply(ctx) {
49
50
  // capture is not re-injected into the very request that produced it — no
50
51
  // self-echo in one hop. The host renders it as collapsible context, the log
51
52
  // replays it, and compaction treats it like any other history.
52
- const PLUGIN = name;
53
53
  let injectEnabled = true;
54
54
  let captureEnabled = true;
55
55
  let injectedMessageIds = [];
@@ -71,10 +71,10 @@ export function apply(ctx) {
71
71
  }
72
72
  return createUserMessage({
73
73
  content: lines.length > 0 ? [{ type: 'text', text: lines.join('\n') }] : [],
74
- source: { kind: 'plugin', plugin: PLUGIN, form: 'recall' },
74
+ source: recallSource,
75
75
  });
76
76
  };
77
- const isOwnContext = (message) => message.source.kind === 'plugin' && message.source.plugin === PLUGIN && message.source.form === 'recall';
77
+ const isOwnContext = (message) => isRecallMessage(message);
78
78
  ctx.on('agent/pre-step', async ({ agent, messages, signal }, next) => {
79
79
  const decision = await next();
80
80
  if (decision.kind === 'reject')
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/client/index.tsx", "../src/client/locales.ts", "../src/client/view.tsx", "../src/client/ui.tsx"],
4
- "sourcesContent": ["/**\n * Browser half of dsh-context-memory: its page inside Settings.\n *\n * Two services are injected by their runtime identity, not by the packages that provide\n * them: the settings shell is `slots`, the dictionary registry is `locale`. The label and\n * every string inside the component are translated through this plugin's own namespace,\n * which is why the page follows the UI language instead of the plugin author's.\n *\n * @module client\n */\n\nimport type { ComponentType } from 'react'\n\nimport { NS, en, zh } from './locales.js'\nimport { ContextMemoryPanel } from './view.js'\n\nexport const inject = ['slots', 'locale']\n\ninterface SlotsService {\n inject(name: string, register: () => void): void\n register(\n options: { name: string; id: string; order: number; label: () => string; locale: string },\n component: ComponentType<{ t: (key: string, params?: Record<string, unknown>) => string }>,\n ): unknown\n}\n\ninterface ClientContext {\n slots: SlotsService\n locale: {\n register(ns: string, dicts: { zh: unknown; en: unknown }): () => void\n bind(ns: string): (key: string, params?: Record<string, unknown>) => string\n }\n effect(callback: () => unknown, label?: string): unknown\n}\n\nexport function apply(ctx: ClientContext): void {\n // `zh` is the key-set source of truth, matching the official client plugins.\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-context-memory: dictionaries')\n\n ctx.slots.inject('settings.section', () => ctx.slots.register(\n {\n name: 'settings.section',\n id: 'context-memory',\n order: 45,\n label: () => ctx.locale.bind(NS)('nav'),\n locale: NS,\n },\n ContextMemoryPanel,\n ))\n}\n", "/**\n * Dictionaries for the Context memory page.\n *\n * `zh` is the key-set source of truth, as in the official client plugins, and `en` is\n * typed against it: a key translated in one language but not the other fails the build\n * instead of silently rendering the raw key.\n *\n * @module client/locales\n */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'contextMemory'\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n 'nav': '\u8DE8\u4F1A\u8BDD\u8BB0\u5FC6',\n 'title': '\u8DE8\u4F1A\u8BDD\u8BB0\u5FC6',\n 'active': '\u751F\u6548\u4E2D',\n 'superseded': '\u5DF2\u5931\u6548',\n 'conflicts': '\u51B2\u7A81',\n 'noConflicts': '\u6CA1\u6709\u51B2\u7A81\u3002',\n 'memory': '\u8BB0\u5FC6',\n 'kind': '\u7C7B\u578B',\n 'status': '\u6821\u9A8C\u72B6\u6001',\n 'from': '\u6765\u6E90',\n 'checkNone': '\u65E0\u68C0\u67E5',\n 'checkPassed': '\u5DF2\u6838\u5BF9\uFF0C\u6210\u7ACB',\n 'checkViolated': '\u5DF2\u6838\u5BF9\uFF0C\u4E0D\u6210\u7ACB',\n 'checkBroken': '\u68C0\u67E5\u65E0\u6CD5\u8FD0\u884C',\n 'checkPending': '\u5C1A\u672A\u6838\u5BF9',\n 'unverified': '\u672A\u9A8C\u8BC1\u6210\u7ACB',\n 'empty': '\u8FD8\u6CA1\u6709\u8BB0\u4F4F\u4EFB\u4F55\u5185\u5BB9\u3002',\n 'checkLegend': '\u65E0\u68C0\u67E5\uFF1D\u65E0\u6CD5\u7F16\u8BD1\u68C0\u67E5\uFF1B\u68C0\u67E5\u65E0\u6CD5\u8FD0\u884C\uFF1D\u68C0\u67E5\u672C\u8EAB\u6709\u95EE\u9898\uFF0C\u4E0D\u4EE3\u8868\u8BB0\u5FC6\u9519\u8BEF\u3002',\n 'refresh': '\u5237\u65B0',\n 'loading': '\u6B63\u5728\u52A0\u8F7D\u2026',\n 'failed': '\u52A0\u8F7D\u5931\u8D25',\n 'retry': '\u91CD\u8BD5',\n 'searchPlaceholder': '\u641C\u7D22\u8BB0\u5FC6\u2026',\n 'search': '\u641C\u7D22',\n 'searchResults': '\u641C\u7D22\u7ED3\u679C',\n 'noResults': '\u6CA1\u6709\u5339\u914D\u7684\u8BB0\u5FC6\u3002',\n 'addMemory': '\u6DFB\u52A0\u8BB0\u5FC6',\n 'memoryText': '\u8BB0\u5FC6\u5185\u5BB9',\n 'subjectOptional': '\u4E3B\u9898\uFF08\u53EF\u9009\uFF09',\n 'kindFact': '\u4E8B\u5B9E',\n 'kindDecision': '\u51B3\u7B56',\n 'kindPreference': '\u504F\u597D',\n 'kindPitfall': '\u9677\u9631',\n 'add': '\u6DFB\u52A0',\n 'forget': '\u5E9F\u5F03',\n 'resolve': '\u89E3\u51B3',\n 'integration': '\u96C6\u6210',\n 'autoCaptureLabel': '\u81EA\u52A8\u6355\u83B7:\u7528\u6237\u6D88\u606F\u91CC\u7684\u300C\u8BB0\u4F4F\u2026\u300D\u300C\u4EE5\u540E\u2026\u300D\u81EA\u52A8\u5165\u5E93',\n 'recallInjectLabel': '\u53EC\u56DE\u6CE8\u5165:\u6BCF\u6B21\u8BF7\u6C42\u524D\u628A\u76F8\u5173\u8BB0\u5FC6\u6298\u8FDB\u4E0A\u4E0B\u6587',\n}\n\n/** English dictionary, checked complete against the zh key set. */\nexport const en: typeof zh = {\n 'nav': 'Context memory',\n 'title': 'Context memory',\n 'active': 'active',\n 'superseded': 'superseded',\n 'conflicts': 'Conflicts',\n 'noConflicts': 'No conflicts.',\n 'memory': 'Memory',\n 'kind': 'Kind',\n 'status': 'Check',\n 'from': 'From',\n 'checkNone': 'no check',\n 'checkPassed': 'checked, holds',\n 'checkViolated': 'checked, does not hold',\n 'checkBroken': 'check unusable',\n 'checkPending': 'not yet checked',\n 'unverified': 'not verified as holding',\n 'empty': 'Nothing is remembered yet.',\n 'checkLegend': 'no check = no check could be compiled; check unusable = the check is defective, not the memory.',\n 'refresh': 'Refresh',\n 'loading': 'Loading\u2026',\n 'failed': 'Failed to load',\n 'retry': 'Retry',\n 'searchPlaceholder': 'Search memories\u2026',\n 'search': 'Search',\n 'searchResults': 'Search Results',\n 'noResults': 'No matching memories.',\n 'addMemory': 'Add Memory',\n 'memoryText': 'Memory text',\n 'subjectOptional': 'Subject (optional)',\n 'kindFact': 'Fact',\n 'kindDecision': 'Decision',\n 'kindPreference': 'Preference',\n 'kindPitfall': 'Pitfall',\n 'add': 'Add',\n 'forget': 'Forget',\n 'resolve': 'Resolve',\n 'integration': 'Integration',\n 'autoCaptureLabel': 'Auto capture: \"remember that\u2026\" statements land in the log',\n 'recallInjectLabel': 'Recall injection: fold relevant memories into each request',\n}\n", "/**\n * Pure rendering half of the contextMemory page. Uses shared UI kit.\n * @module client/view\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { ReactNode } from 'react'\n\nimport type { MemoryState, PanelPayload, RecallHit } from '../types.js'\nimport {\n Badge, Button, Card, ConfirmDialog, EmptyState, Field, Input, Modal,\n SectionTitle, Select, Spinner, StatCard, ToastProvider, tableStyles,\n usePanel, useToast,\n} from './ui.js'\n\nexport type Translate = (key: string, params?: Record<string, unknown>) => string\nexport interface PanelProps { t: Translate }\n\nconst PANEL_PATH = '/api/context-memory.panel'\nconst RECALL_PATH = '/api/context-memory.recall'\nconst REMEMBER_PATH = '/api/context-memory.remember'\nconst FORGET_PATH = '/api/context-memory.forget'\nconst PREFS_PATH = '/api/context-memory.prefs'\n\nexport function checkLabel(memory: MemoryState, t: Translate): string {\n if (!memory.guard) return t('checkNone')\n switch (memory.verification?.outcome) {\n case 'passed': return t('checkPassed')\n case 'violated': return t('checkViolated')\n case 'broken': return t('checkBroken')\n default: return t('checkPending')\n }\n}\n\nexport function provenance(memory: MemoryState): string {\n return [memory.source, memory.sessionId !== undefined ? `session ${memory.sessionId}` : null, memory.step !== undefined ? `step ${String(memory.step)}` : null].filter((part) => part !== null).join(' \u00B7 ')\n}\n\nfunction AddMemoryModal({ t, onClose, onAdded }: { t: Translate; onClose: () => void; onAdded: () => void }): ReactNode {\n const toast = useToast()\n const [text, setText] = useState('')\n const [subject, setSubject] = useState('')\n const [kind, setKind] = useState('fact')\n const [adding, setAdding] = useState(false)\n\n const handleAdd = useCallback(async () => {\n if (!text.trim()) return\n setAdding(true)\n try {\n const r = await fetch(REMEMBER_PATH, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text, subject: subject || undefined, kind, source: 'user' }) })\n if (r.ok) { toast('success', t('memoryAdded')); onAdded(); onClose() }\n } finally { setAdding(false) }\n }, [text, subject, kind, t, toast, onAdded, onClose])\n\n return (\n <Modal title={t('addMemory')} onClose={onClose} width={500}\n footer={<><Button variant=\"secondary\" onClick={onClose}>{t('cancel')}</Button>\n <Button variant=\"primary\" disabled={adding || !text.trim()} onClick={handleAdd}>{adding ? <Spinner size={14} /> : null} {t('add')}</Button></>}>\n <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>\n <Field label={t('memoryText')}><Input value={text} onChange={(e) => setText(e.target.value)} placeholder={t('memoryTextPlaceholder')} /></Field>\n <Field label={t('subjectOptional')}><Input value={subject} onChange={(e) => setSubject(e.target.value)} placeholder={t('subjectPlaceholder')} /></Field>\n <Field label={t('kind')}>\n <Select value={kind} onChange={(e) => setKind(e.target.value)} style={{ width: '100%' }}>\n <option value=\"fact\">{t('kindFact')}</option>\n <option value=\"decision\">{t('kindDecision')}</option>\n <option value=\"preference\">{t('kindPreference')}</option>\n <option value=\"pitfall\">{t('kindPitfall')}</option>\n </Select>\n </Field>\n </div>\n </Modal>\n )\n}\n\nfunction ContextMemoryPanelInner({ t }: PanelProps): ReactNode {\n const { payload, error, reload } = usePanel<PanelPayload>(PANEL_PATH)\n const toast = useToast()\n const [searchQuery, setSearchQuery] = useState('')\n const [searchHits, setSearchHits] = useState<RecallHit[] | null>(null)\n const [searching, setSearching] = useState(false)\n const [prefs, setPrefsState] = useState<{ autoCapture: boolean; recallInject: boolean } | null>(null)\n\n const togglePref = useCallback(async (key: 'autoCapture' | 'recallInject') => {\n const r = await fetch(PREFS_PATH, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ [key]: !(prefs?.[key] ?? true) }) })\n if (r.ok) setPrefsState(await r.json() as { autoCapture: boolean; recallInject: boolean })\n }, [prefs])\n\n useEffect(() => { void fetch(PREFS_PATH).then(async (r) => { if (r.ok) setPrefsState(await r.json() as { autoCapture: boolean; recallInject: boolean }) }) }, [])\n const [addOpen, setAddOpen] = useState(false)\n const [forgetTarget, setForgetTarget] = useState<{ id: string; reason: string } | null>(null)\n\n const handleSearch = useCallback(async () => {\n if (!searchQuery.trim()) { setSearchHits(null); return }\n setSearching(true)\n try { const r = await fetch(`${RECALL_PATH}?q=${encodeURIComponent(searchQuery)}&limit=10`); if (r.ok) { setSearchHits((await r.json() as { hits: RecallHit[] }).hits) } }\n finally { setSearching(false) }\n }, [searchQuery])\n\n const handleForget = useCallback(async () => {\n if (forgetTarget === null) return\n const r = await fetch(FORGET_PATH, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(forgetTarget) })\n if (r.ok) { toast('success', t('forgotten')); setForgetTarget(null); reload() }\n }, [forgetTarget, t, toast, reload])\n\n const header = (\n <header style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 4 }}>\n <strong style={{ fontSize: 15 }}>\uD83E\uDDE0 {t('title')}</strong>\n <span style={{ flex: 1 }} />\n <Button variant=\"primary\" size=\"sm\" onClick={() => setAddOpen(true)}>\u2795 {t('addMemory')}</Button>\n <Button variant=\"secondary\" size=\"sm\" onClick={reload}>{t('refresh')}</Button>\n </header>\n )\n\n if (error !== null) return <div style={{ maxWidth: 820 }}>{header}<Card><p role=\"alert\" style={{ margin: 0, fontSize: 13, color: 'var(--dsw-alias-state-error-primary)' }}>{t('failed')}: {error}</p></Card></div>\n if (payload === null) return <div style={{ maxWidth: 820 }}>{header}<div style={{ display: 'flex', justifyContent: 'center', padding: 40 }}><Spinner size={28} /></div></div>\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 820 }}>\n {header}\n\n <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>\n <StatCard value={payload.totals.active} label={t('active')} />\n <StatCard value={payload.totals.verified} label={t('checkPassed')} />\n <StatCard value={payload.totals.unverified} label={t('unverified')} />\n <StatCard value={payload.totals.superseded} label={t('superseded')} />\n </div>\n\n {/* Integration switches */}\n <Card title={t('integration')} icon=\"\u2699\uFE0F\">\n <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>\n {([['autoCapture', 'autoCaptureLabel'], ['recallInject', 'recallInjectLabel']] as const).map(([key, labelKey]) => (\n <label key={key} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: 'pointer' }}>\n <input type=\"checkbox\" checked={prefs?.[key] ?? true} onChange={() => togglePref(key)} />\n {t(labelKey)}\n </label>\n ))}\n </div>\n </Card>\n\n {/* Search */}\n <Card title={t('search')} icon=\"\uD83D\uDD0D\">\n <div style={{ display: 'flex', gap: 8 }}>\n <Input value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder={t('searchPlaceholder')}\n onKeyDown={(e) => { if (e.key === 'Enter') handleSearch() }} />\n <Button variant=\"secondary\" onClick={handleSearch} disabled={searching}>{searching ? <Spinner size={14} /> : null} {t('search')}</Button>\n </div>\n {searchHits !== null && (\n <div style={{ marginTop: 10 }}>\n {searchHits.length === 0 ? <EmptyState message={t('noResults')} /> : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>\n {searchHits.map((hit) => (\n <div key={hit.memory.id} style={{ display: 'flex', gap: 8, alignItems: 'baseline' }}>\n <Badge color=\"info\">{hit.score.toFixed(2)}</Badge>\n <span style={{ fontSize: 12 }}>{hit.memory.text}</span>\n <span style={{ fontSize: 10, opacity: 0.5 }}>\u2014 {hit.why}</span>\n </div>\n ))}\n </div>\n )}\n </div>\n )}\n </Card>\n\n {/* Conflicts */}\n {payload.conflicts.length > 0 && (\n <>\n <SectionTitle icon=\"\u26A0\uFE0F\">{t('conflicts')}</SectionTitle>\n {payload.conflicts.map((conflict) => (\n <Card key={conflict.subject} title={conflict.subject}>\n {conflict.memories.map((memory) => (\n <div key={memory.id} style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>\n <code style={{ fontSize: 11 }}>{memory.id}</code>\n <span style={{ fontSize: 12, flex: 1 }}>{memory.text}</span>\n <Button variant=\"danger\" size=\"sm\" onClick={() => setForgetTarget({ id: memory.id, reason: `conflict resolved: ${conflict.subject}` })}>{t('resolve')}</Button>\n </div>\n ))}\n </Card>\n ))}\n </>\n )}\n\n {/* Active memories */}\n <SectionTitle icon=\"\u2705\">{t('active')}</SectionTitle>\n {payload.active.length === 0 ? <EmptyState icon=\"\uD83D\uDCED\" message={t('empty')} /> : (\n <Card padding={0}>\n <table style={tableStyles.table}>\n <thead><tr><th style={tableStyles.th}>Id</th><th style={tableStyles.th}>{t('memory')}</th><th style={tableStyles.th}>{t('kind')}</th><th style={tableStyles.th}>{t('status')}</th><th style={tableStyles.th}>{t('from')}</th><th style={tableStyles.th} /></tr></thead>\n <tbody>\n {payload.active.map((memory) => (\n <tr key={memory.id}>\n <td style={tableStyles.td}><code style={{ fontSize: 11 }}>{memory.id}</code></td>\n <td style={tableStyles.td}>{memory.text}</td>\n <td style={tableStyles.td}><Badge>{memory.kind}</Badge></td>\n <td style={tableStyles.td}>{checkLabel(memory, t)}</td>\n <td style={{ ...tableStyles.td, fontSize: 11, opacity: 0.6 }}>{provenance(memory)}</td>\n <td style={tableStyles.td}><Button variant=\"danger\" size=\"sm\" onClick={() => setForgetTarget({ id: memory.id, reason: 'superseded from panel' })}>\uD83D\uDDD1</Button></td>\n </tr>\n ))}\n </tbody>\n </table>\n </Card>\n )}\n\n {payload.superseded.length > 0 && (\n <>\n <SectionTitle icon=\"\uD83D\uDCE6\">{t('superseded')} ({payload.superseded.length})</SectionTitle>\n <Card padding={0}>\n <table style={tableStyles.table}>\n <thead><tr><th style={tableStyles.th}>Id</th><th style={tableStyles.th}>{t('memory')}</th><th style={tableStyles.th}>{t('kind')}</th><th style={tableStyles.th}>{t('status')}</th></tr></thead>\n <tbody>\n {payload.superseded.map((memory) => (\n <tr key={memory.id}><td style={tableStyles.td}><code style={{ fontSize: 11 }}>{memory.id}</code></td><td style={tableStyles.td}>{memory.text}</td><td style={tableStyles.td}><Badge>{memory.kind}</Badge></td><td style={tableStyles.td}>{checkLabel(memory, t)}</td></tr>\n ))}\n </tbody>\n </table>\n </Card>\n </>\n )}\n\n {addOpen && <AddMemoryModal t={t} onClose={() => setAddOpen(false)} onAdded={reload} />}\n {forgetTarget !== null && <ConfirmDialog title={t('forget')} message={t('confirmForget')} confirmLabel={t('forget')} danger onConfirm={handleForget} onClose={() => setForgetTarget(null)} />}\n </div>\n )\n}\n\nexport function ContextMemoryPanel({ t }: PanelProps): ReactNode {\n return <ToastProvider><ContextMemoryPanelInner t={t} /></ToastProvider>\n}\n", "/**\n * Shared UI component kit for dsh plugin panels.\n *\n * Self-contained: imports nothing but React. All styling is inline + CSS custom\n * properties so the components follow the host theme. A single <style> tag injects\n * keyframes the first time any component mounts.\n *\n * @module client/ui\n */\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react'\nimport type {\n ButtonHTMLAttributes,\n CSSProperties,\n InputHTMLAttributes,\n ReactNode,\n SelectHTMLAttributes,\n} from 'react'\n\n// \u2500\u2500\u2500 Theme tokens \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n/**\n * Host theme tokens.\n *\n * Every name here is a real dsh token, read out of the shipped stylesheets\n * (`@deepseek-ai/dsh-client-ui-theme`), which define them on `body`. An earlier\n * version used invented names \u2014 `--accent`, `--border`, `--bg-primary`,\n * `--text-primary` and friends \u2014 that dsh does not define at all. The\n * hardcoded fallbacks then applied in every theme, so each panel rendered its\n * own fixed palette instead of following the host, and the modal and input\n * surfaces stayed white in dark mode.\n *\n * No fallbacks on purpose: if a name were ever missing, the declaration becomes\n * invalid at computed-value time and the property inherits, which degrades\n * gracefully, whereas a hardcoded fallback bakes in a color that is wrong in one\n * of the two themes. `tests/ui-tokens.test.mjs` enforces the name list.\n */\nconst T = {\n accent: 'var(--dsw-alias-brand-primary)',\n accentHover: 'var(--dsw-alias-button-primary-hover)',\n inverted: 'var(--dsw-alias-label-primary-inverted)',\n error: 'var(--dsw-alias-state-error-primary)',\n success: 'var(--dsw-alias-state-success-primary)',\n warning: 'var(--dsw-alias-state-warn-primary)',\n bg: 'var(--dsw-alias-bg-layer-2)',\n bgHover: 'var(--dsw-alias-interactive-bg-hover)',\n border: 'var(--dsw-alias-border-l2)',\n text: 'var(--dsw-alias-label-primary)',\n muted: 'var(--dsw-alias-label-tertiary)',\n surface: 'var(--dsw-alias-bg-layer-1)',\n mask: 'var(--dsw-alias-bg-mask-1)',\n}\n\n// \u2500\u2500\u2500 Style injection (once) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet injected = false\nfunction ensureStyles(): void {\n if (injected || typeof document === 'undefined') return\n injected = true\n const el = document.createElement('style')\n el.textContent = `\n@keyframes dsh-fade-in{from{opacity:0}to{opacity:1}}\n@keyframes dsh-slide-up{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}\n@keyframes dsh-spin{to{transform:rotate(360deg)}}\n.dsh-modal-overlay{animation:dsh-fade-in .15s ease}\n.dsh-modal-body{animation:dsh-slide-up .2s ease}\n.dsh-toast{animation:dsh-slide-up .2s ease}\n.dsh-spin{animation:dsh-spin .6s linear infinite}\n `\n document.head.appendChild(el)\n}\n\n// \u2500\u2500\u2500 Modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Modal({\n title,\n onClose,\n children,\n footer,\n width = 520,\n}: {\n title: string\n onClose: () => void\n children: ReactNode\n footer?: ReactNode\n width?: number\n}): ReactNode {\n useEffect(() => {\n ensureStyles()\n const handler = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') onClose()\n }\n document.addEventListener('keydown', handler)\n return () => document.removeEventListener('keydown', handler)\n }, [onClose])\n\n return (\n <div\n className=\"dsh-modal-overlay\"\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 1000,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n background: T.mask,\n backdropFilter: 'blur(3px)',\n WebkitBackdropFilter: 'blur(3px)',\n }}\n onClick={onClose}\n >\n <div\n className=\"dsh-modal-body\"\n style={{\n background: T.surface,\n borderRadius: 14,\n boxShadow: 'var(--dsw-elevation-prominent)',\n width: `min(92vw, ${width}px)`,\n maxHeight: '85vh',\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden',\n }}\n onClick={(e) => e.stopPropagation()}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '16px 22px',\n borderBottom: `1px solid ${T.border}`,\n }}\n >\n <h3 style={{ margin: 0, fontSize: 15, fontWeight: 600 }}>{title}</h3>\n <button\n type=\"button\"\n onClick={onClose}\n style={{\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n fontSize: 20,\n color: T.muted,\n padding: '0 4px',\n lineHeight: 1,\n borderRadius: 4,\n }}\n aria-label=\"Close\"\n >\n \u00D7\n </button>\n </div>\n <div style={{ padding: 22, overflow: 'auto', flex: 1 }}>{children}</div>\n {footer !== undefined && (\n <div\n style={{\n padding: '14px 22px',\n borderTop: `1px solid ${T.border}`,\n display: 'flex',\n justifyContent: 'flex-end',\n gap: 10,\n background: T.bg,\n }}\n >\n {footer}\n </div>\n )}\n </div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 ConfirmDialog \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function ConfirmDialog({\n title,\n message,\n confirmLabel,\n cancelLabel = 'Cancel',\n onConfirm,\n onClose,\n danger,\n}: {\n title: string\n message: string\n confirmLabel: string\n cancelLabel?: string\n onConfirm: () => void\n onClose: () => void\n danger?: boolean\n}): ReactNode {\n return (\n <Modal\n title={title}\n onClose={onClose}\n width={420}\n footer={\n <>\n <Button variant=\"secondary\" onClick={onClose}>\n {cancelLabel}\n </Button>\n <Button\n variant={danger === true ? 'danger' : 'primary'}\n onClick={() => {\n onConfirm()\n onClose()\n }}\n >\n {confirmLabel}\n </Button>\n </>\n }\n >\n <p style={{ margin: 0, fontSize: 14, lineHeight: 1.6 }}>{message}</p>\n </Modal>\n )\n}\n\n// \u2500\u2500\u2500 Button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Button({\n variant = 'secondary',\n size = 'md',\n children,\n style,\n ...rest\n}: {\n variant?: 'primary' | 'secondary' | 'danger' | 'ghost'\n size?: 'sm' | 'md'\n children: ReactNode\n style?: CSSProperties\n} & ButtonHTMLAttributes<HTMLButtonElement>): ReactNode {\n const base: CSSProperties = {\n cursor: 'pointer',\n borderRadius: 7,\n fontWeight: 500,\n transition: 'all 0.15s ease',\n border: 'none',\n display: 'inline-flex',\n alignItems: 'center',\n gap: 4,\n fontSize: size === 'sm' ? 12 : 13,\n padding: size === 'sm' ? '5px 12px' : '8px 18px',\n fontFamily: 'inherit',\n }\n const variants: Record<string, CSSProperties> = {\n primary: { background: T.accent, color: T.inverted },\n secondary: {\n background: T.bg,\n color: T.text,\n border: `1px solid ${T.border}`,\n },\n danger: {\n background: 'transparent',\n color: T.error,\n border: `1px solid ${T.error}`,\n },\n ghost: { background: 'transparent', color: T.muted, border: 'none' },\n }\n return (\n <button type=\"button\" style={{ ...base, ...variants[variant], ...style }} {...rest}>\n {children}\n </button>\n )\n}\n\n// \u2500\u2500\u2500 Card \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Card({\n title,\n icon,\n children,\n actions,\n style,\n padding = 14,\n}: {\n title?: string\n icon?: string\n children: ReactNode\n actions?: ReactNode\n style?: CSSProperties\n padding?: number\n}): ReactNode {\n return (\n <div\n style={{\n borderRadius: 10,\n border: `1px solid ${T.border}`,\n background: T.bg,\n overflow: 'hidden',\n ...style,\n }}\n >\n {(title !== undefined || actions !== undefined) && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '12px 16px',\n borderBottom: title !== undefined ? `1px solid ${T.border}` : 'none',\n }}\n >\n <span style={{ fontSize: 13, fontWeight: 600, display: 'flex', alignItems: 'center', gap: 6 }}>\n {icon !== undefined && <span>{icon}</span>}\n {title}\n </span>\n {actions}\n </div>\n )}\n <div style={{ padding }}>{children}</div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 StatCard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function StatCard({\n value,\n label,\n color,\n unit,\n}: {\n value: string | number\n label: string\n color?: string\n unit?: string\n}): ReactNode {\n return (\n <div\n style={{\n flex: 1,\n minWidth: 90,\n textAlign: 'center',\n padding: '14px 10px',\n borderRadius: 10,\n border: `1px solid ${T.border}`,\n background: T.bg,\n }}\n >\n <div style={{ fontSize: 24, fontWeight: 700, color: color ?? T.text, lineHeight: 1.2 }}>\n {value}\n {unit !== undefined && (\n <span style={{ fontSize: 13, fontWeight: 500, opacity: 0.7, marginLeft: 2 }}>{unit}</span>\n )}\n </div>\n <div style={{ fontSize: 11, color: T.muted, marginTop: 4 }}>{label}</div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Badge({\n children,\n color = 'default',\n}: {\n children: ReactNode\n color?: 'default' | 'success' | 'error' | 'warning' | 'info'\n}): ReactNode {\n const colors: Record<string, { bg: string; fg: string }> = {\n default: { bg: T.bgHover, fg: T.muted },\n success: { bg: 'var(--dsw-alias-state-success-tertiary)', fg: T.success },\n error: { bg: 'var(--dsw-alias-state-error-secondary)', fg: T.error },\n warning: { bg: 'var(--dsw-alias-state-warn-tertiary)', fg: T.warning },\n info: { bg: 'var(--dsw-alias-state-business-tertiary)', fg: T.accent },\n }\n const c = colors[color]\n return (\n <span\n style={{\n display: 'inline-block',\n padding: '2px 9px',\n borderRadius: 10,\n fontSize: 11,\n fontWeight: 600,\n background: c.bg,\n color: c.fg,\n }}\n >\n {children}\n </span>\n )\n}\n\n// \u2500\u2500\u2500 Input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Input({\n style,\n ...rest\n}: { style?: CSSProperties } & InputHTMLAttributes<HTMLInputElement>): ReactNode {\n return (\n <input\n {...rest}\n style={{\n fontSize: 13,\n padding: '8px 12px',\n borderRadius: 7,\n border: `1px solid ${T.border}`,\n background: T.surface,\n color: T.text,\n width: '100%',\n boxSizing: 'border-box',\n fontFamily: 'inherit',\n outline: 'none',\n transition: 'border-color 0.15s',\n ...style,\n }}\n />\n )\n}\n\n// \u2500\u2500\u2500 Select \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Select({\n style,\n children,\n ...rest\n}: {\n style?: CSSProperties\n children: ReactNode\n} & SelectHTMLAttributes<HTMLSelectElement>): ReactNode {\n return (\n <select\n {...rest}\n style={{\n fontSize: 13,\n padding: '8px 12px',\n borderRadius: 7,\n border: `1px solid ${T.border}`,\n background: T.surface,\n color: T.text,\n cursor: 'pointer',\n fontFamily: 'inherit',\n outline: 'none',\n ...style,\n }}\n >\n {children}\n </select>\n )\n}\n\n// \u2500\u2500\u2500 Textarea \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Textarea({\n style,\n ...rest\n}: { style?: CSSProperties } & React.TextareaHTMLAttributes<HTMLTextAreaElement>): ReactNode {\n return (\n <textarea\n {...rest}\n style={{\n fontSize: 13,\n padding: '8px 12px',\n borderRadius: 7,\n border: `1px solid ${T.border}`,\n background: T.surface,\n color: T.text,\n width: '100%',\n boxSizing: 'border-box',\n fontFamily: 'inherit',\n outline: 'none',\n resize: 'vertical',\n lineHeight: 1.5,\n ...style,\n }}\n />\n )\n}\n\n// \u2500\u2500\u2500 Toast \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ninterface ToastItem {\n id: number\n type: 'success' | 'error' | 'info'\n message: string\n}\n\nconst ToastContext = createContext<(type: ToastItem['type'], message: string) => void>(\n () => {},\n)\n\nexport function ToastProvider({ children }: { children: ReactNode }): ReactNode {\n const [toasts, setToasts] = useState<ToastItem[]>([])\n const show = useCallback((type: ToastItem['type'], message: string) => {\n const id = Date.now() + Math.random()\n setToasts((t) => [...t, { id, type, message }])\n setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 3500)\n }, [])\n return (\n <ToastContext.Provider value={show}>\n {children}\n {toasts.length > 0 && (\n <div\n style={{\n position: 'fixed',\n bottom: 24,\n right: 24,\n zIndex: 2000,\n display: 'flex',\n flexDirection: 'column',\n gap: 8,\n }}\n >\n {toasts.map((t) => (\n <div\n key={t.id}\n className=\"dsh-toast\"\n style={{\n padding: '12px 18px',\n borderRadius: 8,\n fontSize: 13,\n fontWeight: 500,\n boxShadow: 'var(--dsw-elevation-panel)',\n color: T.inverted,\n background:\n t.type === 'success'\n ? T.success\n : t.type === 'error'\n ? T.error\n : T.accent,\n maxWidth: 360,\n }}\n >\n {t.message}\n </div>\n ))}\n </div>\n )}\n </ToastContext.Provider>\n )\n}\n\nexport function useToast(): (type: ToastItem['type'], message: string) => void {\n return useContext(ToastContext)\n}\n\n// \u2500\u2500\u2500 Spinner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Spinner({ size = 16 }: { size?: number }): ReactNode {\n ensureStyles()\n return (\n <span\n className=\"dsh-spin\"\n style={{\n display: 'inline-block',\n width: size,\n height: size,\n border: `2px solid ${T.border}`,\n borderTopColor: T.accent,\n borderRadius: '50%',\n }}\n />\n )\n}\n\n// \u2500\u2500\u2500 EmptyState \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function EmptyState({\n icon,\n message,\n}: {\n icon?: string\n message: string\n}): ReactNode {\n return (\n <div\n style={{\n textAlign: 'center',\n padding: '36px 16px',\n color: T.muted,\n }}\n >\n {icon !== undefined && (\n <div style={{ fontSize: 36, marginBottom: 10, opacity: 0.4 }}>{icon}</div>\n )}\n <div style={{ fontSize: 13 }}>{message}</div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 SectionTitle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function SectionTitle({\n icon,\n children,\n actions,\n}: {\n icon?: string\n children: ReactNode\n actions?: ReactNode\n}): ReactNode {\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n marginTop: 6,\n marginBottom: 8,\n }}\n >\n <span\n style={{\n fontSize: 14,\n fontWeight: 600,\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n }}\n >\n {icon !== undefined && <span>{icon}</span>}\n {children}\n </span>\n {actions}\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Table helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport const tableStyles = {\n table: {\n borderCollapse: 'collapse' as const,\n width: '100%' as const,\n },\n th: {\n textAlign: 'left' as const,\n padding: '8px 12px 8px 0',\n fontWeight: 600 as const,\n fontSize: 12 as const,\n color: T.muted,\n borderBottom: `1px solid ${T.border}`,\n },\n td: {\n padding: '8px 12px 8px 0',\n fontSize: 13 as const,\n borderBottom: `1px solid ${T.border}`,\n },\n clickRow: { cursor: 'pointer' as const },\n}\n\n// \u2500\u2500\u2500 CodeBlock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function CodeBlock({\n children,\n maxHeight = 200,\n style,\n}: {\n children: string\n maxHeight?: number\n style?: CSSProperties\n}): ReactNode {\n return (\n <pre\n style={{\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n fontSize: 12,\n maxHeight,\n overflow: 'auto',\n padding: 10,\n borderRadius: 7,\n background: T.bg,\n border: `1px solid ${T.border}`,\n margin: 0,\n ...style,\n }}\n >\n {children}\n </pre>\n )\n}\n\n// \u2500\u2500\u2500 ProgressBar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function ProgressBar({\n pct,\n color,\n height = 18,\n}: {\n pct: number\n color?: string\n height?: number\n}): ReactNode {\n return (\n <div\n style={{\n width: '100%',\n height,\n background: T.bgHover,\n borderRadius: 5,\n overflow: 'hidden',\n position: 'relative',\n }}\n >\n <div\n style={{\n width: `${Math.min(pct, 100)}%`,\n height: '100%',\n background: color ?? T.accent,\n borderRadius: 5,\n transition: 'width 0.3s ease',\n }}\n />\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Field label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Field({\n label,\n children,\n hint,\n}: {\n label: string\n children: ReactNode\n hint?: string\n}): ReactNode {\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>\n <label style={{ fontSize: 12, fontWeight: 600, color: T.text }}>{label}</label>\n {children}\n {hint !== undefined && (\n <span style={{ fontSize: 11, color: T.muted }}>{hint}</span>\n )}\n </div>\n )\n}\n\n// \u2500\u2500\u2500 usePanel hook (shared fetch logic) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function usePanel<P>(path: string): {\n payload: P | null\n error: string | null\n reload: () => void\n} {\n const [payload, setPayload] = useState<P | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [tick, setTick] = useState(0)\n const reload = useCallback(() => setTick((v) => v + 1), [])\n\n useEffect(() => {\n const c = new AbortController()\n setError(null)\n fetch(path, { signal: c.signal })\n .then(async (r) => {\n if (!r.ok) throw new Error(String(r.status))\n return r.json() as Promise<P>\n })\n .then((p) => {\n if (!c.signal.aborted) setPayload(p)\n })\n .catch((e: unknown) => {\n if (!c.signal.aborted) setPayload(null), setError(e instanceof Error ? e.message : String(e))\n })\n return () => c.abort()\n }, [tick, path])\n\n return useMemo(() => ({ payload, error, reload }), [payload, error, reload])\n}\n"],
4
+ "sourcesContent": ["/**\r\n * Browser half of dsh-context-memory: its page inside Settings.\r\n *\r\n * Two services are injected by their runtime identity, not by the packages that provide\r\n * them: the settings shell is `slots`, the dictionary registry is `locale`. The label and\r\n * every string inside the component are translated through this plugin's own namespace,\r\n * which is why the page follows the UI language instead of the plugin author's.\r\n *\r\n * @module client\r\n */\r\n\r\nimport type { ComponentType } from 'react'\r\n\r\nimport { NS, en, zh } from './locales.js'\r\nimport { ContextMemoryPanel } from './view.js'\r\n\r\nexport const inject = ['slots', 'locale']\r\n\r\ninterface SlotsService {\r\n inject(name: string, register: () => void): void\r\n register(\r\n options: { name: string; id: string; order: number; label: () => string; locale: string },\r\n component: ComponentType<{ t: (key: string, params?: Record<string, unknown>) => string }>,\r\n ): unknown\r\n}\r\n\r\ninterface ClientContext {\r\n slots: SlotsService\r\n locale: {\r\n register(ns: string, dicts: { zh: unknown; en: unknown }): () => void\r\n bind(ns: string): (key: string, params?: Record<string, unknown>) => string\r\n }\r\n effect(callback: () => unknown, label?: string): unknown\r\n}\r\n\r\nexport function apply(ctx: ClientContext): void {\r\n // `zh` is the key-set source of truth, matching the official client plugins.\r\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-context-memory: dictionaries')\r\n\r\n ctx.slots.inject('settings.section', () => ctx.slots.register(\r\n {\r\n name: 'settings.section',\r\n id: 'context-memory',\r\n order: 45,\r\n label: () => ctx.locale.bind(NS)('nav'),\r\n locale: NS,\r\n },\r\n ContextMemoryPanel,\r\n ))\r\n}\r\n", "/**\r\n * Dictionaries for the Context memory page.\r\n *\r\n * `zh` is the key-set source of truth, as in the official client plugins, and `en` is\r\n * typed against it: a key translated in one language but not the other fails the build\r\n * instead of silently rendering the raw key.\r\n *\r\n * @module client/locales\r\n */\r\n\r\n/** Dictionary namespace owned by this plugin. */\r\nexport const NS = 'contextMemory'\r\n\r\n/** Simplified Chinese dictionary (the key-set source of truth). */\r\nexport const zh = {\r\n 'nav': '\u8DE8\u4F1A\u8BDD\u8BB0\u5FC6',\r\n 'title': '\u8DE8\u4F1A\u8BDD\u8BB0\u5FC6',\r\n 'active': '\u751F\u6548\u4E2D',\r\n 'superseded': '\u5DF2\u5931\u6548',\r\n 'conflicts': '\u51B2\u7A81',\r\n 'noConflicts': '\u6CA1\u6709\u51B2\u7A81\u3002',\r\n 'memory': '\u8BB0\u5FC6',\r\n 'kind': '\u7C7B\u578B',\r\n 'status': '\u6821\u9A8C\u72B6\u6001',\r\n 'from': '\u6765\u6E90',\r\n 'checkNone': '\u65E0\u68C0\u67E5',\r\n 'checkPassed': '\u5DF2\u6838\u5BF9\uFF0C\u6210\u7ACB',\r\n 'checkViolated': '\u5DF2\u6838\u5BF9\uFF0C\u4E0D\u6210\u7ACB',\r\n 'checkBroken': '\u68C0\u67E5\u65E0\u6CD5\u8FD0\u884C',\r\n 'checkPending': '\u5C1A\u672A\u6838\u5BF9',\r\n 'unverified': '\u672A\u9A8C\u8BC1\u6210\u7ACB',\r\n 'empty': '\u8FD8\u6CA1\u6709\u8BB0\u4F4F\u4EFB\u4F55\u5185\u5BB9\u3002',\r\n 'checkLegend': '\u65E0\u68C0\u67E5\uFF1D\u65E0\u6CD5\u7F16\u8BD1\u68C0\u67E5\uFF1B\u68C0\u67E5\u65E0\u6CD5\u8FD0\u884C\uFF1D\u68C0\u67E5\u672C\u8EAB\u6709\u95EE\u9898\uFF0C\u4E0D\u4EE3\u8868\u8BB0\u5FC6\u9519\u8BEF\u3002',\r\n 'refresh': '\u5237\u65B0',\r\n 'loading': '\u6B63\u5728\u52A0\u8F7D\u2026',\r\n 'failed': '\u52A0\u8F7D\u5931\u8D25',\r\n 'retry': '\u91CD\u8BD5',\r\n 'searchPlaceholder': '\u641C\u7D22\u8BB0\u5FC6\u2026',\r\n 'search': '\u641C\u7D22',\r\n 'searchResults': '\u641C\u7D22\u7ED3\u679C',\r\n 'noResults': '\u6CA1\u6709\u5339\u914D\u7684\u8BB0\u5FC6\u3002',\r\n 'addMemory': '\u6DFB\u52A0\u8BB0\u5FC6',\r\n 'memoryText': '\u8BB0\u5FC6\u5185\u5BB9',\r\n 'subjectOptional': '\u4E3B\u9898\uFF08\u53EF\u9009\uFF09',\r\n 'kindFact': '\u4E8B\u5B9E',\r\n 'kindDecision': '\u51B3\u7B56',\r\n 'kindPreference': '\u504F\u597D',\r\n 'kindPitfall': '\u9677\u9631',\r\n 'add': '\u6DFB\u52A0',\r\n 'forget': '\u5E9F\u5F03',\r\n 'resolve': '\u89E3\u51B3',\r\n 'integration': '\u96C6\u6210',\r\n 'autoCaptureLabel': '\u81EA\u52A8\u6355\u83B7:\u7528\u6237\u6D88\u606F\u91CC\u7684\u300C\u8BB0\u4F4F\u2026\u300D\u300C\u4EE5\u540E\u2026\u300D\u81EA\u52A8\u5165\u5E93',\r\n 'recallInjectLabel': '\u53EC\u56DE\u6CE8\u5165:\u6BCF\u6B21\u8BF7\u6C42\u524D\u628A\u76F8\u5173\u8BB0\u5FC6\u6298\u8FDB\u4E0A\u4E0B\u6587',\r\n}\r\n\r\n/** English dictionary, checked complete against the zh key set. */\r\nexport const en: typeof zh = {\r\n 'nav': 'Context memory',\r\n 'title': 'Context memory',\r\n 'active': 'active',\r\n 'superseded': 'superseded',\r\n 'conflicts': 'Conflicts',\r\n 'noConflicts': 'No conflicts.',\r\n 'memory': 'Memory',\r\n 'kind': 'Kind',\r\n 'status': 'Check',\r\n 'from': 'From',\r\n 'checkNone': 'no check',\r\n 'checkPassed': 'checked, holds',\r\n 'checkViolated': 'checked, does not hold',\r\n 'checkBroken': 'check unusable',\r\n 'checkPending': 'not yet checked',\r\n 'unverified': 'not verified as holding',\r\n 'empty': 'Nothing is remembered yet.',\r\n 'checkLegend': 'no check = no check could be compiled; check unusable = the check is defective, not the memory.',\r\n 'refresh': 'Refresh',\r\n 'loading': 'Loading\u2026',\r\n 'failed': 'Failed to load',\r\n 'retry': 'Retry',\r\n 'searchPlaceholder': 'Search memories\u2026',\r\n 'search': 'Search',\r\n 'searchResults': 'Search Results',\r\n 'noResults': 'No matching memories.',\r\n 'addMemory': 'Add Memory',\r\n 'memoryText': 'Memory text',\r\n 'subjectOptional': 'Subject (optional)',\r\n 'kindFact': 'Fact',\r\n 'kindDecision': 'Decision',\r\n 'kindPreference': 'Preference',\r\n 'kindPitfall': 'Pitfall',\r\n 'add': 'Add',\r\n 'forget': 'Forget',\r\n 'resolve': 'Resolve',\r\n 'integration': 'Integration',\r\n 'autoCaptureLabel': 'Auto capture: \"remember that\u2026\" statements land in the log',\r\n 'recallInjectLabel': 'Recall injection: fold relevant memories into each request',\r\n}\r\n", "/**\r\n * Pure rendering half of the contextMemory page. Uses shared UI kit.\r\n * @module client/view\r\n */\r\n\r\nimport { useCallback, useEffect, useState } from 'react'\r\nimport type { ReactNode } from 'react'\r\n\r\nimport type { MemoryState, PanelPayload, RecallHit } from '../types.js'\r\nimport {\r\n Badge, Button, Card, ConfirmDialog, EmptyState, Field, Input, Modal,\r\n SectionTitle, Select, Spinner, StatCard, ToastProvider, tableStyles,\r\n usePanel, useToast,\r\n} from './ui.js'\r\n\r\nexport type Translate = (key: string, params?: Record<string, unknown>) => string\r\nexport interface PanelProps { t: Translate }\r\n\r\nconst PANEL_PATH = '/api/context-memory.panel'\r\nconst RECALL_PATH = '/api/context-memory.recall'\r\nconst REMEMBER_PATH = '/api/context-memory.remember'\r\nconst FORGET_PATH = '/api/context-memory.forget'\r\nconst PREFS_PATH = '/api/context-memory.prefs'\r\n\r\nexport function checkLabel(memory: MemoryState, t: Translate): string {\r\n if (!memory.guard) return t('checkNone')\r\n switch (memory.verification?.outcome) {\r\n case 'passed': return t('checkPassed')\r\n case 'violated': return t('checkViolated')\r\n case 'broken': return t('checkBroken')\r\n default: return t('checkPending')\r\n }\r\n}\r\n\r\nexport function provenance(memory: MemoryState): string {\r\n return [memory.source, memory.sessionId !== undefined ? `session ${memory.sessionId}` : null, memory.step !== undefined ? `step ${String(memory.step)}` : null].filter((part) => part !== null).join(' \u00B7 ')\r\n}\r\n\r\nfunction AddMemoryModal({ t, onClose, onAdded }: { t: Translate; onClose: () => void; onAdded: () => void }): ReactNode {\r\n const toast = useToast()\r\n const [text, setText] = useState('')\r\n const [subject, setSubject] = useState('')\r\n const [kind, setKind] = useState('fact')\r\n const [adding, setAdding] = useState(false)\r\n\r\n const handleAdd = useCallback(async () => {\r\n if (!text.trim()) return\r\n setAdding(true)\r\n try {\r\n const r = await fetch(REMEMBER_PATH, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text, subject: subject || undefined, kind, source: 'user' }) })\r\n if (r.ok) { toast('success', t('memoryAdded')); onAdded(); onClose() }\r\n } finally { setAdding(false) }\r\n }, [text, subject, kind, t, toast, onAdded, onClose])\r\n\r\n return (\r\n <Modal title={t('addMemory')} onClose={onClose} width={500}\r\n footer={<><Button variant=\"secondary\" onClick={onClose}>{t('cancel')}</Button>\r\n <Button variant=\"primary\" disabled={adding || !text.trim()} onClick={handleAdd}>{adding ? <Spinner size={14} /> : null} {t('add')}</Button></>}>\r\n <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>\r\n <Field label={t('memoryText')}><Input value={text} onChange={(e) => setText(e.target.value)} placeholder={t('memoryTextPlaceholder')} /></Field>\r\n <Field label={t('subjectOptional')}><Input value={subject} onChange={(e) => setSubject(e.target.value)} placeholder={t('subjectPlaceholder')} /></Field>\r\n <Field label={t('kind')}>\r\n <Select value={kind} onChange={(e) => setKind(e.target.value)} style={{ width: '100%' }}>\r\n <option value=\"fact\">{t('kindFact')}</option>\r\n <option value=\"decision\">{t('kindDecision')}</option>\r\n <option value=\"preference\">{t('kindPreference')}</option>\r\n <option value=\"pitfall\">{t('kindPitfall')}</option>\r\n </Select>\r\n </Field>\r\n </div>\r\n </Modal>\r\n )\r\n}\r\n\r\nfunction ContextMemoryPanelInner({ t }: PanelProps): ReactNode {\r\n const { payload, error, reload } = usePanel<PanelPayload>(PANEL_PATH)\r\n const toast = useToast()\r\n const [searchQuery, setSearchQuery] = useState('')\r\n const [searchHits, setSearchHits] = useState<RecallHit[] | null>(null)\r\n const [searching, setSearching] = useState(false)\r\n const [prefs, setPrefsState] = useState<{ autoCapture: boolean; recallInject: boolean } | null>(null)\r\n\r\n const togglePref = useCallback(async (key: 'autoCapture' | 'recallInject') => {\r\n const r = await fetch(PREFS_PATH, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ [key]: !(prefs?.[key] ?? true) }) })\r\n if (r.ok) setPrefsState(await r.json() as { autoCapture: boolean; recallInject: boolean })\r\n }, [prefs])\r\n\r\n useEffect(() => { void fetch(PREFS_PATH).then(async (r) => { if (r.ok) setPrefsState(await r.json() as { autoCapture: boolean; recallInject: boolean }) }) }, [])\r\n const [addOpen, setAddOpen] = useState(false)\r\n const [forgetTarget, setForgetTarget] = useState<{ id: string; reason: string } | null>(null)\r\n\r\n const handleSearch = useCallback(async () => {\r\n if (!searchQuery.trim()) { setSearchHits(null); return }\r\n setSearching(true)\r\n try { const r = await fetch(`${RECALL_PATH}?q=${encodeURIComponent(searchQuery)}&limit=10`); if (r.ok) { setSearchHits((await r.json() as { hits: RecallHit[] }).hits) } }\r\n finally { setSearching(false) }\r\n }, [searchQuery])\r\n\r\n const handleForget = useCallback(async () => {\r\n if (forgetTarget === null) return\r\n const r = await fetch(FORGET_PATH, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(forgetTarget) })\r\n if (r.ok) { toast('success', t('forgotten')); setForgetTarget(null); reload() }\r\n }, [forgetTarget, t, toast, reload])\r\n\r\n const header = (\r\n <header style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 4 }}>\r\n <strong style={{ fontSize: 15 }}>\uD83E\uDDE0 {t('title')}</strong>\r\n <span style={{ flex: 1 }} />\r\n <Button variant=\"primary\" size=\"sm\" onClick={() => setAddOpen(true)}>\u2795 {t('addMemory')}</Button>\r\n <Button variant=\"secondary\" size=\"sm\" onClick={reload}>{t('refresh')}</Button>\r\n </header>\r\n )\r\n\r\n if (error !== null) return <div style={{ maxWidth: 820 }}>{header}<Card><p role=\"alert\" style={{ margin: 0, fontSize: 13, color: 'var(--dsw-alias-state-error-primary)' }}>{t('failed')}: {error}</p></Card></div>\r\n if (payload === null) return <div style={{ maxWidth: 820 }}>{header}<div style={{ display: 'flex', justifyContent: 'center', padding: 40 }}><Spinner size={28} /></div></div>\r\n\r\n return (\r\n <div style={{ display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 820 }}>\r\n {header}\r\n\r\n <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>\r\n <StatCard value={payload.totals.active} label={t('active')} />\r\n <StatCard value={payload.totals.verified} label={t('checkPassed')} />\r\n <StatCard value={payload.totals.unverified} label={t('unverified')} />\r\n <StatCard value={payload.totals.superseded} label={t('superseded')} />\r\n </div>\r\n\r\n {/* Integration switches */}\r\n <Card title={t('integration')} icon=\"\u2699\uFE0F\">\r\n <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>\r\n {([['autoCapture', 'autoCaptureLabel'], ['recallInject', 'recallInjectLabel']] as const).map(([key, labelKey]) => (\r\n <label key={key} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: 'pointer' }}>\r\n <input type=\"checkbox\" checked={prefs?.[key] ?? true} onChange={() => togglePref(key)} />\r\n {t(labelKey)}\r\n </label>\r\n ))}\r\n </div>\r\n </Card>\r\n\r\n {/* Search */}\r\n <Card title={t('search')} icon=\"\uD83D\uDD0D\">\r\n <div style={{ display: 'flex', gap: 8 }}>\r\n <Input value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder={t('searchPlaceholder')}\r\n onKeyDown={(e) => { if (e.key === 'Enter') handleSearch() }} />\r\n <Button variant=\"secondary\" onClick={handleSearch} disabled={searching}>{searching ? <Spinner size={14} /> : null} {t('search')}</Button>\r\n </div>\r\n {searchHits !== null && (\r\n <div style={{ marginTop: 10 }}>\r\n {searchHits.length === 0 ? <EmptyState message={t('noResults')} /> : (\r\n <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>\r\n {searchHits.map((hit) => (\r\n <div key={hit.memory.id} style={{ display: 'flex', gap: 8, alignItems: 'baseline' }}>\r\n <Badge color=\"info\">{hit.score.toFixed(2)}</Badge>\r\n <span style={{ fontSize: 12 }}>{hit.memory.text}</span>\r\n <span style={{ fontSize: 10, opacity: 0.5 }}>\u2014 {hit.why}</span>\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n </Card>\r\n\r\n {/* Conflicts */}\r\n {payload.conflicts.length > 0 && (\r\n <>\r\n <SectionTitle icon=\"\u26A0\uFE0F\">{t('conflicts')}</SectionTitle>\r\n {payload.conflicts.map((conflict) => (\r\n <Card key={conflict.subject} title={conflict.subject}>\r\n {conflict.memories.map((memory) => (\r\n <div key={memory.id} style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>\r\n <code style={{ fontSize: 11 }}>{memory.id}</code>\r\n <span style={{ fontSize: 12, flex: 1 }}>{memory.text}</span>\r\n <Button variant=\"danger\" size=\"sm\" onClick={() => setForgetTarget({ id: memory.id, reason: `conflict resolved: ${conflict.subject}` })}>{t('resolve')}</Button>\r\n </div>\r\n ))}\r\n </Card>\r\n ))}\r\n </>\r\n )}\r\n\r\n {/* Active memories */}\r\n <SectionTitle icon=\"\u2705\">{t('active')}</SectionTitle>\r\n {payload.active.length === 0 ? <EmptyState icon=\"\uD83D\uDCED\" message={t('empty')} /> : (\r\n <Card padding={0}>\r\n <table style={tableStyles.table}>\r\n <thead><tr><th style={tableStyles.th}>Id</th><th style={tableStyles.th}>{t('memory')}</th><th style={tableStyles.th}>{t('kind')}</th><th style={tableStyles.th}>{t('status')}</th><th style={tableStyles.th}>{t('from')}</th><th style={tableStyles.th} /></tr></thead>\r\n <tbody>\r\n {payload.active.map((memory) => (\r\n <tr key={memory.id}>\r\n <td style={tableStyles.td}><code style={{ fontSize: 11 }}>{memory.id}</code></td>\r\n <td style={tableStyles.td}>{memory.text}</td>\r\n <td style={tableStyles.td}><Badge>{memory.kind}</Badge></td>\r\n <td style={tableStyles.td}>{checkLabel(memory, t)}</td>\r\n <td style={{ ...tableStyles.td, fontSize: 11, opacity: 0.6 }}>{provenance(memory)}</td>\r\n <td style={tableStyles.td}><Button variant=\"danger\" size=\"sm\" onClick={() => setForgetTarget({ id: memory.id, reason: 'superseded from panel' })}>\uD83D\uDDD1</Button></td>\r\n </tr>\r\n ))}\r\n </tbody>\r\n </table>\r\n </Card>\r\n )}\r\n\r\n {payload.superseded.length > 0 && (\r\n <>\r\n <SectionTitle icon=\"\uD83D\uDCE6\">{t('superseded')} ({payload.superseded.length})</SectionTitle>\r\n <Card padding={0}>\r\n <table style={tableStyles.table}>\r\n <thead><tr><th style={tableStyles.th}>Id</th><th style={tableStyles.th}>{t('memory')}</th><th style={tableStyles.th}>{t('kind')}</th><th style={tableStyles.th}>{t('status')}</th></tr></thead>\r\n <tbody>\r\n {payload.superseded.map((memory) => (\r\n <tr key={memory.id}><td style={tableStyles.td}><code style={{ fontSize: 11 }}>{memory.id}</code></td><td style={tableStyles.td}>{memory.text}</td><td style={tableStyles.td}><Badge>{memory.kind}</Badge></td><td style={tableStyles.td}>{checkLabel(memory, t)}</td></tr>\r\n ))}\r\n </tbody>\r\n </table>\r\n </Card>\r\n </>\r\n )}\r\n\r\n {addOpen && <AddMemoryModal t={t} onClose={() => setAddOpen(false)} onAdded={reload} />}\r\n {forgetTarget !== null && <ConfirmDialog title={t('forget')} message={t('confirmForget')} confirmLabel={t('forget')} danger onConfirm={handleForget} onClose={() => setForgetTarget(null)} />}\r\n </div>\r\n )\r\n}\r\n\r\nexport function ContextMemoryPanel({ t }: PanelProps): ReactNode {\r\n return <ToastProvider><ContextMemoryPanelInner t={t} /></ToastProvider>\r\n}\r\n", "/**\r\n * Shared UI component kit for dsh plugin panels.\r\n *\r\n * Self-contained: imports nothing but React. All styling is inline + CSS custom\r\n * properties so the components follow the host theme. A single <style> tag injects\r\n * keyframes the first time any component mounts.\r\n *\r\n * @module client/ui\r\n */\r\n\r\nimport {\r\n createContext,\r\n useCallback,\r\n useContext,\r\n useEffect,\r\n useMemo,\r\n useState,\r\n} from 'react'\r\nimport type {\r\n ButtonHTMLAttributes,\r\n CSSProperties,\r\n InputHTMLAttributes,\r\n ReactNode,\r\n SelectHTMLAttributes,\r\n} from 'react'\r\n\r\n// \u2500\u2500\u2500 Theme tokens \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n/**\r\n * Host theme tokens.\r\n *\r\n * Every name here is a real dsh token, read out of the shipped stylesheets\r\n * (`@deepseek-ai/dsh-client-ui-theme`), which define them on `body`. An earlier\r\n * version used invented names \u2014 `--accent`, `--border`, `--bg-primary`,\r\n * `--text-primary` and friends \u2014 that dsh does not define at all. The\r\n * hardcoded fallbacks then applied in every theme, so each panel rendered its\r\n * own fixed palette instead of following the host, and the modal and input\r\n * surfaces stayed white in dark mode.\r\n *\r\n * No fallbacks on purpose: if a name were ever missing, the declaration becomes\r\n * invalid at computed-value time and the property inherits, which degrades\r\n * gracefully, whereas a hardcoded fallback bakes in a color that is wrong in one\r\n * of the two themes. `tests/ui-tokens.test.mjs` enforces the name list.\r\n */\r\nconst T = {\r\n accent: 'var(--dsw-alias-brand-primary)',\r\n accentHover: 'var(--dsw-alias-button-primary-hover)',\r\n inverted: 'var(--dsw-alias-label-primary-inverted)',\r\n error: 'var(--dsw-alias-state-error-primary)',\r\n success: 'var(--dsw-alias-state-success-primary)',\r\n warning: 'var(--dsw-alias-state-warn-primary)',\r\n bg: 'var(--dsw-alias-bg-layer-2)',\r\n bgHover: 'var(--dsw-alias-interactive-bg-hover)',\r\n border: 'var(--dsw-alias-border-l2)',\r\n text: 'var(--dsw-alias-label-primary)',\r\n muted: 'var(--dsw-alias-label-tertiary)',\r\n surface: 'var(--dsw-alias-bg-layer-1)',\r\n mask: 'var(--dsw-alias-bg-mask-1)',\r\n}\r\n\r\n// \u2500\u2500\u2500 Style injection (once) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nlet injected = false\r\nfunction ensureStyles(): void {\r\n if (injected || typeof document === 'undefined') return\r\n injected = true\r\n const el = document.createElement('style')\r\n el.textContent = `\r\n@keyframes dsh-fade-in{from{opacity:0}to{opacity:1}}\r\n@keyframes dsh-slide-up{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}\r\n@keyframes dsh-spin{to{transform:rotate(360deg)}}\r\n.dsh-modal-overlay{animation:dsh-fade-in .15s ease}\r\n.dsh-modal-body{animation:dsh-slide-up .2s ease}\r\n.dsh-toast{animation:dsh-slide-up .2s ease}\r\n.dsh-spin{animation:dsh-spin .6s linear infinite}\r\n `\r\n document.head.appendChild(el)\r\n}\r\n\r\n// \u2500\u2500\u2500 Modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function Modal({\r\n title,\r\n onClose,\r\n children,\r\n footer,\r\n width = 520,\r\n}: {\r\n title: string\r\n onClose: () => void\r\n children: ReactNode\r\n footer?: ReactNode\r\n width?: number\r\n}): ReactNode {\r\n useEffect(() => {\r\n ensureStyles()\r\n const handler = (e: KeyboardEvent): void => {\r\n if (e.key === 'Escape') onClose()\r\n }\r\n document.addEventListener('keydown', handler)\r\n return () => document.removeEventListener('keydown', handler)\r\n }, [onClose])\r\n\r\n return (\r\n <div\r\n className=\"dsh-modal-overlay\"\r\n style={{\r\n position: 'fixed',\r\n inset: 0,\r\n zIndex: 1000,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: T.mask,\r\n backdropFilter: 'blur(3px)',\r\n WebkitBackdropFilter: 'blur(3px)',\r\n }}\r\n onClick={onClose}\r\n >\r\n <div\r\n className=\"dsh-modal-body\"\r\n style={{\r\n background: T.surface,\r\n borderRadius: 14,\r\n boxShadow: 'var(--dsw-elevation-prominent)',\r\n width: `min(92vw, ${width}px)`,\r\n maxHeight: '85vh',\r\n display: 'flex',\r\n flexDirection: 'column',\r\n overflow: 'hidden',\r\n }}\r\n onClick={(e) => e.stopPropagation()}\r\n >\r\n <div\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'space-between',\r\n padding: '16px 22px',\r\n borderBottom: `1px solid ${T.border}`,\r\n }}\r\n >\r\n <h3 style={{ margin: 0, fontSize: 15, fontWeight: 600 }}>{title}</h3>\r\n <button\r\n type=\"button\"\r\n onClick={onClose}\r\n style={{\r\n background: 'none',\r\n border: 'none',\r\n cursor: 'pointer',\r\n fontSize: 20,\r\n color: T.muted,\r\n padding: '0 4px',\r\n lineHeight: 1,\r\n borderRadius: 4,\r\n }}\r\n aria-label=\"Close\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n <div style={{ padding: 22, overflow: 'auto', flex: 1 }}>{children}</div>\r\n {footer !== undefined && (\r\n <div\r\n style={{\r\n padding: '14px 22px',\r\n borderTop: `1px solid ${T.border}`,\r\n display: 'flex',\r\n justifyContent: 'flex-end',\r\n gap: 10,\r\n background: T.bg,\r\n }}\r\n >\r\n {footer}\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 ConfirmDialog \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function ConfirmDialog({\r\n title,\r\n message,\r\n confirmLabel,\r\n cancelLabel = 'Cancel',\r\n onConfirm,\r\n onClose,\r\n danger,\r\n}: {\r\n title: string\r\n message: string\r\n confirmLabel: string\r\n cancelLabel?: string\r\n onConfirm: () => void\r\n onClose: () => void\r\n danger?: boolean\r\n}): ReactNode {\r\n return (\r\n <Modal\r\n title={title}\r\n onClose={onClose}\r\n width={420}\r\n footer={\r\n <>\r\n <Button variant=\"secondary\" onClick={onClose}>\r\n {cancelLabel}\r\n </Button>\r\n <Button\r\n variant={danger === true ? 'danger' : 'primary'}\r\n onClick={() => {\r\n onConfirm()\r\n onClose()\r\n }}\r\n >\r\n {confirmLabel}\r\n </Button>\r\n </>\r\n }\r\n >\r\n <p style={{ margin: 0, fontSize: 14, lineHeight: 1.6 }}>{message}</p>\r\n </Modal>\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 Button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function Button({\r\n variant = 'secondary',\r\n size = 'md',\r\n children,\r\n style,\r\n ...rest\r\n}: {\r\n variant?: 'primary' | 'secondary' | 'danger' | 'ghost'\r\n size?: 'sm' | 'md'\r\n children: ReactNode\r\n style?: CSSProperties\r\n} & ButtonHTMLAttributes<HTMLButtonElement>): ReactNode {\r\n const base: CSSProperties = {\r\n cursor: 'pointer',\r\n borderRadius: 7,\r\n fontWeight: 500,\r\n transition: 'all 0.15s ease',\r\n border: 'none',\r\n display: 'inline-flex',\r\n alignItems: 'center',\r\n gap: 4,\r\n fontSize: size === 'sm' ? 12 : 13,\r\n padding: size === 'sm' ? '5px 12px' : '8px 18px',\r\n fontFamily: 'inherit',\r\n }\r\n const variants: Record<string, CSSProperties> = {\r\n primary: { background: T.accent, color: T.inverted },\r\n secondary: {\r\n background: T.bg,\r\n color: T.text,\r\n border: `1px solid ${T.border}`,\r\n },\r\n danger: {\r\n background: 'transparent',\r\n color: T.error,\r\n border: `1px solid ${T.error}`,\r\n },\r\n ghost: { background: 'transparent', color: T.muted, border: 'none' },\r\n }\r\n return (\r\n <button type=\"button\" style={{ ...base, ...variants[variant], ...style }} {...rest}>\r\n {children}\r\n </button>\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 Card \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function Card({\r\n title,\r\n icon,\r\n children,\r\n actions,\r\n style,\r\n padding = 14,\r\n}: {\r\n title?: string\r\n icon?: string\r\n children: ReactNode\r\n actions?: ReactNode\r\n style?: CSSProperties\r\n padding?: number\r\n}): ReactNode {\r\n return (\r\n <div\r\n style={{\r\n borderRadius: 10,\r\n border: `1px solid ${T.border}`,\r\n background: T.bg,\r\n overflow: 'hidden',\r\n ...style,\r\n }}\r\n >\r\n {(title !== undefined || actions !== undefined) && (\r\n <div\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'space-between',\r\n padding: '12px 16px',\r\n borderBottom: title !== undefined ? `1px solid ${T.border}` : 'none',\r\n }}\r\n >\r\n <span style={{ fontSize: 13, fontWeight: 600, display: 'flex', alignItems: 'center', gap: 6 }}>\r\n {icon !== undefined && <span>{icon}</span>}\r\n {title}\r\n </span>\r\n {actions}\r\n </div>\r\n )}\r\n <div style={{ padding }}>{children}</div>\r\n </div>\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 StatCard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function StatCard({\r\n value,\r\n label,\r\n color,\r\n unit,\r\n}: {\r\n value: string | number\r\n label: string\r\n color?: string\r\n unit?: string\r\n}): ReactNode {\r\n return (\r\n <div\r\n style={{\r\n flex: 1,\r\n minWidth: 90,\r\n textAlign: 'center',\r\n padding: '14px 10px',\r\n borderRadius: 10,\r\n border: `1px solid ${T.border}`,\r\n background: T.bg,\r\n }}\r\n >\r\n <div style={{ fontSize: 24, fontWeight: 700, color: color ?? T.text, lineHeight: 1.2 }}>\r\n {value}\r\n {unit !== undefined && (\r\n <span style={{ fontSize: 13, fontWeight: 500, opacity: 0.7, marginLeft: 2 }}>{unit}</span>\r\n )}\r\n </div>\r\n <div style={{ fontSize: 11, color: T.muted, marginTop: 4 }}>{label}</div>\r\n </div>\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 Badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function Badge({\r\n children,\r\n color = 'default',\r\n}: {\r\n children: ReactNode\r\n color?: 'default' | 'success' | 'error' | 'warning' | 'info'\r\n}): ReactNode {\r\n const colors: Record<string, { bg: string; fg: string }> = {\r\n default: { bg: T.bgHover, fg: T.muted },\r\n success: { bg: 'var(--dsw-alias-state-success-tertiary)', fg: T.success },\r\n error: { bg: 'var(--dsw-alias-state-error-secondary)', fg: T.error },\r\n warning: { bg: 'var(--dsw-alias-state-warn-tertiary)', fg: T.warning },\r\n info: { bg: 'var(--dsw-alias-state-business-tertiary)', fg: T.accent },\r\n }\r\n const c = colors[color]\r\n return (\r\n <span\r\n style={{\r\n display: 'inline-block',\r\n padding: '2px 9px',\r\n borderRadius: 10,\r\n fontSize: 11,\r\n fontWeight: 600,\r\n background: c.bg,\r\n color: c.fg,\r\n }}\r\n >\r\n {children}\r\n </span>\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 Input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function Input({\r\n style,\r\n ...rest\r\n}: { style?: CSSProperties } & InputHTMLAttributes<HTMLInputElement>): ReactNode {\r\n return (\r\n <input\r\n {...rest}\r\n style={{\r\n fontSize: 13,\r\n padding: '8px 12px',\r\n borderRadius: 7,\r\n border: `1px solid ${T.border}`,\r\n background: T.surface,\r\n color: T.text,\r\n width: '100%',\r\n boxSizing: 'border-box',\r\n fontFamily: 'inherit',\r\n outline: 'none',\r\n transition: 'border-color 0.15s',\r\n ...style,\r\n }}\r\n />\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 Select \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function Select({\r\n style,\r\n children,\r\n ...rest\r\n}: {\r\n style?: CSSProperties\r\n children: ReactNode\r\n} & SelectHTMLAttributes<HTMLSelectElement>): ReactNode {\r\n return (\r\n <select\r\n {...rest}\r\n style={{\r\n fontSize: 13,\r\n padding: '8px 12px',\r\n borderRadius: 7,\r\n border: `1px solid ${T.border}`,\r\n background: T.surface,\r\n color: T.text,\r\n cursor: 'pointer',\r\n fontFamily: 'inherit',\r\n outline: 'none',\r\n ...style,\r\n }}\r\n >\r\n {children}\r\n </select>\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 Textarea \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function Textarea({\r\n style,\r\n ...rest\r\n}: { style?: CSSProperties } & React.TextareaHTMLAttributes<HTMLTextAreaElement>): ReactNode {\r\n return (\r\n <textarea\r\n {...rest}\r\n style={{\r\n fontSize: 13,\r\n padding: '8px 12px',\r\n borderRadius: 7,\r\n border: `1px solid ${T.border}`,\r\n background: T.surface,\r\n color: T.text,\r\n width: '100%',\r\n boxSizing: 'border-box',\r\n fontFamily: 'inherit',\r\n outline: 'none',\r\n resize: 'vertical',\r\n lineHeight: 1.5,\r\n ...style,\r\n }}\r\n />\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 Toast \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\ninterface ToastItem {\r\n id: number\r\n type: 'success' | 'error' | 'info'\r\n message: string\r\n}\r\n\r\nconst ToastContext = createContext<(type: ToastItem['type'], message: string) => void>(\r\n () => {},\r\n)\r\n\r\nexport function ToastProvider({ children }: { children: ReactNode }): ReactNode {\r\n const [toasts, setToasts] = useState<ToastItem[]>([])\r\n const show = useCallback((type: ToastItem['type'], message: string) => {\r\n const id = Date.now() + Math.random()\r\n setToasts((t) => [...t, { id, type, message }])\r\n setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 3500)\r\n }, [])\r\n return (\r\n <ToastContext.Provider value={show}>\r\n {children}\r\n {toasts.length > 0 && (\r\n <div\r\n style={{\r\n position: 'fixed',\r\n bottom: 24,\r\n right: 24,\r\n zIndex: 2000,\r\n display: 'flex',\r\n flexDirection: 'column',\r\n gap: 8,\r\n }}\r\n >\r\n {toasts.map((t) => (\r\n <div\r\n key={t.id}\r\n className=\"dsh-toast\"\r\n style={{\r\n padding: '12px 18px',\r\n borderRadius: 8,\r\n fontSize: 13,\r\n fontWeight: 500,\r\n boxShadow: 'var(--dsw-elevation-panel)',\r\n color: T.inverted,\r\n background:\r\n t.type === 'success'\r\n ? T.success\r\n : t.type === 'error'\r\n ? T.error\r\n : T.accent,\r\n maxWidth: 360,\r\n }}\r\n >\r\n {t.message}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </ToastContext.Provider>\r\n )\r\n}\r\n\r\nexport function useToast(): (type: ToastItem['type'], message: string) => void {\r\n return useContext(ToastContext)\r\n}\r\n\r\n// \u2500\u2500\u2500 Spinner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function Spinner({ size = 16 }: { size?: number }): ReactNode {\r\n ensureStyles()\r\n return (\r\n <span\r\n className=\"dsh-spin\"\r\n style={{\r\n display: 'inline-block',\r\n width: size,\r\n height: size,\r\n border: `2px solid ${T.border}`,\r\n borderTopColor: T.accent,\r\n borderRadius: '50%',\r\n }}\r\n />\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 EmptyState \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function EmptyState({\r\n icon,\r\n message,\r\n}: {\r\n icon?: string\r\n message: string\r\n}): ReactNode {\r\n return (\r\n <div\r\n style={{\r\n textAlign: 'center',\r\n padding: '36px 16px',\r\n color: T.muted,\r\n }}\r\n >\r\n {icon !== undefined && (\r\n <div style={{ fontSize: 36, marginBottom: 10, opacity: 0.4 }}>{icon}</div>\r\n )}\r\n <div style={{ fontSize: 13 }}>{message}</div>\r\n </div>\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 SectionTitle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function SectionTitle({\r\n icon,\r\n children,\r\n actions,\r\n}: {\r\n icon?: string\r\n children: ReactNode\r\n actions?: ReactNode\r\n}): ReactNode {\r\n return (\r\n <div\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'space-between',\r\n marginTop: 6,\r\n marginBottom: 8,\r\n }}\r\n >\r\n <span\r\n style={{\r\n fontSize: 14,\r\n fontWeight: 600,\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: 6,\r\n }}\r\n >\r\n {icon !== undefined && <span>{icon}</span>}\r\n {children}\r\n </span>\r\n {actions}\r\n </div>\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 Table helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport const tableStyles = {\r\n table: {\r\n borderCollapse: 'collapse' as const,\r\n width: '100%' as const,\r\n },\r\n th: {\r\n textAlign: 'left' as const,\r\n padding: '8px 12px 8px 0',\r\n fontWeight: 600 as const,\r\n fontSize: 12 as const,\r\n color: T.muted,\r\n borderBottom: `1px solid ${T.border}`,\r\n },\r\n td: {\r\n padding: '8px 12px 8px 0',\r\n fontSize: 13 as const,\r\n borderBottom: `1px solid ${T.border}`,\r\n },\r\n clickRow: { cursor: 'pointer' as const },\r\n}\r\n\r\n// \u2500\u2500\u2500 CodeBlock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function CodeBlock({\r\n children,\r\n maxHeight = 200,\r\n style,\r\n}: {\r\n children: string\r\n maxHeight?: number\r\n style?: CSSProperties\r\n}): ReactNode {\r\n return (\r\n <pre\r\n style={{\r\n whiteSpace: 'pre-wrap',\r\n wordBreak: 'break-word',\r\n fontSize: 12,\r\n maxHeight,\r\n overflow: 'auto',\r\n padding: 10,\r\n borderRadius: 7,\r\n background: T.bg,\r\n border: `1px solid ${T.border}`,\r\n margin: 0,\r\n ...style,\r\n }}\r\n >\r\n {children}\r\n </pre>\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 ProgressBar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function ProgressBar({\r\n pct,\r\n color,\r\n height = 18,\r\n}: {\r\n pct: number\r\n color?: string\r\n height?: number\r\n}): ReactNode {\r\n return (\r\n <div\r\n style={{\r\n width: '100%',\r\n height,\r\n background: T.bgHover,\r\n borderRadius: 5,\r\n overflow: 'hidden',\r\n position: 'relative',\r\n }}\r\n >\r\n <div\r\n style={{\r\n width: `${Math.min(pct, 100)}%`,\r\n height: '100%',\r\n background: color ?? T.accent,\r\n borderRadius: 5,\r\n transition: 'width 0.3s ease',\r\n }}\r\n />\r\n </div>\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 Field label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function Field({\r\n label,\r\n children,\r\n hint,\r\n}: {\r\n label: string\r\n children: ReactNode\r\n hint?: string\r\n}): ReactNode {\r\n return (\r\n <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>\r\n <label style={{ fontSize: 12, fontWeight: 600, color: T.text }}>{label}</label>\r\n {children}\r\n {hint !== undefined && (\r\n <span style={{ fontSize: 11, color: T.muted }}>{hint}</span>\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\n// \u2500\u2500\u2500 usePanel hook (shared fetch logic) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nexport function usePanel<P>(path: string): {\r\n payload: P | null\r\n error: string | null\r\n reload: () => void\r\n} {\r\n const [payload, setPayload] = useState<P | null>(null)\r\n const [error, setError] = useState<string | null>(null)\r\n const [tick, setTick] = useState(0)\r\n const reload = useCallback(() => setTick((v) => v + 1), [])\r\n\r\n useEffect(() => {\r\n const c = new AbortController()\r\n setError(null)\r\n fetch(path, { signal: c.signal })\r\n .then(async (r) => {\r\n if (!r.ok) throw new Error(String(r.status))\r\n return r.json() as Promise<P>\r\n })\r\n .then((p) => {\r\n if (!c.signal.aborted) setPayload(p)\r\n })\r\n .catch((e: unknown) => {\r\n if (!c.signal.aborted) setPayload(null), setError(e instanceof Error ? e.message : String(e))\r\n })\r\n return () => c.abort()\r\n }, [tick, path])\r\n\r\n return useMemo(() => ({ payload, error, reload }), [payload, error, reload])\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": ["import_react", "import_jsx_runtime"]
7
7
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Message source of the recall context this plugin injects into a step.
3
+ *
4
+ * 0.2.0-rc.2 removed the shared catch-all `plugin` kind: a producer declares its
5
+ * own `kind` by augmenting `MessageSourceMap` in its own module, and a consumer
6
+ * narrows on that kind — the official `dsh-agent-loop` does the same with its
7
+ * `runtime-context` kind. `form` is the producer-independent axis, and `recall`
8
+ * is exactly what this plugin hands the model: material lifted out of another
9
+ * session's log.
10
+ *
11
+ * @module context-memory/source
12
+ */
13
+ import type { ContextFormed } from '@deepseek-ai/dsh-llm';
14
+ import type { UserMessage } from '@deepseek-ai/dsh-session';
15
+ declare module '@deepseek-ai/dsh-llm' {
16
+ interface MessageSourceMap {
17
+ 'dsh-context-memory': {
18
+ kind: 'dsh-context-memory';
19
+ } & ContextFormed;
20
+ }
21
+ }
22
+ /** Source stamped on every message this plugin injects. */
23
+ export declare const recallSource: {
24
+ readonly kind: "dsh-context-memory";
25
+ readonly form: "recall";
26
+ };
27
+ /** True when a message is this plugin's own recall injection. */
28
+ export declare function isRecallMessage(message: UserMessage): boolean;
package/lib/source.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Message source of the recall context this plugin injects into a step.
3
+ *
4
+ * 0.2.0-rc.2 removed the shared catch-all `plugin` kind: a producer declares its
5
+ * own `kind` by augmenting `MessageSourceMap` in its own module, and a consumer
6
+ * narrows on that kind — the official `dsh-agent-loop` does the same with its
7
+ * `runtime-context` kind. `form` is the producer-independent axis, and `recall`
8
+ * is exactly what this plugin hands the model: material lifted out of another
9
+ * session's log.
10
+ *
11
+ * @module context-memory/source
12
+ */
13
+ /** Source stamped on every message this plugin injects. */
14
+ export const recallSource = { kind: 'dsh-context-memory', form: 'recall' };
15
+ /** True when a message is this plugin's own recall injection. */
16
+ export function isRecallMessage(message) {
17
+ return message.source.kind === 'dsh-context-memory' && message.source.form === 'recall';
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context-memory",
3
- "version": "0.6.1",
3
+ "version": "0.7.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",
@@ -51,27 +51,32 @@
51
51
  "author": "hj01857655",
52
52
  "repository": {
53
53
  "type": "git",
54
- "url": "git+https://github.com/hj01857655/dsh-context-memory.git"
54
+ "url": "git+https://github.com/hj01857655/dsh-plugins.git",
55
+ "directory": "packages/dsh-context-memory"
55
56
  },
56
57
  "bugs": {
57
- "url": "https://github.com/hj01857655/dsh-context-memory/issues"
58
+ "url": "https://github.com/hj01857655/dsh-plugins/issues"
58
59
  },
59
- "homepage": "https://github.com/hj01857655/dsh-context-memory#readme",
60
+ "homepage": "https://github.com/hj01857655/dsh-plugins/tree/main/packages/dsh-context-memory#readme",
60
61
  "engines": {
61
62
  "node": ">=20"
62
63
  },
63
64
  "peerDependencies": {
64
- "@deepseek-ai/cordis": "^4.0.1"
65
- },
66
- "dependencies": {
67
- "@deepseek-ai/dsh-llm": "^0.0.1-rc.1"
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"
68
69
  },
69
70
  "devDependencies": {
70
- "@deepseek-ai/cordis": "^4.0.2",
71
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
72
- "@deepseek-ai/dsh-llm": "^0.0.1-rc.1",
73
- "@deepseek-ai/dsh-session": "^0.0.1-rc.1",
74
- "@deepseek-ai/dsh-tools": "^0.0.1-rc.1",
71
+ "@deepseek-ai/cordis": "~4.0.4",
72
+ "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
73
+ "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
74
+ "@deepseek-ai/dsh-brand": "0.2.0-rc.2",
75
+ "@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
76
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
77
+ "@deepseek-ai/dsh-session": "0.2.0-rc.2",
78
+ "@deepseek-ai/dsh-timeout": "0.2.0-rc.2",
79
+ "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
75
80
  "@types/node": "^22.20.3",
76
81
  "@types/react": "^19.3.0",
77
82
  "esbuild": "^0.28.2",