dsh-context-memory 0.6.0 → 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 +120 -106
- package/LICENSE +21 -21
- package/README.md +152 -133
- package/cordis.patch.yml +6 -6
- package/lib/client/ui.js +8 -8
- package/lib/index.js +3 -3
- package/lib/memory.web.js.map +1 -1
- package/lib/source.d.ts +28 -0
- package/lib/source.js +18 -0
- package/package.json +18 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,106 +1,120 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.
|
|
4
|
-
|
|
5
|
-
**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
`
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- `
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
- **
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
[](https://www.npmjs.com/package/dsh-context-memory)
|
|
4
|
-
[](https://www.npmjs.com/package/dsh-context-memory)
|
|
5
|
-
[](https://github.com/hj01857655/dsh-context-memory/actions/workflows/ci.yml)
|
|
6
|
-
[](LICENSE)
|
|
7
|
-
[](package.json)
|
|
8
|
-
[](https://github.com/hj01857655/dsh-context-memory/stargazers)
|
|
9
|
-
[](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
|
-
##
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
1
|
+
# dsh-context-memory
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/dsh-context-memory)
|
|
4
|
+
[](https://www.npmjs.com/package/dsh-context-memory)
|
|
5
|
+
[](https://github.com/hj01857655/dsh-context-memory/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](package.json)
|
|
8
|
+
[](https://github.com/hj01857655/dsh-context-memory/stargazers)
|
|
9
|
+
[](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:
|
|
74
|
+
source: recallSource,
|
|
75
75
|
});
|
|
76
76
|
};
|
|
77
|
-
const isOwnContext = (message) => message
|
|
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')
|
package/lib/memory.web.js.map
CHANGED
|
@@ -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
|
}
|
package/lib/source.d.ts
ADDED
|
@@ -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.
|
|
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,24 +51,32 @@
|
|
|
51
51
|
"author": "hj01857655",
|
|
52
52
|
"repository": {
|
|
53
53
|
"type": "git",
|
|
54
|
-
"url": "git+https://github.com/hj01857655/dsh-
|
|
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-
|
|
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": "
|
|
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"
|
|
65
69
|
},
|
|
66
70
|
"devDependencies": {
|
|
67
|
-
"@deepseek-ai/cordis": "
|
|
68
|
-
"@deepseek-ai/dsh-agent": "
|
|
69
|
-
"@deepseek-ai/dsh-
|
|
70
|
-
"@deepseek-ai/dsh-
|
|
71
|
-
"@deepseek-ai/dsh-
|
|
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",
|
|
72
80
|
"@types/node": "^22.20.3",
|
|
73
81
|
"@types/react": "^19.3.0",
|
|
74
82
|
"esbuild": "^0.28.2",
|