dsh-context-memory 0.2.0 → 0.3.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.
@@ -1,14 +1,15 @@
1
1
  /**
2
- * Browser half of dsh-context-memory: the Context memory page inside Settings.
2
+ * Browser half of dsh-context-memory: its page inside Settings.
3
3
  *
4
- * Services are named by their runtime identity, not by the package that provides them:
5
- * the settings shell is the `slots` service and the host bridge is `connection`. A
6
- * fiber that injects package names never resolves and the plugin stays pending, which
7
- * the loader reports as "did not activate".
4
+ * Two services are injected by their runtime identity, not by the packages that provide
5
+ * them: the settings shell is `slots`, the dictionary registry is `locale`. The label and
6
+ * every string inside the component are translated through this plugin's own namespace,
7
+ * which is why the page follows the UI language instead of the plugin author's.
8
8
  *
9
9
  * @module client
10
10
  */
11
11
  import type { ComponentType } from 'react';
12
+ export declare const inject: string[];
12
13
  interface SlotsService {
13
14
  inject(name: string, register: () => void): void;
14
15
  register(options: {
@@ -16,11 +17,21 @@ interface SlotsService {
16
17
  id: string;
17
18
  order: number;
18
19
  label: () => string;
19
- }, component: ComponentType): unknown;
20
+ locale: string;
21
+ }, component: ComponentType<{
22
+ t: (key: string, params?: Record<string, unknown>) => string;
23
+ }>): unknown;
20
24
  }
21
- /** The slots service is what the settings shell exposes. */
22
- export declare const inject: string[];
23
- export declare function apply(ctx: {
25
+ interface ClientContext {
24
26
  slots: SlotsService;
25
- }): void;
27
+ locale: {
28
+ register(ns: string, dicts: {
29
+ zh: unknown;
30
+ en: unknown;
31
+ }): () => void;
32
+ bind(ns: string): (key: string, params?: Record<string, unknown>) => string;
33
+ };
34
+ effect(callback: () => unknown, label?: string): unknown;
35
+ }
36
+ export declare function apply(ctx: ClientContext): void;
26
37
  export {};
@@ -1,65 +1,24 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
1
  /**
3
- * Browser half of dsh-context-memory: the Context memory page inside Settings.
2
+ * Browser half of dsh-context-memory: its page inside Settings.
4
3
  *
5
- * Services are named by their runtime identity, not by the package that provides them:
6
- * the settings shell is the `slots` service and the host bridge is `connection`. A
7
- * fiber that injects package names never resolves and the plugin stays pending, which
8
- * the loader reports as "did not activate".
4
+ * Two services are injected by their runtime identity, not by the packages that provide
5
+ * them: the settings shell is `slots`, the dictionary registry is `locale`. The label and
6
+ * every string inside the component are translated through this plugin's own namespace,
7
+ * which is why the page follows the UI language instead of the plugin author's.
9
8
  *
10
9
  * @module client
11
10
  */
12
- import { useCallback, useEffect, useState } from 'react';
13
- import { renderPanel } from './view.js';
14
- /** Path the host half registers on the web connection. */
15
- const PANEL_PATH = '/api/context-memory.panel';
16
- /** Fetch the panel payload; `reload` re-runs the request. */
17
- function usePanel() {
18
- const [html, setHtml] = useState(null);
19
- const [error, setError] = useState(null);
20
- const [tick, setTick] = useState(0);
21
- const reload = useCallback(() => setTick((t) => t + 1), []);
22
- useEffect(() => {
23
- const controller = new AbortController();
24
- setError(null);
25
- fetch(PANEL_PATH, { signal: controller.signal })
26
- .then(async (response) => {
27
- if (!response.ok)
28
- throw new Error(`panel request failed with ${response.status}`);
29
- return response.json();
30
- })
31
- .then((payload) => {
32
- if (!controller.signal.aborted)
33
- setHtml(renderPanel(payload));
34
- })
35
- .catch((cause) => {
36
- if (controller.signal.aborted)
37
- return;
38
- // Say what failed rather than rendering an empty panel, which would read as
39
- // "nothing recorded" — a different and wrong answer.
40
- setError(cause instanceof Error ? cause.message : String(cause));
41
- });
42
- return () => controller.abort();
43
- }, [tick]);
44
- return { html, error, reload };
45
- }
46
- function ContextmemoryPage() {
47
- const { html, error, reload } = usePanel();
48
- if (error !== null) {
49
- return (_jsxs("section", { children: [_jsxs("p", { role: "alert", children: ["Context memory panel failed to load: ", error] }), _jsx("button", { type: "button", onClick: reload, children: "Retry" })] }));
50
- }
51
- if (html === null)
52
- return _jsx("p", { "aria-live": "polite", children: "Loading the Context memory panel\u2026" });
53
- // The markup is produced by `renderPanel`, which escapes every interpolated value.
54
- return _jsx("div", { dangerouslySetInnerHTML: { __html: html } });
55
- }
56
- /** The slots service is what the settings shell exposes. */
57
- export const inject = ['slots'];
11
+ import { NS, en, zh } from './locales.js';
12
+ import { ContextMemoryPanel } from './view.js';
13
+ export const inject = ['slots', 'locale'];
58
14
  export function apply(ctx) {
15
+ // `zh` is the key-set source of truth, matching the official client plugins.
16
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-context-memory: dictionaries');
59
17
  ctx.slots.inject('settings.section', () => ctx.slots.register({
60
18
  name: 'settings.section',
61
19
  id: 'context-memory',
62
20
  order: 46,
63
- label: () => 'Context memory',
64
- }, ContextmemoryPage));
21
+ label: () => ctx.locale.bind(NS)('nav'),
22
+ locale: NS,
23
+ }, ContextMemoryPanel));
65
24
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Dictionaries for the Context memory page.
3
+ *
4
+ * `zh` is the key-set source of truth, as in the official client plugins, and `en` is
5
+ * typed against it: a key translated in one language but not the other fails the build
6
+ * instead of silently rendering the raw key.
7
+ *
8
+ * @module client/locales
9
+ */
10
+ /** Dictionary namespace owned by this plugin. */
11
+ export declare const NS = "contextMemory";
12
+ /** Simplified Chinese dictionary (the key-set source of truth). */
13
+ export declare const zh: {
14
+ nav: string;
15
+ title: string;
16
+ active: string;
17
+ superseded: string;
18
+ conflicts: string;
19
+ noConflicts: string;
20
+ memory: string;
21
+ kind: string;
22
+ status: string;
23
+ from: string;
24
+ checkNone: string;
25
+ checkPassed: string;
26
+ checkViolated: string;
27
+ checkBroken: string;
28
+ checkPending: string;
29
+ unverified: string;
30
+ empty: string;
31
+ checkLegend: string;
32
+ refresh: string;
33
+ loading: string;
34
+ failed: string;
35
+ retry: string;
36
+ searchPlaceholder: string;
37
+ search: string;
38
+ searchResults: string;
39
+ noResults: string;
40
+ addMemory: string;
41
+ memoryText: string;
42
+ subjectOptional: string;
43
+ kindFact: string;
44
+ kindDecision: string;
45
+ kindPreference: string;
46
+ kindPitfall: string;
47
+ add: string;
48
+ forget: string;
49
+ resolve: string;
50
+ };
51
+ /** English dictionary, checked complete against the zh key set. */
52
+ export declare const en: typeof zh;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Dictionaries for the Context memory page.
3
+ *
4
+ * `zh` is the key-set source of truth, as in the official client plugins, and `en` is
5
+ * typed against it: a key translated in one language but not the other fails the build
6
+ * instead of silently rendering the raw key.
7
+ *
8
+ * @module client/locales
9
+ */
10
+ /** Dictionary namespace owned by this plugin. */
11
+ export const NS = 'contextMemory';
12
+ /** Simplified Chinese dictionary (the key-set source of truth). */
13
+ export const zh = {
14
+ 'nav': '跨会话记忆',
15
+ 'title': '跨会话记忆',
16
+ 'active': '生效中',
17
+ 'superseded': '已失效',
18
+ 'conflicts': '冲突',
19
+ 'noConflicts': '没有冲突。',
20
+ 'memory': '记忆',
21
+ 'kind': '类型',
22
+ 'status': '校验状态',
23
+ 'from': '来源',
24
+ 'checkNone': '无检查',
25
+ 'checkPassed': '已核对,成立',
26
+ 'checkViolated': '已核对,不成立',
27
+ 'checkBroken': '检查无法运行',
28
+ 'checkPending': '尚未核对',
29
+ 'unverified': '未验证成立',
30
+ 'empty': '还没有记住任何内容。',
31
+ 'checkLegend': '无检查=无法编译检查;检查无法运行=检查本身有问题,不代表记忆错误。',
32
+ 'refresh': '刷新',
33
+ 'loading': '正在加载…',
34
+ 'failed': '加载失败',
35
+ 'retry': '重试',
36
+ 'searchPlaceholder': '搜索记忆…',
37
+ 'search': '搜索',
38
+ 'searchResults': '搜索结果',
39
+ 'noResults': '没有匹配的记忆。',
40
+ 'addMemory': '添加记忆',
41
+ 'memoryText': '记忆内容',
42
+ 'subjectOptional': '主题(可选)',
43
+ 'kindFact': '事实',
44
+ 'kindDecision': '决策',
45
+ 'kindPreference': '偏好',
46
+ 'kindPitfall': '陷阱',
47
+ 'add': '添加',
48
+ 'forget': '废弃',
49
+ 'resolve': '解决',
50
+ };
51
+ /** English dictionary, checked complete against the zh key set. */
52
+ export const en = {
53
+ 'nav': 'Context memory',
54
+ 'title': 'Context memory',
55
+ 'active': 'active',
56
+ 'superseded': 'superseded',
57
+ 'conflicts': 'Conflicts',
58
+ 'noConflicts': 'No conflicts.',
59
+ 'memory': 'Memory',
60
+ 'kind': 'Kind',
61
+ 'status': 'Check',
62
+ 'from': 'From',
63
+ 'checkNone': 'no check',
64
+ 'checkPassed': 'checked, holds',
65
+ 'checkViolated': 'checked, does not hold',
66
+ 'checkBroken': 'check unusable',
67
+ 'checkPending': 'not yet checked',
68
+ 'unverified': 'not verified as holding',
69
+ 'empty': 'Nothing is remembered yet.',
70
+ 'checkLegend': 'no check = no check could be compiled; check unusable = the check is defective, not the memory.',
71
+ 'refresh': 'Refresh',
72
+ 'loading': 'Loading…',
73
+ 'failed': 'Failed to load',
74
+ 'retry': 'Retry',
75
+ 'searchPlaceholder': 'Search memories…',
76
+ 'search': 'Search',
77
+ 'searchResults': 'Search Results',
78
+ 'noResults': 'No matching memories.',
79
+ 'addMemory': 'Add Memory',
80
+ 'memoryText': 'Memory text',
81
+ 'subjectOptional': 'Subject (optional)',
82
+ 'kindFact': 'Fact',
83
+ 'kindDecision': 'Decision',
84
+ 'kindPreference': 'Preference',
85
+ 'kindPitfall': 'Pitfall',
86
+ 'add': 'Add',
87
+ 'forget': 'Forget',
88
+ 'resolve': 'Resolve',
89
+ };
@@ -1,20 +1,26 @@
1
1
  /**
2
- * Browser half of dsh-context-memory — the view model.
2
+ * Pure rendering half of the contextMemory page.
3
3
  *
4
- * Rendering is kept here, separate from the fetch-and-register shell, so it can be
5
- * statically rendered in Node during tests. The panel is a claim about what the agent
6
- * remembers, so it is worth asserting on without a browser.
4
+ * Separate from `index.tsx` so a static render can assert in Node what the page draws —
5
+ * the shipped bundle is a loader factory only a browser can run. Every user-visible
6
+ * string comes from the `t` seat the renderer binds from this plugin's namespace, so the
7
+ * page follows the UI language; no copy is hardcoded here.
7
8
  *
8
- * @module context-memory/client/view
9
+ * @module client/view
9
10
  */
10
11
  import type { MemoryState, PanelPayload } from '../types.js';
11
- /**
12
- * A memory's verification badge.
13
- *
14
- * Four states, not two, because "no check exists" and "the check could not run" are
15
- * different answers from "the check failed" — collapsing them would report an
16
- * unverifiable memory and a false one identically, which is the failure this plugin
17
- * exists to avoid.
18
- */
19
- export declare function verificationLabel(memory: MemoryState): string;
20
- export declare function renderPanel(payload: PanelPayload): string;
12
+ export type Translate = (key: string, params?: Record<string, unknown>) => string;
13
+ export interface PanelProps {
14
+ t: Translate;
15
+ }
16
+ interface PanelState {
17
+ payload: PanelPayload | null;
18
+ error: string | null;
19
+ }
20
+ export declare function usePanel(): PanelState & {
21
+ reload: () => void;
22
+ };
23
+ export declare function checkLabel(memory: MemoryState, t: Translate): string;
24
+ export declare function provenance(memory: MemoryState): string;
25
+ export declare function ContextMemoryPanel({ t }: PanelProps): import("react").JSX.Element;
26
+ export {};
@@ -1,64 +1,129 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
2
  /**
2
- * Browser half of dsh-context-memory — the view model.
3
+ * Pure rendering half of the contextMemory page.
3
4
  *
4
- * Rendering is kept here, separate from the fetch-and-register shell, so it can be
5
- * statically rendered in Node during tests. The panel is a claim about what the agent
6
- * remembers, so it is worth asserting on without a browser.
5
+ * Separate from `index.tsx` so a static render can assert in Node what the page draws —
6
+ * the shipped bundle is a loader factory only a browser can run. Every user-visible
7
+ * string comes from the `t` seat the renderer binds from this plugin's namespace, so the
8
+ * page follows the UI language; no copy is hardcoded here.
7
9
  *
8
- * @module context-memory/client/view
10
+ * @module client/view
9
11
  */
10
- function escapeHtml(value) {
11
- return value
12
- .replace(/&/g, '&amp;')
13
- .replace(/</g, '&lt;')
14
- .replace(/>/g, '&gt;')
15
- .replace(/"/g, '&quot;');
12
+ import { useCallback, useEffect, useState } from 'react';
13
+ const PANEL_PATH = '/api/context-memory.panel';
14
+ const RECALL_PATH = '/api/context-memory.recall';
15
+ const REMEMBER_PATH = '/api/context-memory.remember';
16
+ const FORGET_PATH = '/api/context-memory.forget';
17
+ const wrap = { display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 820, fontFamily: 'inherit' };
18
+ const head = { display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap' };
19
+ const muted = { fontSize: 12, opacity: 0.75 };
20
+ const table = { borderCollapse: 'collapse', width: '100%' };
21
+ const th = { textAlign: 'left', padding: '4px 10px 4px 0', fontWeight: 600, fontSize: 12, opacity: 0.8, borderBottom: '0.5px solid rgba(128,128,128,0.4)' };
22
+ const td = { padding: '6px 10px 6px 0', fontSize: 13, borderBottom: '0.5px solid rgba(128,128,128,0.18)' };
23
+ const list = { margin: 0, paddingLeft: 18, fontSize: 13 };
24
+ const card = { padding: '8px 12px', borderRadius: 6, border: '1px solid rgba(128,128,128,0.2)', fontSize: 13 };
25
+ const btn = { fontSize: 12, cursor: 'pointer', padding: '3px 10px', borderRadius: 4, border: '0.5px solid rgba(128,128,128,0.4)' };
26
+ const dangerBtn = { ...btn, color: '#e55', borderColor: '#e55' };
27
+ const inputStyle = { fontSize: 13, padding: '4px 8px', borderRadius: 4, border: '0.5px solid rgba(128,128,128,0.4)', flex: 1 };
28
+ export function usePanel() {
29
+ const [state, setState] = useState({ payload: null, error: null });
30
+ const [tick, setTick] = useState(0);
31
+ const reload = useCallback(() => setTick((v) => v + 1), []);
32
+ useEffect(() => {
33
+ const c = new AbortController();
34
+ setState((p) => ({ ...p, error: null }));
35
+ fetch(PANEL_PATH, { signal: c.signal })
36
+ .then(async (r) => { if (!r.ok)
37
+ throw new Error(String(r.status)); return r.json(); })
38
+ .then((payload) => { if (!c.signal.aborted)
39
+ setState({ payload, error: null }); })
40
+ .catch((e) => { if (!c.signal.aborted)
41
+ setState({ payload: null, error: e instanceof Error ? e.message : String(e) }); });
42
+ return () => c.abort();
43
+ }, [tick]);
44
+ return { ...state, reload };
16
45
  }
17
- /**
18
- * A memory's verification badge.
19
- *
20
- * Four states, not two, because "no check exists" and "the check could not run" are
21
- * different answers from "the check failed" — collapsing them would report an
22
- * unverifiable memory and a false one identically, which is the failure this plugin
23
- * exists to avoid.
24
- */
25
- export function verificationLabel(memory) {
46
+ export function checkLabel(memory, t) {
26
47
  if (!memory.guard)
27
- return 'no check';
48
+ return t('checkNone');
28
49
  switch (memory.verification?.outcome) {
29
- case 'passed': return 'checked, holds';
30
- case 'violated': return 'checked, does not hold';
31
- case 'broken': return 'check unusable';
32
- default: return 'not yet checked';
50
+ case 'passed': return t('checkPassed');
51
+ case 'violated': return t('checkViolated');
52
+ case 'broken': return t('checkBroken');
53
+ default: return t('checkPending');
33
54
  }
34
55
  }
35
- function rows(memories) {
36
- return memories
37
- .map((memory) => {
38
- const provenance = [
39
- memory.source,
40
- memory.sessionId ? `session ${memory.sessionId}` : null,
41
- memory.step !== undefined ? `step ${memory.step}` : null,
42
- ].filter(Boolean).join(' · ');
43
- return `<tr><td><code>${escapeHtml(memory.id)}</code></td><td>${escapeHtml(memory.text)}</td><td>${escapeHtml(memory.kind)}</td><td>${escapeHtml(verificationLabel(memory))}</td><td>${escapeHtml(provenance)}</td></tr>`;
44
- })
45
- .join('');
56
+ export function provenance(memory) {
57
+ return [
58
+ memory.source,
59
+ memory.sessionId !== undefined ? `session ${memory.sessionId}` : null,
60
+ memory.step !== undefined ? `step ${String(memory.step)}` : null,
61
+ ].filter((part) => part !== null).join(' · ');
46
62
  }
47
- export function renderPanel(payload) {
63
+ // --- Search bar ---
64
+ function SearchBar({ t, onResult }) {
65
+ const [query, setQuery] = useState('');
66
+ const [loading, setLoading] = useState(false);
67
+ const handleSearch = useCallback(async () => {
68
+ if (!query.trim()) {
69
+ onResult(null);
70
+ return;
71
+ }
72
+ setLoading(true);
73
+ try {
74
+ const r = await fetch(`${RECALL_PATH}?q=${encodeURIComponent(query)}&limit=10`);
75
+ if (r.ok) {
76
+ const data = await r.json();
77
+ onResult(data.hits);
78
+ }
79
+ }
80
+ finally {
81
+ setLoading(false);
82
+ }
83
+ }, [query, onResult]);
84
+ return (_jsxs("div", { style: { display: 'flex', gap: 8 }, children: [_jsx("input", { style: inputStyle, placeholder: t('searchPlaceholder'), value: query, onChange: (e) => setQuery(e.target.value), onKeyDown: (e) => { if (e.key === 'Enter')
85
+ handleSearch(); } }), _jsx("button", { type: "button", style: btn, disabled: loading, onClick: handleSearch, children: loading ? '…' : t('search') })] }));
86
+ }
87
+ // --- Add memory form ---
88
+ function AddMemoryForm({ t, onAdded }) {
89
+ const [text, setText] = useState('');
90
+ const [subject, setSubject] = useState('');
91
+ const [kind, setKind] = useState('fact');
92
+ const [adding, setAdding] = useState(false);
93
+ const handleAdd = useCallback(async () => {
94
+ if (!text.trim())
95
+ return;
96
+ setAdding(true);
97
+ try {
98
+ await fetch(REMEMBER_PATH, {
99
+ method: 'POST',
100
+ headers: { 'content-type': 'application/json' },
101
+ body: JSON.stringify({ text, subject: subject || undefined, kind, source: 'user' }),
102
+ });
103
+ setText('');
104
+ setSubject('');
105
+ setKind('fact');
106
+ onAdded();
107
+ }
108
+ finally {
109
+ setAdding(false);
110
+ }
111
+ }, [text, subject, kind, onAdded]);
112
+ return (_jsxs("div", { style: { ...card, display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsxs("strong", { style: { fontSize: 12 }, children: ["\u2795 ", t('addMemory')] }), _jsx("input", { style: inputStyle, placeholder: t('memoryText'), value: text, onChange: (e) => setText(e.target.value) }), _jsxs("div", { style: { display: 'flex', gap: 8 }, children: [_jsx("input", { style: inputStyle, placeholder: t('subjectOptional'), value: subject, onChange: (e) => setSubject(e.target.value) }), _jsxs("select", { style: { ...btn, flex: '0 0 auto' }, value: kind, onChange: (e) => setKind(e.target.value), children: [_jsx("option", { value: "fact", children: t('kindFact') }), _jsx("option", { value: "decision", children: t('kindDecision') }), _jsx("option", { value: "preference", children: t('kindPreference') }), _jsx("option", { value: "pitfall", children: t('kindPitfall') })] }), _jsx("button", { type: "button", style: btn, disabled: adding, onClick: handleAdd, children: adding ? '…' : t('add') })] })] }));
113
+ }
114
+ export function ContextMemoryPanel({ t }) {
115
+ const { payload, error, reload } = usePanel();
116
+ const [searchHits, setSearchHits] = useState(null);
117
+ const handleForget = useCallback(async (id, reason) => {
118
+ await fetch(FORGET_PATH, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id, reason }) });
119
+ reload();
120
+ }, [reload]);
121
+ const header = (_jsxs("header", { style: head, children: [_jsxs("strong", { style: { fontSize: 13 }, children: ["\uD83E\uDDE0 ", t('title')] }), _jsx("span", { style: { flex: 1 } }), _jsx("button", { type: "button", style: btn, onClick: reload, children: t('refresh') })] }));
122
+ if (error !== null) {
123
+ return (_jsxs("div", { style: wrap, children: [header, _jsxs("p", { role: "alert", style: { margin: 0, fontSize: 13 }, children: [t('failed'), ": ", error] }), _jsx("button", { type: "button", onClick: reload, style: { ...btn, alignSelf: 'flex-start' }, children: t('retry') })] }));
124
+ }
125
+ if (payload === null)
126
+ return _jsx("p", { style: muted, "aria-live": "polite", children: t('loading') });
48
127
  const { totals } = payload;
49
- const summary = `<p class="memory-summary">${totals.active} active (${totals.verified} checked and holding, ${totals.unverified} not), ${totals.superseded} superseded, ${totals.withGuard} with a compiled check.</p>`;
50
- const conflictBlock = payload.conflicts.length === 0
51
- ? '<p>No conflicts.</p>'
52
- : `<div class="memory-conflicts"><h3>Conflicts</h3><ul>${payload.conflicts
53
- .map((conflict) => `<li><strong>${escapeHtml(conflict.subject)}</strong><ul>${conflict.memories
54
- .map((memory) => `<li><code>${escapeHtml(memory.id)}</code> ${escapeHtml(memory.text)}</li>`)
55
- .join('')}</ul></li>`)
56
- .join('')}</ul></div>`;
57
- const activeBlock = payload.active.length === 0
58
- ? '<p>Nothing is remembered yet.</p>'
59
- : `<table><thead><tr><th>Id</th><th>Memory</th><th>Kind</th><th>Status</th><th>From</th></tr></thead><tbody>${rows(payload.active)}</tbody></table>`;
60
- const supersededBlock = payload.superseded.length === 0
61
- ? ''
62
- : `<details><summary>Superseded (${payload.superseded.length})</summary><table><thead><tr><th>Id</th><th>Memory</th><th>Kind</th><th>Status</th><th>From</th></tr></thead><tbody>${rows(payload.superseded)}</tbody></table></details>`;
63
- return `<div class="context-memory-panel"><h2>Context memory</h2>${summary}${conflictBlock}<h3>Active</h3>${activeBlock}${supersededBlock}</div>`;
128
+ return (_jsxs("div", { style: wrap, children: [header, _jsxs("span", { style: muted, children: [t('active'), " ", totals.active, " \u00B7 ", t('checkPassed'), " ", totals.verified, " \u00B7 ", t('unverified'), " ", totals.unverified, " \u00B7 ", t('superseded'), " ", totals.superseded] }), _jsx(SearchBar, { t: t, onResult: setSearchHits }), searchHits !== null && (_jsxs("div", { style: card, children: [_jsxs("strong", { style: { fontSize: 12 }, children: [t('searchResults'), " (", searchHits.length, ")"] }), searchHits.length === 0 ? (_jsx("p", { style: muted, children: t('noResults') })) : (_jsx("ul", { style: list, children: searchHits.map((hit) => (_jsxs("li", { style: { marginBottom: 4 }, children: [_jsx("code", { style: { fontSize: 11 }, children: hit.memory.id }), " (", hit.score.toFixed(2), ") ", hit.memory.text, _jsxs("span", { style: { ...muted, marginLeft: 8 }, children: ["\u2014 ", hit.why] })] }, hit.memory.id))) }))] })), _jsx(AddMemoryForm, { t: t, onAdded: reload }), _jsxs("strong", { style: { fontSize: 13 }, children: ["\u26A0\uFE0F ", t('conflicts')] }), payload.conflicts.length === 0 ? (_jsx("span", { style: muted, children: t('noConflicts') })) : (_jsx("ul", { style: list, children: payload.conflicts.map((conflict) => (_jsxs("li", { children: [_jsx("strong", { children: conflict.subject }), _jsx("ul", { style: { ...list, fontSize: 12, opacity: 0.85 }, children: conflict.memories.map((memory) => (_jsxs("li", { children: [_jsx("code", { style: { fontSize: 11 }, children: memory.id }), " ", memory.text, _jsx("button", { type: "button", style: { ...dangerBtn, marginLeft: 8 }, onClick: () => handleForget(memory.id, `conflict resolved: ${conflict.subject}`), children: t('resolve') })] }, memory.id))) })] }, conflict.subject))) })), _jsxs("strong", { style: { fontSize: 13 }, children: ["\u2705 ", t('active')] }), payload.active.length === 0 ? (_jsx("p", { style: { margin: 0, fontSize: 13, opacity: 0.8 }, children: t('empty') })) : (_jsxs("table", { style: table, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { style: th, children: "Id" }), _jsx("th", { style: th, children: t('memory') }), _jsx("th", { style: th, children: t('kind') }), _jsx("th", { style: th, children: t('status') }), _jsx("th", { style: th, children: t('from') }), _jsx("th", { style: th })] }) }), _jsx("tbody", { children: payload.active.map((memory) => (_jsxs("tr", { children: [_jsx("td", { style: td, children: _jsx("code", { style: { fontSize: 11 }, children: memory.id }) }), _jsx("td", { style: td, children: memory.text }), _jsx("td", { style: td, children: memory.kind }), _jsx("td", { style: td, children: checkLabel(memory, t) }), _jsx("td", { style: td, children: provenance(memory) }), _jsx("td", { style: td, children: _jsxs("button", { type: "button", style: dangerBtn, onClick: () => handleForget(memory.id, 'superseded from panel'), children: ["\uD83D\uDDD1 ", t('forget')] }) })] }, memory.id))) })] })), payload.superseded.length > 0 && (_jsxs(_Fragment, { children: [_jsxs("strong", { style: { fontSize: 13 }, children: ["\uD83D\uDCE6 ", t('superseded'), " (", payload.superseded.length, ")"] }), _jsxs("table", { style: table, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { style: th, children: "Id" }), _jsx("th", { style: th, children: t('memory') }), _jsx("th", { style: th, children: t('kind') }), _jsx("th", { style: th, children: t('status') }), _jsx("th", { style: th, children: t('from') })] }) }), _jsx("tbody", { children: payload.superseded.map((memory) => (_jsxs("tr", { children: [_jsx("td", { style: td, children: _jsx("code", { style: { fontSize: 11 }, children: memory.id }) }), _jsx("td", { style: td, children: memory.text }), _jsx("td", { style: td, children: memory.kind }), _jsx("td", { style: td, children: checkLabel(memory, t) }), _jsx("td", { style: td, children: provenance(memory) })] }, memory.id))) })] })] }))] }));
64
129
  }
package/lib/memory.web.js CHANGED
@@ -26,92 +26,356 @@ __export(index_exports, {
26
26
  inject: () => inject
27
27
  });
28
28
  module.exports = __toCommonJS(index_exports);
29
- var import_react = require("react");
29
+
30
+ // src/client/locales.ts
31
+ var NS = "contextMemory";
32
+ var zh = {
33
+ "nav": "\u8DE8\u4F1A\u8BDD\u8BB0\u5FC6",
34
+ "title": "\u8DE8\u4F1A\u8BDD\u8BB0\u5FC6",
35
+ "active": "\u751F\u6548\u4E2D",
36
+ "superseded": "\u5DF2\u5931\u6548",
37
+ "conflicts": "\u51B2\u7A81",
38
+ "noConflicts": "\u6CA1\u6709\u51B2\u7A81\u3002",
39
+ "memory": "\u8BB0\u5FC6",
40
+ "kind": "\u7C7B\u578B",
41
+ "status": "\u6821\u9A8C\u72B6\u6001",
42
+ "from": "\u6765\u6E90",
43
+ "checkNone": "\u65E0\u68C0\u67E5",
44
+ "checkPassed": "\u5DF2\u6838\u5BF9\uFF0C\u6210\u7ACB",
45
+ "checkViolated": "\u5DF2\u6838\u5BF9\uFF0C\u4E0D\u6210\u7ACB",
46
+ "checkBroken": "\u68C0\u67E5\u65E0\u6CD5\u8FD0\u884C",
47
+ "checkPending": "\u5C1A\u672A\u6838\u5BF9",
48
+ "unverified": "\u672A\u9A8C\u8BC1\u6210\u7ACB",
49
+ "empty": "\u8FD8\u6CA1\u6709\u8BB0\u4F4F\u4EFB\u4F55\u5185\u5BB9\u3002",
50
+ "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",
51
+ "refresh": "\u5237\u65B0",
52
+ "loading": "\u6B63\u5728\u52A0\u8F7D\u2026",
53
+ "failed": "\u52A0\u8F7D\u5931\u8D25",
54
+ "retry": "\u91CD\u8BD5",
55
+ "searchPlaceholder": "\u641C\u7D22\u8BB0\u5FC6\u2026",
56
+ "search": "\u641C\u7D22",
57
+ "searchResults": "\u641C\u7D22\u7ED3\u679C",
58
+ "noResults": "\u6CA1\u6709\u5339\u914D\u7684\u8BB0\u5FC6\u3002",
59
+ "addMemory": "\u6DFB\u52A0\u8BB0\u5FC6",
60
+ "memoryText": "\u8BB0\u5FC6\u5185\u5BB9",
61
+ "subjectOptional": "\u4E3B\u9898\uFF08\u53EF\u9009\uFF09",
62
+ "kindFact": "\u4E8B\u5B9E",
63
+ "kindDecision": "\u51B3\u7B56",
64
+ "kindPreference": "\u504F\u597D",
65
+ "kindPitfall": "\u9677\u9631",
66
+ "add": "\u6DFB\u52A0",
67
+ "forget": "\u5E9F\u5F03",
68
+ "resolve": "\u89E3\u51B3"
69
+ };
70
+ var en = {
71
+ "nav": "Context memory",
72
+ "title": "Context memory",
73
+ "active": "active",
74
+ "superseded": "superseded",
75
+ "conflicts": "Conflicts",
76
+ "noConflicts": "No conflicts.",
77
+ "memory": "Memory",
78
+ "kind": "Kind",
79
+ "status": "Check",
80
+ "from": "From",
81
+ "checkNone": "no check",
82
+ "checkPassed": "checked, holds",
83
+ "checkViolated": "checked, does not hold",
84
+ "checkBroken": "check unusable",
85
+ "checkPending": "not yet checked",
86
+ "unverified": "not verified as holding",
87
+ "empty": "Nothing is remembered yet.",
88
+ "checkLegend": "no check = no check could be compiled; check unusable = the check is defective, not the memory.",
89
+ "refresh": "Refresh",
90
+ "loading": "Loading\u2026",
91
+ "failed": "Failed to load",
92
+ "retry": "Retry",
93
+ "searchPlaceholder": "Search memories\u2026",
94
+ "search": "Search",
95
+ "searchResults": "Search Results",
96
+ "noResults": "No matching memories.",
97
+ "addMemory": "Add Memory",
98
+ "memoryText": "Memory text",
99
+ "subjectOptional": "Subject (optional)",
100
+ "kindFact": "Fact",
101
+ "kindDecision": "Decision",
102
+ "kindPreference": "Preference",
103
+ "kindPitfall": "Pitfall",
104
+ "add": "Add",
105
+ "forget": "Forget",
106
+ "resolve": "Resolve"
107
+ };
30
108
 
31
109
  // src/client/view.tsx
32
- function escapeHtml(value) {
33
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
110
+ var import_react = require("react");
111
+ var import_jsx_runtime = require("react/jsx-runtime");
112
+ var PANEL_PATH = "/api/context-memory.panel";
113
+ var RECALL_PATH = "/api/context-memory.recall";
114
+ var REMEMBER_PATH = "/api/context-memory.remember";
115
+ var FORGET_PATH = "/api/context-memory.forget";
116
+ var wrap = { display: "flex", flexDirection: "column", gap: 12, maxWidth: 820, fontFamily: "inherit" };
117
+ var head = { display: "flex", alignItems: "baseline", gap: 12, flexWrap: "wrap" };
118
+ var muted = { fontSize: 12, opacity: 0.75 };
119
+ var table = { borderCollapse: "collapse", width: "100%" };
120
+ var th = { textAlign: "left", padding: "4px 10px 4px 0", fontWeight: 600, fontSize: 12, opacity: 0.8, borderBottom: "0.5px solid rgba(128,128,128,0.4)" };
121
+ var td = { padding: "6px 10px 6px 0", fontSize: 13, borderBottom: "0.5px solid rgba(128,128,128,0.18)" };
122
+ var list = { margin: 0, paddingLeft: 18, fontSize: 13 };
123
+ var card = { padding: "8px 12px", borderRadius: 6, border: "1px solid rgba(128,128,128,0.2)", fontSize: 13 };
124
+ var btn = { fontSize: 12, cursor: "pointer", padding: "3px 10px", borderRadius: 4, border: "0.5px solid rgba(128,128,128,0.4)" };
125
+ var dangerBtn = { ...btn, color: "#e55", borderColor: "#e55" };
126
+ var inputStyle = { fontSize: 13, padding: "4px 8px", borderRadius: 4, border: "0.5px solid rgba(128,128,128,0.4)", flex: 1 };
127
+ function usePanel() {
128
+ const [state, setState] = (0, import_react.useState)({ payload: null, error: null });
129
+ const [tick, setTick] = (0, import_react.useState)(0);
130
+ const reload = (0, import_react.useCallback)(() => setTick((v) => v + 1), []);
131
+ (0, import_react.useEffect)(() => {
132
+ const c = new AbortController();
133
+ setState((p) => ({ ...p, error: null }));
134
+ fetch(PANEL_PATH, { signal: c.signal }).then(async (r) => {
135
+ if (!r.ok) throw new Error(String(r.status));
136
+ return r.json();
137
+ }).then((payload) => {
138
+ if (!c.signal.aborted) setState({ payload, error: null });
139
+ }).catch((e) => {
140
+ if (!c.signal.aborted) setState({ payload: null, error: e instanceof Error ? e.message : String(e) });
141
+ });
142
+ return () => c.abort();
143
+ }, [tick]);
144
+ return { ...state, reload };
34
145
  }
35
- function verificationLabel(memory) {
36
- if (!memory.guard) return "no check";
146
+ function checkLabel(memory, t) {
147
+ if (!memory.guard) return t("checkNone");
37
148
  switch (memory.verification?.outcome) {
38
149
  case "passed":
39
- return "checked, holds";
150
+ return t("checkPassed");
40
151
  case "violated":
41
- return "checked, does not hold";
152
+ return t("checkViolated");
42
153
  case "broken":
43
- return "check unusable";
154
+ return t("checkBroken");
44
155
  default:
45
- return "not yet checked";
156
+ return t("checkPending");
46
157
  }
47
158
  }
48
- function rows(memories) {
49
- return memories.map((memory) => {
50
- const provenance = [
51
- memory.source,
52
- memory.sessionId ? `session ${memory.sessionId}` : null,
53
- memory.step !== void 0 ? `step ${memory.step}` : null
54
- ].filter(Boolean).join(" \xB7 ");
55
- return `<tr><td><code>${escapeHtml(memory.id)}</code></td><td>${escapeHtml(memory.text)}</td><td>${escapeHtml(memory.kind)}</td><td>${escapeHtml(verificationLabel(memory))}</td><td>${escapeHtml(provenance)}</td></tr>`;
56
- }).join("");
159
+ function provenance(memory) {
160
+ return [
161
+ memory.source,
162
+ memory.sessionId !== void 0 ? `session ${memory.sessionId}` : null,
163
+ memory.step !== void 0 ? `step ${String(memory.step)}` : null
164
+ ].filter((part) => part !== null).join(" \xB7 ");
57
165
  }
58
- function renderPanel(payload) {
59
- const { totals } = payload;
60
- const summary = `<p class="memory-summary">${totals.active} active (${totals.verified} checked and holding, ${totals.unverified} not), ${totals.superseded} superseded, ${totals.withGuard} with a compiled check.</p>`;
61
- const conflictBlock = payload.conflicts.length === 0 ? "<p>No conflicts.</p>" : `<div class="memory-conflicts"><h3>Conflicts</h3><ul>${payload.conflicts.map((conflict) => `<li><strong>${escapeHtml(conflict.subject)}</strong><ul>${conflict.memories.map((memory) => `<li><code>${escapeHtml(memory.id)}</code> ${escapeHtml(memory.text)}</li>`).join("")}</ul></li>`).join("")}</ul></div>`;
62
- const activeBlock = payload.active.length === 0 ? "<p>Nothing is remembered yet.</p>" : `<table><thead><tr><th>Id</th><th>Memory</th><th>Kind</th><th>Status</th><th>From</th></tr></thead><tbody>${rows(payload.active)}</tbody></table>`;
63
- const supersededBlock = payload.superseded.length === 0 ? "" : `<details><summary>Superseded (${payload.superseded.length})</summary><table><thead><tr><th>Id</th><th>Memory</th><th>Kind</th><th>Status</th><th>From</th></tr></thead><tbody>${rows(payload.superseded)}</tbody></table></details>`;
64
- return `<div class="context-memory-panel"><h2>Context memory</h2>${summary}${conflictBlock}<h3>Active</h3>${activeBlock}${supersededBlock}</div>`;
166
+ function SearchBar({ t, onResult }) {
167
+ const [query, setQuery] = (0, import_react.useState)("");
168
+ const [loading, setLoading] = (0, import_react.useState)(false);
169
+ const handleSearch = (0, import_react.useCallback)(async () => {
170
+ if (!query.trim()) {
171
+ onResult(null);
172
+ return;
173
+ }
174
+ setLoading(true);
175
+ try {
176
+ const r = await fetch(`${RECALL_PATH}?q=${encodeURIComponent(query)}&limit=10`);
177
+ if (r.ok) {
178
+ const data = await r.json();
179
+ onResult(data.hits);
180
+ }
181
+ } finally {
182
+ setLoading(false);
183
+ }
184
+ }, [query, onResult]);
185
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle, placeholder: t("searchPlaceholder"), value: query, onChange: (e) => setQuery(e.target.value), onKeyDown: (e) => {
187
+ if (e.key === "Enter") handleSearch();
188
+ } }),
189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: btn, disabled: loading, onClick: handleSearch, children: loading ? "\u2026" : t("search") })
190
+ ] });
65
191
  }
66
-
67
- // src/client/index.tsx
68
- var import_jsx_runtime = require("react/jsx-runtime");
69
- var PANEL_PATH = "/api/context-memory.panel";
70
- function usePanel() {
71
- const [html, setHtml] = (0, import_react.useState)(null);
72
- const [error, setError] = (0, import_react.useState)(null);
73
- const [tick, setTick] = (0, import_react.useState)(0);
74
- const reload = (0, import_react.useCallback)(() => setTick((t) => t + 1), []);
75
- (0, import_react.useEffect)(() => {
76
- const controller = new AbortController();
77
- setError(null);
78
- fetch(PANEL_PATH, { signal: controller.signal }).then(async (response) => {
79
- if (!response.ok) throw new Error(`panel request failed with ${response.status}`);
80
- return response.json();
81
- }).then((payload) => {
82
- if (!controller.signal.aborted) setHtml(renderPanel(payload));
83
- }).catch((cause) => {
84
- if (controller.signal.aborted) return;
85
- setError(cause instanceof Error ? cause.message : String(cause));
86
- });
87
- return () => controller.abort();
88
- }, [tick]);
89
- return { html, error, reload };
192
+ function AddMemoryForm({ t, onAdded }) {
193
+ const [text, setText] = (0, import_react.useState)("");
194
+ const [subject, setSubject] = (0, import_react.useState)("");
195
+ const [kind, setKind] = (0, import_react.useState)("fact");
196
+ const [adding, setAdding] = (0, import_react.useState)(false);
197
+ const handleAdd = (0, import_react.useCallback)(async () => {
198
+ if (!text.trim()) return;
199
+ setAdding(true);
200
+ try {
201
+ await fetch(REMEMBER_PATH, {
202
+ method: "POST",
203
+ headers: { "content-type": "application/json" },
204
+ body: JSON.stringify({ text, subject: subject || void 0, kind, source: "user" })
205
+ });
206
+ setText("");
207
+ setSubject("");
208
+ setKind("fact");
209
+ onAdded();
210
+ } finally {
211
+ setAdding(false);
212
+ }
213
+ }, [text, subject, kind, onAdded]);
214
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...card, display: "flex", flexDirection: "column", gap: 6 }, children: [
215
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { style: { fontSize: 12 }, children: [
216
+ "\u2795 ",
217
+ t("addMemory")
218
+ ] }),
219
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle, placeholder: t("memoryText"), value: text, onChange: (e) => setText(e.target.value) }),
220
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle, placeholder: t("subjectOptional"), value: subject, onChange: (e) => setSubject(e.target.value) }),
222
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { style: { ...btn, flex: "0 0 auto" }, value: kind, onChange: (e) => setKind(e.target.value), children: [
223
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "fact", children: t("kindFact") }),
224
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "decision", children: t("kindDecision") }),
225
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "preference", children: t("kindPreference") }),
226
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "pitfall", children: t("kindPitfall") })
227
+ ] }),
228
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: btn, disabled: adding, onClick: handleAdd, children: adding ? "\u2026" : t("add") })
229
+ ] })
230
+ ] });
90
231
  }
91
- function ContextmemoryPage() {
92
- const { html, error, reload } = usePanel();
232
+ function ContextMemoryPanel({ t }) {
233
+ const { payload, error, reload } = usePanel();
234
+ const [searchHits, setSearchHits] = (0, import_react.useState)(null);
235
+ const handleForget = (0, import_react.useCallback)(async (id, reason) => {
236
+ await fetch(FORGET_PATH, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ id, reason }) });
237
+ reload();
238
+ }, [reload]);
239
+ const header = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { style: head, children: [
240
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { style: { fontSize: 13 }, children: [
241
+ "\u{1F9E0} ",
242
+ t("title")
243
+ ] }),
244
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
245
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: btn, onClick: reload, children: t("refresh") })
246
+ ] });
93
247
  if (error !== null) {
94
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
95
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { role: "alert", children: [
96
- "Context memory panel failed to load: ",
248
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: wrap, children: [
249
+ header,
250
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { role: "alert", style: { margin: 0, fontSize: 13 }, children: [
251
+ t("failed"),
252
+ ": ",
97
253
  error
98
254
  ] }),
99
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: reload, children: "Retry" })
255
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: reload, style: { ...btn, alignSelf: "flex-start" }, children: t("retry") })
100
256
  ] });
101
257
  }
102
- if (html === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { "aria-live": "polite", children: "Loading the Context memory panel\u2026" });
103
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: html } });
258
+ if (payload === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: muted, "aria-live": "polite", children: t("loading") });
259
+ const { totals } = payload;
260
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: wrap, children: [
261
+ header,
262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: muted, children: [
263
+ t("active"),
264
+ " ",
265
+ totals.active,
266
+ " \xB7 ",
267
+ t("checkPassed"),
268
+ " ",
269
+ totals.verified,
270
+ " \xB7 ",
271
+ t("unverified"),
272
+ " ",
273
+ totals.unverified,
274
+ " \xB7 ",
275
+ t("superseded"),
276
+ " ",
277
+ totals.superseded
278
+ ] }),
279
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchBar, { t, onResult: setSearchHits }),
280
+ searchHits !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: card, children: [
281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { style: { fontSize: 12 }, children: [
282
+ t("searchResults"),
283
+ " (",
284
+ searchHits.length,
285
+ ")"
286
+ ] }),
287
+ searchHits.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: muted, children: t("noResults") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: list, children: searchHits.map((hit) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { marginBottom: 4 }, children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { style: { fontSize: 11 }, children: hit.memory.id }),
289
+ " (",
290
+ hit.score.toFixed(2),
291
+ ") ",
292
+ hit.memory.text,
293
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { ...muted, marginLeft: 8 }, children: [
294
+ "\u2014 ",
295
+ hit.why
296
+ ] })
297
+ ] }, hit.memory.id)) })
298
+ ] }),
299
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AddMemoryForm, { t, onAdded: reload }),
300
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { style: { fontSize: 13 }, children: [
301
+ "\u26A0\uFE0F ",
302
+ t("conflicts")
303
+ ] }),
304
+ payload.conflicts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: muted, children: t("noConflicts") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: list, children: payload.conflicts.map((conflict) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
305
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: conflict.subject }),
306
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { ...list, fontSize: 12, opacity: 0.85 }, children: conflict.memories.map((memory) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
307
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { style: { fontSize: 11 }, children: memory.id }),
308
+ " ",
309
+ memory.text,
310
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: { ...dangerBtn, marginLeft: 8 }, onClick: () => handleForget(memory.id, `conflict resolved: ${conflict.subject}`), children: t("resolve") })
311
+ ] }, memory.id)) })
312
+ ] }, conflict.subject)) }),
313
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { style: { fontSize: 13 }, children: [
314
+ "\u2705 ",
315
+ t("active")
316
+ ] }),
317
+ payload.active.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: 0, fontSize: 13, opacity: 0.8 }, children: t("empty") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { style: table, children: [
318
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
319
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: "Id" }),
320
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("memory") }),
321
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("kind") }),
322
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("status") }),
323
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("from") }),
324
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th })
325
+ ] }) }),
326
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: payload.active.map((memory) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
327
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { style: { fontSize: 11 }, children: memory.id }) }),
328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: memory.text }),
329
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: memory.kind }),
330
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: checkLabel(memory, t) }),
331
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: provenance(memory) }),
332
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", style: dangerBtn, onClick: () => handleForget(memory.id, "superseded from panel"), children: [
333
+ "\u{1F5D1} ",
334
+ t("forget")
335
+ ] }) })
336
+ ] }, memory.id)) })
337
+ ] }),
338
+ payload.superseded.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
339
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { style: { fontSize: 13 }, children: [
340
+ "\u{1F4E6} ",
341
+ t("superseded"),
342
+ " (",
343
+ payload.superseded.length,
344
+ ")"
345
+ ] }),
346
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { style: table, children: [
347
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: "Id" }),
349
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("memory") }),
350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("kind") }),
351
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("status") }),
352
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("from") })
353
+ ] }) }),
354
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: payload.superseded.map((memory) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
355
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { style: { fontSize: 11 }, children: memory.id }) }),
356
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: memory.text }),
357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: memory.kind }),
358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: checkLabel(memory, t) }),
359
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: provenance(memory) })
360
+ ] }, memory.id)) })
361
+ ] })
362
+ ] })
363
+ ] });
104
364
  }
105
- var inject = ["slots"];
365
+
366
+ // src/client/index.tsx
367
+ var inject = ["slots", "locale"];
106
368
  function apply(ctx) {
369
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-context-memory: dictionaries");
107
370
  ctx.slots.inject("settings.section", () => ctx.slots.register(
108
371
  {
109
372
  name: "settings.section",
110
373
  id: "context-memory",
111
374
  order: 46,
112
- label: () => "Context memory"
375
+ label: () => ctx.locale.bind(NS)("nav"),
376
+ locale: NS
113
377
  },
114
- ContextmemoryPage
378
+ ContextMemoryPanel
115
379
  ));
116
380
  }
117
381
  return module.exports; } });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/client/index.tsx", "../src/client/view.tsx"],
4
- "sourcesContent": ["/**\n * Browser half of dsh-context-memory: the Context memory page inside Settings.\n *\n * Services are named by their runtime identity, not by the package that provides them:\n * the settings shell is the `slots` service and the host bridge is `connection`. A\n * fiber that injects package names never resolves and the plugin stays pending, which\n * the loader reports as \"did not activate\".\n *\n * @module client\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { ComponentType } from 'react'\nimport type { PanelPayload } from '../types.js'\nimport { renderPanel } from './view.js'\n\n/** Path the host half registers on the web connection. */\nconst PANEL_PATH = '/api/context-memory.panel'\n\ninterface SlotsService {\n inject(name: string, register: () => void): void\n register(options: { name: string; id: string; order: number; label: () => string }, component: ComponentType): unknown\n}\n\n/** Fetch the panel payload; `reload` re-runs the request. */\nfunction usePanel() {\n const [html, setHtml] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [tick, setTick] = useState(0)\n const reload = useCallback(() => setTick((t) => t + 1), [])\n\n useEffect(() => {\n const controller = new AbortController()\n setError(null)\n fetch(PANEL_PATH, { signal: controller.signal })\n .then(async (response) => {\n if (!response.ok) throw new Error(`panel request failed with ${response.status}`)\n return response.json() as Promise<PanelPayload>\n })\n .then((payload) => {\n if (!controller.signal.aborted) setHtml(renderPanel(payload))\n })\n .catch((cause: unknown) => {\n if (controller.signal.aborted) return\n // Say what failed rather than rendering an empty panel, which would read as\n // \"nothing recorded\" \u2014 a different and wrong answer.\n setError(cause instanceof Error ? cause.message : String(cause))\n })\n return () => controller.abort()\n }, [tick])\n\n return { html, error, reload }\n}\n\nfunction ContextmemoryPage() {\n const { html, error, reload } = usePanel()\n if (error !== null) {\n return (\n <section>\n <p role=\"alert\">Context memory panel failed to load: {error}</p>\n <button type=\"button\" onClick={reload}>Retry</button>\n </section>\n )\n }\n if (html === null) return <p aria-live=\"polite\">Loading the Context memory panel\u2026</p>\n // The markup is produced by `renderPanel`, which escapes every interpolated value.\n return <div dangerouslySetInnerHTML={{ __html: html }} />\n}\n\n/** The slots service is what the settings shell exposes. */\nexport const inject = ['slots']\n\nexport function apply(ctx: { slots: SlotsService }): void {\n ctx.slots.inject('settings.section', () => ctx.slots.register(\n {\n name: 'settings.section',\n id: 'context-memory',\n order: 46,\n label: () => 'Context memory',\n },\n ContextmemoryPage,\n ))\n}\n", "/**\n * Browser half of dsh-context-memory \u2014 the view model.\n *\n * Rendering is kept here, separate from the fetch-and-register shell, so it can be\n * statically rendered in Node during tests. The panel is a claim about what the agent\n * remembers, so it is worth asserting on without a browser.\n *\n * @module context-memory/client/view\n */\n\nimport type { MemoryState, PanelPayload } from '../types.js'\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n\n/**\n * A memory's verification badge.\n *\n * Four states, not two, because \"no check exists\" and \"the check could not run\" are\n * different answers from \"the check failed\" \u2014 collapsing them would report an\n * unverifiable memory and a false one identically, which is the failure this plugin\n * exists to avoid.\n */\nexport function verificationLabel(memory: MemoryState): string {\n if (!memory.guard) return 'no check'\n switch (memory.verification?.outcome) {\n case 'passed': return 'checked, holds'\n case 'violated': return 'checked, does not hold'\n case 'broken': return 'check unusable'\n default: return 'not yet checked'\n }\n}\n\nfunction rows(memories: MemoryState[]): string {\n return memories\n .map((memory) => {\n const provenance = [\n memory.source,\n memory.sessionId ? `session ${memory.sessionId}` : null,\n memory.step !== undefined ? `step ${memory.step}` : null,\n ].filter(Boolean).join(' \u00B7 ')\n return `<tr><td><code>${escapeHtml(memory.id)}</code></td><td>${escapeHtml(memory.text)}</td><td>${escapeHtml(memory.kind)}</td><td>${escapeHtml(verificationLabel(memory))}</td><td>${escapeHtml(provenance)}</td></tr>`\n })\n .join('')\n}\n\nexport function renderPanel(payload: PanelPayload): string {\n const { totals } = payload\n\n const summary = `<p class=\"memory-summary\">${totals.active} active (${totals.verified} checked and holding, ${totals.unverified} not), ${totals.superseded} superseded, ${totals.withGuard} with a compiled check.</p>`\n\n const conflictBlock = payload.conflicts.length === 0\n ? '<p>No conflicts.</p>'\n : `<div class=\"memory-conflicts\"><h3>Conflicts</h3><ul>${payload.conflicts\n .map((conflict) => `<li><strong>${escapeHtml(conflict.subject)}</strong><ul>${conflict.memories\n .map((memory) => `<li><code>${escapeHtml(memory.id)}</code> ${escapeHtml(memory.text)}</li>`)\n .join('')}</ul></li>`)\n .join('')}</ul></div>`\n\n const activeBlock = payload.active.length === 0\n ? '<p>Nothing is remembered yet.</p>'\n : `<table><thead><tr><th>Id</th><th>Memory</th><th>Kind</th><th>Status</th><th>From</th></tr></thead><tbody>${rows(payload.active)}</tbody></table>`\n\n const supersededBlock = payload.superseded.length === 0\n ? ''\n : `<details><summary>Superseded (${payload.superseded.length})</summary><table><thead><tr><th>Id</th><th>Memory</th><th>Kind</th><th>Status</th><th>From</th></tr></thead><tbody>${rows(payload.superseded)}</tbody></table></details>`\n\n return `<div class=\"context-memory-panel\"><h2>Context memory</h2>${summary}${conflictBlock}<h3>Active</h3>${activeBlock}${supersededBlock}</div>`\n}\n"],
5
- "mappings": "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",
3
+ "sources": ["../src/client/index.tsx", "../src/client/locales.ts", "../src/client/view.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: 46,\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}\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}\n", "/**\n * Pure rendering half of the contextMemory page.\n *\n * Separate from `index.tsx` so a static render can assert in Node what the page draws \u2014\n * the shipped bundle is a loader factory only a browser can run. Every user-visible\n * string comes from the `t` seat the renderer binds from this plugin's namespace, so the\n * page follows the UI language; no copy is hardcoded here.\n *\n * @module client/view\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { CSSProperties } from 'react'\n\nimport type { MemoryState, PanelPayload, RecallHit } from '../types.js'\n\nexport type Translate = (key: string, params?: Record<string, unknown>) => string\n\nexport interface PanelProps {\n t: Translate\n}\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'\n\nconst wrap: CSSProperties = { display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 820, fontFamily: 'inherit' }\nconst head: CSSProperties = { display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap' }\nconst muted: CSSProperties = { fontSize: 12, opacity: 0.75 }\nconst table: CSSProperties = { borderCollapse: 'collapse', width: '100%' }\nconst th: CSSProperties = { textAlign: 'left', padding: '4px 10px 4px 0', fontWeight: 600, fontSize: 12, opacity: 0.8, borderBottom: '0.5px solid rgba(128,128,128,0.4)' }\nconst td: CSSProperties = { padding: '6px 10px 6px 0', fontSize: 13, borderBottom: '0.5px solid rgba(128,128,128,0.18)' }\nconst list: CSSProperties = { margin: 0, paddingLeft: 18, fontSize: 13 }\nconst card: CSSProperties = { padding: '8px 12px', borderRadius: 6, border: '1px solid rgba(128,128,128,0.2)', fontSize: 13 }\nconst btn: CSSProperties = { fontSize: 12, cursor: 'pointer', padding: '3px 10px', borderRadius: 4, border: '0.5px solid rgba(128,128,128,0.4)' }\nconst dangerBtn: CSSProperties = { ...btn, color: '#e55', borderColor: '#e55' }\nconst inputStyle: CSSProperties = { fontSize: 13, padding: '4px 8px', borderRadius: 4, border: '0.5px solid rgba(128,128,128,0.4)', flex: 1 }\n\ninterface PanelState {\n payload: PanelPayload | null\n error: string | null\n}\n\nexport function usePanel(): PanelState & { reload: () => void } {\n const [state, setState] = useState<PanelState>({ payload: null, error: null })\n const [tick, setTick] = useState(0)\n const reload = useCallback(() => setTick((v) => v + 1), [])\n useEffect(() => {\n const c = new AbortController()\n setState((p) => ({ ...p, error: null }))\n fetch(PANEL_PATH, { signal: c.signal })\n .then(async (r) => { if (!r.ok) throw new Error(String(r.status)); return r.json() as Promise<PanelPayload> })\n .then((payload) => { if (!c.signal.aborted) setState({ payload, error: null }) })\n .catch((e: unknown) => { if (!c.signal.aborted) setState({ payload: null, error: e instanceof Error ? e.message : String(e) }) })\n return () => c.abort()\n }, [tick])\n return { ...state, reload }\n}\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 [\n memory.source,\n memory.sessionId !== undefined ? `session ${memory.sessionId}` : null,\n memory.step !== undefined ? `step ${String(memory.step)}` : null,\n ].filter((part) => part !== null).join(' \u00B7 ')\n}\n\n// --- Search bar ---\nfunction SearchBar({ t, onResult }: { t: Translate; onResult: (hits: RecallHit[] | null) => void }) {\n const [query, setQuery] = useState('')\n const [loading, setLoading] = useState(false)\n const handleSearch = useCallback(async () => {\n if (!query.trim()) { onResult(null); return }\n setLoading(true)\n try {\n const r = await fetch(`${RECALL_PATH}?q=${encodeURIComponent(query)}&limit=10`)\n if (r.ok) { const data = await r.json() as { hits: RecallHit[] }; onResult(data.hits) }\n } finally { setLoading(false) }\n }, [query, onResult])\n return (\n <div style={{ display: 'flex', gap: 8 }}>\n <input style={inputStyle} placeholder={t('searchPlaceholder')} value={query} onChange={(e) => setQuery(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleSearch() }} />\n <button type=\"button\" style={btn} disabled={loading} onClick={handleSearch}>{loading ? '\u2026' : t('search')}</button>\n </div>\n )\n}\n\n// --- Add memory form ---\nfunction AddMemoryForm({ t, onAdded }: { t: Translate; onAdded: () => void }) {\n const [text, setText] = useState('')\n const [subject, setSubject] = useState('')\n const [kind, setKind] = useState('fact')\n const [adding, setAdding] = useState(false)\n const handleAdd = useCallback(async () => {\n if (!text.trim()) return\n setAdding(true)\n try {\n await fetch(REMEMBER_PATH, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ text, subject: subject || undefined, kind, source: 'user' }),\n })\n setText(''); setSubject(''); setKind('fact')\n onAdded()\n } finally { setAdding(false) }\n }, [text, subject, kind, onAdded])\n return (\n <div style={{ ...card, display: 'flex', flexDirection: 'column', gap: 6 }}>\n <strong style={{ fontSize: 12 }}>\u2795 {t('addMemory')}</strong>\n <input style={inputStyle} placeholder={t('memoryText')} value={text} onChange={(e) => setText(e.target.value)} />\n <div style={{ display: 'flex', gap: 8 }}>\n <input style={inputStyle} placeholder={t('subjectOptional')} value={subject} onChange={(e) => setSubject(e.target.value)} />\n <select style={{ ...btn, flex: '0 0 auto' }} value={kind} onChange={(e) => setKind(e.target.value)}>\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 <button type=\"button\" style={btn} disabled={adding} onClick={handleAdd}>{adding ? '\u2026' : t('add')}</button>\n </div>\n </div>\n )\n}\n\nexport function ContextMemoryPanel({ t }: PanelProps) {\n const { payload, error, reload } = usePanel()\n const [searchHits, setSearchHits] = useState<RecallHit[] | null>(null)\n\n const handleForget = useCallback(async (id: string, reason: string) => {\n await fetch(FORGET_PATH, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id, reason }) })\n reload()\n }, [reload])\n\n const header = (\n <header style={head}>\n <strong style={{ fontSize: 13 }}>\uD83E\uDDE0 {t('title')}</strong>\n <span style={{ flex: 1 }} />\n <button type=\"button\" style={btn} onClick={reload}>{t('refresh')}</button>\n </header>\n )\n if (error !== null) {\n return (\n <div style={wrap}>\n {header}\n <p role=\"alert\" style={{ margin: 0, fontSize: 13 }}>{t('failed')}: {error}</p>\n <button type=\"button\" onClick={reload} style={{ ...btn, alignSelf: 'flex-start' }}>{t('retry')}</button>\n </div>\n )\n }\n if (payload === null) return <p style={muted} aria-live=\"polite\">{t('loading')}</p>\n const { totals } = payload\n return (\n <div style={wrap}>\n {header}\n <span style={muted}>\n {t('active')} {totals.active} \u00B7 {t('checkPassed')} {totals.verified} \u00B7 {t('unverified')} {totals.unverified} \u00B7 {t('superseded')} {totals.superseded}\n </span>\n\n {/* Search */}\n <SearchBar t={t} onResult={setSearchHits} />\n {searchHits !== null && (\n <div style={card}>\n <strong style={{ fontSize: 12 }}>{t('searchResults')} ({searchHits.length})</strong>\n {searchHits.length === 0 ? (\n <p style={muted}>{t('noResults')}</p>\n ) : (\n <ul style={list}>\n {searchHits.map((hit) => (\n <li key={hit.memory.id} style={{ marginBottom: 4 }}>\n <code style={{ fontSize: 11 }}>{hit.memory.id}</code> ({hit.score.toFixed(2)}) {hit.memory.text}\n <span style={{ ...muted, marginLeft: 8 }}>\u2014 {hit.why}</span>\n </li>\n ))}\n </ul>\n )}\n </div>\n )}\n\n {/* Add memory */}\n <AddMemoryForm t={t} onAdded={reload} />\n\n {/* Conflicts with resolve buttons */}\n <strong style={{ fontSize: 13 }}>\u26A0\uFE0F {t('conflicts')}</strong>\n {payload.conflicts.length === 0 ? (\n <span style={muted}>{t('noConflicts')}</span>\n ) : (\n <ul style={list}>\n {payload.conflicts.map((conflict) => (\n <li key={conflict.subject}>\n <strong>{conflict.subject}</strong>\n <ul style={{ ...list, fontSize: 12, opacity: 0.85 }}>\n {conflict.memories.map((memory) => (\n <li key={memory.id}>\n <code style={{ fontSize: 11 }}>{memory.id}</code> {memory.text}\n <button type=\"button\" style={{ ...dangerBtn, marginLeft: 8 }} onClick={() => handleForget(memory.id, `conflict resolved: ${conflict.subject}`)}>\n {t('resolve')}\n </button>\n </li>\n ))}\n </ul>\n </li>\n ))}\n </ul>\n )}\n\n {/* Active memories with supersede buttons */}\n <strong style={{ fontSize: 13 }}>\u2705 {t('active')}</strong>\n {payload.active.length === 0 ? (\n <p style={{ margin: 0, fontSize: 13, opacity: 0.8 }}>{t('empty')}</p>\n ) : (\n <table style={table}>\n <thead>\n <tr>\n <th style={th}>Id</th><th style={th}>{t('memory')}</th><th style={th}>{t('kind')}</th>\n <th style={th}>{t('status')}</th><th style={th}>{t('from')}</th><th style={th} />\n </tr>\n </thead>\n <tbody>\n {payload.active.map((memory) => (\n <tr key={memory.id}>\n <td style={td}><code style={{ fontSize: 11 }}>{memory.id}</code></td>\n <td style={td}>{memory.text}</td>\n <td style={td}>{memory.kind}</td>\n <td style={td}>{checkLabel(memory, t)}</td>\n <td style={td}>{provenance(memory)}</td>\n <td style={td}>\n <button type=\"button\" style={dangerBtn} onClick={() => handleForget(memory.id, 'superseded from panel')}>\n \uD83D\uDDD1 {t('forget')}\n </button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n\n {/* Superseded */}\n {payload.superseded.length > 0 && (\n <>\n <strong style={{ fontSize: 13 }}>\uD83D\uDCE6 {t('superseded')} ({payload.superseded.length})</strong>\n <table style={table}>\n <thead>\n <tr>\n <th style={th}>Id</th><th style={th}>{t('memory')}</th><th style={th}>{t('kind')}</th>\n <th style={th}>{t('status')}</th><th style={th}>{t('from')}</th>\n </tr>\n </thead>\n <tbody>\n {payload.superseded.map((memory) => (\n <tr key={memory.id}>\n <td style={td}><code style={{ fontSize: 11 }}>{memory.id}</code></td>\n <td style={td}>{memory.text}</td>\n <td style={td}>{memory.kind}</td>\n <td style={td}>{checkLabel(memory, t)}</td>\n <td style={td}>{provenance(memory)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/lib/routes.d.ts CHANGED
@@ -11,4 +11,7 @@ import type { Context } from '@deepseek-ai/cordis';
11
11
  import type { MemoryService } from './index.js';
12
12
  /** The panel path, shared with the browser half so the two cannot drift apart. */
13
13
  export declare const MEMORY_PANEL_PATH = "/api/context-memory.panel";
14
+ export declare const MEMORY_RECALL_PATH = "/api/context-memory.recall";
15
+ export declare const MEMORY_REMEMBER_PATH = "/api/context-memory.remember";
16
+ export declare const MEMORY_FORGET_PATH = "/api/context-memory.forget";
14
17
  export declare function registerMemoryRoutes(ctx: Context, memory: MemoryService): void;
package/lib/routes.js CHANGED
@@ -9,6 +9,9 @@
9
9
  */
10
10
  /** The panel path, shared with the browser half so the two cannot drift apart. */
11
11
  export const MEMORY_PANEL_PATH = '/api/context-memory.panel';
12
+ export const MEMORY_RECALL_PATH = '/api/context-memory.recall';
13
+ export const MEMORY_REMEMBER_PATH = '/api/context-memory.remember';
14
+ export const MEMORY_FORGET_PATH = '/api/context-memory.forget';
12
15
  export function registerMemoryRoutes(ctx, memory) {
13
16
  // Injecting rather than reading `ctx.connection` directly: a plain property access on
14
17
  // a service that was not declared throws, and the web connection is absent in headless
@@ -30,12 +33,69 @@ export function registerMemoryRoutes(ctx, memory) {
30
33
  fetch: async () => {
31
34
  const summary = memory.verify();
32
35
  return Response.json(summary, {
33
- // The verification is the answer, not an error: a violated memory is reported
34
- // with 200 and the caller decides what it means. Only a broken guard is a
35
- // defect in the request path itself, and that is reported in the payload.
36
36
  headers: { 'cache-control': 'no-store' },
37
37
  });
38
38
  },
39
39
  });
40
+ // Recall (search) — GET with query param
41
+ connection.fetch.register({
42
+ path: MEMORY_RECALL_PATH,
43
+ methods: ['GET'],
44
+ requestBody: 'buffered',
45
+ fetch: async (request) => {
46
+ const url = new URL(request.url);
47
+ const query = url.searchParams.get('q') ?? '';
48
+ const limit = Number(url.searchParams.get('limit') ?? '10');
49
+ const hits = memory.recall(query, limit);
50
+ return Response.json({ hits }, { headers: { 'cache-control': 'no-store' } });
51
+ },
52
+ });
53
+ // Remember (add) — POST with body
54
+ connection.fetch.register({
55
+ path: MEMORY_REMEMBER_PATH,
56
+ methods: ['POST'],
57
+ requestBody: 'buffered',
58
+ fetch: async (request) => {
59
+ let body;
60
+ try {
61
+ body = await request.json();
62
+ }
63
+ catch {
64
+ return Response.json({ error: 'invalid JSON' }, { status: 400 });
65
+ }
66
+ if (!body.text)
67
+ return Response.json({ error: 'missing text' }, { status: 400 });
68
+ const input = { text: body.text };
69
+ if (body.subject)
70
+ input.subject = body.subject;
71
+ if (body.kind)
72
+ input.kind = body.kind;
73
+ if (body.source)
74
+ input.source = body.source;
75
+ const mem = memory.remember(input);
76
+ return Response.json(mem);
77
+ },
78
+ });
79
+ // Forget (supersede) — POST with id + reason
80
+ connection.fetch.register({
81
+ path: MEMORY_FORGET_PATH,
82
+ methods: ['POST'],
83
+ requestBody: 'buffered',
84
+ fetch: async (request) => {
85
+ let body;
86
+ try {
87
+ body = await request.json();
88
+ }
89
+ catch {
90
+ return Response.json({ error: 'invalid JSON' }, { status: 400 });
91
+ }
92
+ if (!body.id)
93
+ return Response.json({ error: 'missing id' }, { status: 400 });
94
+ const ok = memory.forget(body.id, body.reason ?? 'superseded from panel', body.replacedBy);
95
+ if (!ok)
96
+ return Response.json({ error: 'not found' }, { status: 404 });
97
+ return Response.json({ ok: true });
98
+ },
99
+ });
40
100
  });
41
101
  }
package/package.json CHANGED
@@ -1,32 +1,68 @@
1
1
  {
2
2
  "name": "dsh-context-memory",
3
- "version": "0.2.0",
3
+ "version": "0.3.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",
7
7
  "types": "lib/index.d.ts",
8
- "bin": { "dsh-context-memory": "lib/bin.js" },
8
+ "bin": {
9
+ "dsh-context-memory": "lib/bin.js"
10
+ },
9
11
  "exports": {
10
- ".": { "types": "./lib/index.d.ts", "default": "./lib/index.js" },
11
- "./client": { "types": "./lib/client/index.d.ts", "default": "./lib/memory.web.js" },
12
+ ".": {
13
+ "types": "./lib/index.d.ts",
14
+ "default": "./lib/index.js"
15
+ },
16
+ "./client": {
17
+ "types": "./lib/client/index.d.ts",
18
+ "default": "./lib/memory.web.js"
19
+ },
12
20
  "./package.json": "./package.json"
13
21
  },
14
- "files": ["lib", "cordis.patch.yml", "README.md", "CHANGELOG.md"],
15
- "keywords": ["dsh", "dsh-plugin", "deepseek-harness", "memory", "context", "recall", "persistence"],
22
+ "files": [
23
+ "lib",
24
+ "cordis.patch.yml",
25
+ "README.md",
26
+ "CHANGELOG.md"
27
+ ],
28
+ "keywords": [
29
+ "dsh",
30
+ "dsh-plugin",
31
+ "deepseek-harness",
32
+ "memory",
33
+ "context",
34
+ "recall",
35
+ "persistence"
36
+ ],
16
37
  "dsh": {
17
- "bundle": { "patch": "./cordis.patch.yml" },
38
+ "bundle": {
39
+ "patch": "./cordis.patch.yml"
40
+ },
18
41
  "client": {
19
42
  "platform": "web",
20
- "inject": ["slots", "connection"]
43
+ "inject": [
44
+ "@deepseek-ai/dsh-client-ui-renderer",
45
+ "@deepseek-ai/dsh-client-locale",
46
+ "@deepseek-ai/dsh-client-ui-settings"
47
+ ]
21
48
  }
22
49
  },
23
50
  "license": "MIT",
24
51
  "author": "hj01857655",
25
- "repository": { "type": "git", "url": "git+https://github.com/hj01857655/dsh-context-memory.git" },
26
- "bugs": { "url": "https://github.com/hj01857655/dsh-context-memory/issues" },
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/hj01857655/dsh-context-memory.git"
55
+ },
56
+ "bugs": {
57
+ "url": "https://github.com/hj01857655/dsh-context-memory/issues"
58
+ },
27
59
  "homepage": "https://github.com/hj01857655/dsh-context-memory#readme",
28
- "engines": { "node": ">=20" },
29
- "peerDependencies": { "@deepseek-ai/cordis": "^4.0.1" },
60
+ "engines": {
61
+ "node": ">=20"
62
+ },
63
+ "peerDependencies": {
64
+ "@deepseek-ai/cordis": "^4.0.1"
65
+ },
30
66
  "devDependencies": {
31
67
  "@deepseek-ai/cordis": "^4.0.2",
32
68
  "@types/node": "^22.20.3",