dsh-memoir 0.4.3
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/LICENSE +201 -0
- package/README.md +242 -0
- package/cordis.patch.yml +21 -0
- package/lib/autodistill.d.ts +65 -0
- package/lib/autodistill.js +94 -0
- package/lib/client.js +973 -0
- package/lib/client.js.map +7 -0
- package/lib/index.d.ts +98 -0
- package/lib/index.js +204 -0
- package/lib/retrieval.d.ts +151 -0
- package/lib/retrieval.js +363 -0
- package/lib/routes.d.ts +77 -0
- package/lib/routes.js +253 -0
- package/lib/selector.d.ts +89 -0
- package/lib/selector.js +179 -0
- package/lib/snapshot.d.ts +88 -0
- package/lib/snapshot.js +111 -0
- package/lib/store.d.ts +248 -0
- package/lib/store.js +553 -0
- package/lib/tools.d.ts +35 -0
- package/lib/tools.js +292 -0
- package/package.json +78 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/client/index.tsx", "../src/client/controller.ts", "../src/client/api.ts", "../src/client/cwd.ts", "../src/client/i18n.ts", "../src/client/styles.ts", "../src/client/sidebar-entry.ts", "../src/client/mount.tsx", "../src/client/panel.tsx"],
|
|
4
|
+
"sourcesContent": ["/**\n * Browser-half entry for the dsh-memoir plugin \u2014 runs inside the dsh web GUI.\n * Mounts the sidebar entry row (toggles the panel) and the memoir panel in\n * the center column, bound to the active session's workspace. Failure policy:\n * DOM mounting problems are logged, never thrown \u2014 the web shell fails the\n * whole boot when a plugin apply throws, and an external plugin must not take\n * the GUI down.\n */\n\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport { PanelController } from './controller.js'\nimport { MemoirApi } from './api.js'\nimport { createCwdTracker } from './cwd.js'\nimport { makeT } from './i18n.js'\nimport { PANEL_CSS } from './styles.js'\nimport { mountSidebarEntry } from './sidebar-entry.js'\nimport { mountPanel } from './mount.jsx'\n\n/** Required services (fiber inject waiting \u2014 the runtime must be up first). */\nexport const inject = ['sessions']\n\n/** Inject the panel stylesheet once (the loader removes plugin-owned tags on unload). */\nfunction injectStyles(): void {\n if (document.querySelector('style[data-plugin=\"dsh-memoir\"]') !== null) return\n const tag = document.createElement('style')\n tag.dataset.plugin = 'dsh-memoir'\n tag.textContent = PANEL_CSS\n document.head.appendChild(tag)\n}\n\n/**\n * Mount the memoir panel.\n * @param ctx - client root context (sessions service).\n */\nexport function apply(ctx: ClientContext): void {\n const t = makeT(document)\n const disposers: Array<() => void> = []\n try {\n const controller = new PanelController()\n const api = new MemoirApi()\n const cwdTracker = createCwdTracker(ctx.sessions)\n injectStyles()\n disposers.push(mountSidebarEntry(controller, t))\n disposers.push(mountPanel(controller, api, cwdTracker, t))\n } catch (error) {\n // DOM failures degrade the panel, never the GUI.\n console.warn('[dsh-memoir] mount failed:', error)\n }\n ctx.effect(() => () => {\n for (const dispose of disposers.splice(0)) dispose()\n }, 'dsh-memoir: ui mounts')\n}\n", "/**\n * Panel open/close state machine \u2014 pure, no DOM, unit-testable. Mirrors the\n * sibling panel controllers: a snapshot + subscribe contract drives the\n * sidebar entry highlight and the panel visibility attributes.\n */\n\nexport interface PanelSnapshot {\n panelOpen: boolean\n}\n\nexport class PanelController {\n private state: PanelSnapshot = { panelOpen: false }\n private listeners = new Set<() => void>()\n\n /** The current immutable snapshot. */\n getSnapshot(): PanelSnapshot {\n return this.state\n }\n\n /** Subscribe to state changes; returns an unsubscribe function. */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n open(): void {\n this.patch({ panelOpen: true })\n }\n\n close(): void {\n this.patch({ panelOpen: false })\n }\n\n toggle(): void {\n this.patch({ panelOpen: !this.state.panelOpen })\n }\n\n /** Replace the snapshot; notifies only when the observable state changed. */\n private patch(next: Partial<PanelSnapshot>): void {\n const prev = this.state\n this.state = { ...prev, ...next }\n if (this.state.panelOpen === prev.panelOpen) return\n for (const listener of [...this.listeners]) listener()\n }\n}\n", "/**\r\n * Browser API client for the host /api/dsh-memoir routes. The only data path\r\n * the panel uses \u2014 same-origin fetch, JSON envelope { ok, value | error }.\r\n * The fetch implementation is injectable for tests.\r\n */\r\n\r\nimport type { SectionKey } from './types.ts'\r\n\r\n/** Error carrying the route's JSON error message. */\r\nexport class MemoirApiError extends Error {\r\n constructor(message: string) {\r\n super(message)\r\n this.name = 'MemoirApiError'\r\n }\r\n}\r\n\r\ninterface EnvelopeResponse {\r\n status: number\r\n ok: boolean\r\n json(): Promise<unknown>\r\n}\r\n\r\n/** Parse the envelope or throw a MemoirApiError (transport or route error). */\r\nexport async function readEnvelope(response: EnvelopeResponse): Promise<unknown> {\r\n let body: unknown\r\n try {\r\n body = await response.json()\r\n } catch {\r\n throw new MemoirApiError(`HTTP ${response.status}: invalid JSON response`)\r\n }\r\n if (typeof body !== 'object' || body === null || (body as { ok?: boolean }).ok !== true) {\r\n const record = body as { error?: { message?: string } } | null\r\n const message = record && typeof record.error === 'object' && record.error !== null && typeof record.error.message === 'string'\r\n ? record.error.message\r\n : `HTTP ${response.status}`\r\n throw new MemoirApiError(message)\r\n }\r\n return (body as { value: unknown }).value\r\n}\r\n\r\n/** Query-string helper (omits undefined/empty values). */\r\nexport function query(params: Record<string, string | undefined>): string {\r\n const search = new URLSearchParams()\r\n for (const [key, value] of Object.entries(params)) {\r\n if (value !== undefined && value !== '') search.set(key, String(value))\r\n }\r\n const text = search.toString()\r\n return text === '' ? '' : '?' + text\r\n}\r\n\r\n/** One memory entry as the panel sees it over the wire. */\r\nexport interface WireEntry {\r\n id: string\r\n section: SectionKey\r\n title?: string\r\n content: string\r\n time: number\r\n sessionId?: string\r\n}\r\n\r\nexport interface WireProject {\r\n key: string\r\n path: string\r\n title: string\r\n updatedAt: number\r\n entries: WireEntry[]\r\n}\r\n\r\n/** The host /diagnostics payload (v0.4 observability, v0.4.2 extended). */\r\nexport interface WireDiagnostics {\r\n storeRevision: number\r\n snapshotEpoch: number\r\n cache: {\r\n revision: number\r\n epoch: number\r\n loads: number\r\n hits: number\r\n misses: number\r\n hitRate: number\r\n fileReads: number\r\n statProbes: number\r\n corruptBackups: number\r\n renders: number\r\n renderComputes: number\r\n renderHitRate: number\r\n lastLoadMs?: number\r\n }\r\n snapshotCount: number\r\n snapshotMax: number\r\n hotMemory: { selected: number; total: number; estimatedTokens: number } | null\r\n /** v0.4.2: retrieval index / query cache / last query. */\r\n retrieval: {\r\n index: { docs: number; terms: number; epoch: number } | null\r\n cache: { hits: number; misses: number; evictions: number; hitRate: number; size: number; capacity: number }\r\n lastQuery: { query: string; latencyMs: number; candidates: number; returned: number; at: number } | null\r\n }\r\n /** v0.4.2: the most recently frozen session snapshot. */\r\n snapshot: { hash: string; createdAt: number; storeRevision: number } | null\r\n config: {\r\n hotMemoryTokens: number\r\n hotMemoryMaxTokens: number\r\n readDefaultLimit: number\r\n readMaxLimit: number\r\n sessionSnapshotMax: number\r\n queryCacheSize: number\r\n }\r\n}\r\n\r\n/** One ranked /search hit (v0.4.2: shared RetrievalEngine order). */\r\nexport interface WireSearchResult {\r\n projectPath: string\r\n entry: WireEntry\r\n score: number\r\n}\r\n\r\n/** The /hot-memory inspector payload (v0.4.2). */\r\nexport interface WireHotMemory {\r\n text: string\r\n selected: WireEntry[]\r\n total: number\r\n estimatedTokens: number\r\n}\r\n\r\nexport interface RecordPayload {\r\n path: string\r\n section: SectionKey\r\n title?: string\r\n content: string\r\n}\r\n\r\ntype FetchLike = (input: string, init?: RequestInit) => Promise<EnvelopeResponse>\r\n\r\n/** The browser half's only data entry point. */\r\nexport class MemoirApi {\r\n private readonly fetchImpl: FetchLike\r\n\r\n /**\r\n * @param fetchImpl - injectable fetch (defaults to globalThis.fetch).\r\n */\r\n constructor(fetchImpl?: FetchLike) {\r\n this.fetchImpl = fetchImpl ?? ((...args) => globalThis.fetch(...args) as Promise<EnvelopeResponse>)\r\n }\r\n\r\n /** Read one project's memory (empty project shape when unknown). */\r\n async project(path: string, options: { section?: SectionKey; query?: string } = {}): Promise<{ project: WireProject }> {\r\n const response = await this.fetchImpl('/api/dsh-memoir/project' + query({ path, ...options }))\r\n return readEnvelope(response) as Promise<{ project: WireProject }>\r\n }\r\n\r\n /** Read the cross-project global index. */\r\n async global(options: { section?: SectionKey; query?: string } = {}): Promise<{ projects: WireProject[] }> {\r\n const response = await this.fetchImpl('/api/dsh-memoir/global' + query({ ...options }))\r\n return readEnvelope(response) as Promise<{ projects: WireProject[] }>\r\n }\r\n\r\n /** Read runtime diagnostics (cache hit rates, snapshot/hot-memory stats). */\r\n async diagnostics(path?: string): Promise<WireDiagnostics> {\r\n const response = await this.fetchImpl('/api/dsh-memoir/diagnostics' + query({ path }))\r\n return readEnvelope(response) as Promise<WireDiagnostics>\r\n }\r\n\r\n /**\r\n * Ranked search over the host RetrievalEngine (v0.4.2) \u2014 the same ranking\r\n * memoir_read uses, so the GUI search and the agent recall never diverge.\r\n */\r\n async search(options: { scope: 'project' | 'global' | 'all'; path?: string; section?: SectionKey; query: string; limit?: number }): Promise<{ results: WireSearchResult[] }> {\r\n const response = await this.fetchImpl('/api/dsh-memoir/search' + query({\r\n scope: options.scope,\r\n path: options.path,\r\n section: options.section,\r\n query: options.query,\r\n limit: options.limit === undefined ? undefined : String(options.limit),\r\n }))\r\n return readEnvelope(response) as Promise<{ results: WireSearchResult[] }>\r\n }\r\n\r\n /** Hot-memory preview for one workspace (the inspector). */\r\n async hotMemory(path: string): Promise<{ hotMemory: WireHotMemory | null }> {\r\n const response = await this.fetchImpl('/api/dsh-memoir/hot-memory' + query({ path }))\r\n return readEnvelope(response) as Promise<{ hotMemory: WireHotMemory | null }>\r\n }\r\n\r\n /** Record one entry (host regenerates PROJECT_MEMORY.md). */\r\n async record(payload: RecordPayload): Promise<{ entry: WireEntry }> {\r\n const response = await this.fetchImpl('/api/dsh-memoir/entries', {\r\n method: 'POST',\r\n headers: { 'content-type': 'application/json' },\r\n body: JSON.stringify(payload),\r\n })\r\n return readEnvelope(response) as Promise<{ entry: WireEntry }>\r\n }\r\n\r\n /** Delete one entry by id. */\r\n async remove(payload: { path: string; id: string }): Promise<{ removed: boolean }> {\r\n const response = await this.fetchImpl('/api/dsh-memoir/entries', {\r\n method: 'DELETE',\r\n headers: { 'content-type': 'application/json' },\r\n body: JSON.stringify(payload),\r\n })\r\n return readEnvelope(response) as Promise<{ removed: boolean }>\r\n }\r\n}\r\n", "/**\n * Workspace tracker: projects the client sessions service's active session\n * cwd into a useSyncExternalStore-compatible subscription. Pure logic \u2014 the\n * sessions service is injected, unit-testable with a mock.\n */\n\nexport interface SessionRecordLike {\n cwd?: string\n}\n\nexport interface SessionsLike {\n list: {\n getSnapshot(): { current: string | undefined; byId: Record<string, SessionRecordLike> }\n subscribe(listener: () => void): () => void\n }\n}\n\n/** Read the active session's cwd from a sessions service snapshot ('' when none). */\nexport function readCwd(sessions: SessionsLike): string {\n const snapshot = sessions.list.getSnapshot()\n const id = snapshot.current\n const cwd = id === undefined ? undefined : snapshot.byId[id]?.cwd\n return typeof cwd === 'string' && cwd !== '' ? cwd : ''\n}\n\n/** A useSyncExternalStore-compatible tracker over the sessions service. */\nexport interface CwdTracker {\n getSnapshot(): string\n subscribe(listener: () => void): () => void\n}\n\n/**\n * Build a { getSnapshot, subscribe } tracker over the sessions service.\n * @param sessions - the client sessions service (with `list.getSnapshot` and\n * `list.subscribe`).\n */\nexport function createCwdTracker(sessions: SessionsLike): CwdTracker {\n let current = readCwd(sessions)\n const listeners = new Set<() => void>()\n const tracker: CwdTracker = {\n getSnapshot: () => current,\n subscribe: (listener) => {\n listeners.add(listener)\n const dispose = sessions.list.subscribe(() => {\n const next = readCwd(sessions)\n if (next === current) return\n current = next\n for (const l of [...listeners]) l()\n })\n return () => {\n listeners.delete(listener)\n dispose()\n }\n },\n }\n return tracker\n}\n", "/**\r\n * Bilingual surface copy (zh-first). Pure \u2014 no DOM, unit-testable.\r\n * The shell owns <html lang>; the panel reads it at render time.\r\n */\r\n\r\nexport type Lang = 'zh' | 'en'\r\n\r\nexport const dictionaries: Record<Lang, Record<string, string>> = {\r\n zh: {\r\n 'entry.label': '\u8BB0\u5FC6',\r\n 'entry.tooltip': '\u6253\u5F00\u8BB0\u5FC6\u9762\u677F\uFF08\u9879\u76EE\u8BB0\u5FC6 / \u5168\u5C40\u8BB0\u5FC6\uFF09',\r\n 'panel.title': '\u8BB0\u5FC6 Memoir',\r\n 'panel.close': '\u5173\u95ED',\r\n 'panel.refresh': '\u5237\u65B0',\r\n 'tab.project': '\u9879\u76EE\u8BB0\u5FC6',\r\n 'tab.global': '\u5168\u5C40\u8BB0\u5FC6',\r\n 'toolbar.search': '\u641C\u7D22\u6807\u9898\u4E0E\u6B63\u6587\u2026',\r\n 'toolbar.add': '\u6DFB\u52A0\u8BB0\u5FC6',\r\n 'toolbar.cancel': '\u53D6\u6D88',\r\n 'form.section': '\u5206\u7C7B',\r\n 'form.title': '\u6807\u9898\uFF08\u53EF\u9009\uFF09',\r\n 'form.content': '\u5185\u5BB9',\r\n 'form.submit': '\u8BB0\u5F55',\r\n 'form.placeholder.title': '\u4E00\u53E5\u8BDD\u6807\u9898\uFF0C\u5982\u300C\u4FEE\u590D pet \u60AC\u505C\u95EA\u9000\u300D',\r\n 'form.placeholder.content': '\u5177\u4F53\u505A\u4E86\u4EC0\u4E48\u3001\u7ED3\u8BBA\u3001\u6559\u8BAD\u6216\u4E0B\u4E00\u6B65\u600E\u4E48\u505A\u3002\u5EFA\u8BAE\u7CBE\u70BC\u3001\u53EF\u6267\u884C\u3002',\r\n 'empty.project': '\u672C\u9879\u76EE\u6682\u65E0\u6301\u4E45\u8BB0\u5FC6',\r\n 'empty.projectHint': '\u8BA9 agent \u7528 memoir_record \u6C89\u6DC0\u7ECF\u9A8C\uFF0C\u6216\u70B9\u300C\u6DFB\u52A0\u8BB0\u5FC6\u300D\u624B\u52A8\u8BB0\u5F55\u3002',\r\n 'empty.workspace': '\u672A\u6253\u5F00\u9879\u76EE\u4F1A\u8BDD',\r\n 'empty.workspaceHint': '\u6253\u5F00\u4E00\u4E2A\u9879\u76EE\u4F1A\u8BDD\u540E\uFF0C\u8FD9\u91CC\u663E\u793A\u8BE5\u9879\u76EE\u7684\u6301\u4E45\u8BB0\u5FC6\u3002',\r\n 'empty.global': '\u5168\u5C40\u7D22\u5F15\u4E3A\u7A7A',\r\n 'empty.globalHint': '\u5728\u4EFB\u4F55\u9879\u76EE\u91CC\u7528 memoir_record \u8BB0\u5F55\u540E\uFF0C\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u4F9B\u8DE8\u9879\u76EE\u68C0\u7D22\u3002',\r\n 'empty.search': '\u6CA1\u6709\u5339\u914D\u7684\u6761\u76EE',\r\n 'load.failed': '\u52A0\u8F7D\u5931\u8D25',\r\n 'record.failed': '\u8BB0\u5F55\u5931\u8D25',\r\n 'delete.failed': '\u5220\u9664\u5931\u8D25',\r\n 'delete.confirm': '\u5220\u9664\u8FD9\u6761\u8BB0\u5FC6\uFF1F',\r\n 'updated': '\u66F4\u65B0\u4E8E',\r\n 'entries': '\u6761',\r\n 'session': '\u4F1A\u8BDD',\r\n 'sections.work': '\u5DE5\u4F5C\u8BB0\u5F55',\r\n 'sections.lessons': '\u7ECF\u9A8C\u6559\u8BAD',\r\n 'sections.actions': '\u884C\u52A8\u6307\u5357',\r\n 'sections.note': '\u5907\u6CE8',\r\n 'diag.title': 'Memory Diagnostics',\r\n 'diag.revision': 'Store revision',\r\n 'diag.snapshot': 'Session snapshots',\r\n 'diag.cache': 'Store cache',\r\n 'diag.render': 'Render cache',\r\n 'diag.hot': 'Hot memory',\r\n 'diag.retrieval': 'Retrieval \u7D22\u5F15',\r\n 'diag.qcache': 'Query cache',\r\n 'diag.lastQuery': '\u6700\u8FD1\u67E5\u8BE2',\r\n 'diag.snapshotInfo': '\u4F1A\u8BDD\u5FEB\u7167',\r\n 'inspector.title': 'Hot Memory \u9884\u89C8',\r\n 'inspector.empty': '\u672C\u5DE5\u4F5C\u533A\u6682\u65E0 Hot Memory',\r\n 'search.ranked': '\u6309\u76F8\u5173\u6027\u6392\u5E8F',\r\n },\r\n en: {\r\n 'entry.label': 'Memory',\r\n 'entry.tooltip': 'Open the memory panel (project / global)',\r\n 'panel.title': 'Memory Memoir',\r\n 'panel.close': 'Close',\r\n 'panel.refresh': 'Refresh',\r\n 'tab.project': 'Project',\r\n 'tab.global': 'Global',\r\n 'toolbar.search': 'Search titles and content\u2026',\r\n 'toolbar.add': 'Add entry',\r\n 'toolbar.cancel': 'Cancel',\r\n 'form.section': 'Section',\r\n 'form.title': 'Title (optional)',\r\n 'form.content': 'Content',\r\n 'form.submit': 'Record',\r\n 'form.placeholder.title': 'A one-line title, e.g. \"Fix pet hover crash\"',\r\n 'form.placeholder.content': 'What was done, the conclusion, the lesson, or the next step. Concise and actionable.',\r\n 'empty.project': 'No persistent memory in this project yet',\r\n 'empty.projectHint': 'Ask the agent to distill via memoir_record, or add an entry manually.',\r\n 'empty.workspace': 'No project session open',\r\n 'empty.workspaceHint': 'Open a project session to see its persistent memory here.',\r\n 'empty.global': 'The global index is empty',\r\n 'empty.globalHint': 'Entries recorded via memoir_record in any project appear here for cross-project search.',\r\n 'empty.search': 'No matching entries',\r\n 'load.failed': 'Failed to load',\r\n 'record.failed': 'Failed to record',\r\n 'delete.failed': 'Failed to delete',\r\n 'delete.confirm': 'Delete this entry?',\r\n 'updated': 'Updated',\r\n 'entries': 'entries',\r\n 'session': 'session',\r\n 'sections.work': 'Work Log',\r\n 'sections.lessons': 'Lessons Learned',\r\n 'sections.actions': 'Action Guide',\r\n 'sections.note': 'Notes',\r\n 'diag.title': 'Memory Diagnostics',\r\n 'diag.revision': 'Store revision',\r\n 'diag.snapshot': 'Session snapshots',\r\n 'diag.cache': 'Store cache',\r\n 'diag.render': 'Render cache',\r\n 'diag.hot': 'Hot memory',\r\n 'diag.retrieval': 'Retrieval index',\r\n 'diag.qcache': 'Query cache',\r\n 'diag.lastQuery': 'Last query',\r\n 'diag.snapshotInfo': 'Session snapshot',\r\n 'inspector.title': 'Hot Memory Inspector',\r\n 'inspector.empty': 'No hot memory in this workspace yet',\r\n 'search.ranked': 'Ranked by relevance',\r\n },\r\n}\r\n\r\n/** Section keys in canonical display order. */\r\nexport const SECTION_KEYS: string[] = ['work', 'lessons', 'actions', 'note']\r\n\r\n/** Minimal document surface for language detection. */\r\nexport interface DocumentLike {\r\n documentElement?: { lang?: string }\r\n}\r\n\r\n/** Current UI language: zh when the shell's <html lang> starts with zh. */\r\nexport function detectLanguage(documentLike: DocumentLike | undefined): Lang {\r\n const lang = documentLike?.documentElement?.lang\r\n return typeof lang === 'string' && lang.startsWith('zh') ? 'zh' : 'en'\r\n}\r\n\r\n/** Pure translation lookup: `translate(lang, key)` with en fallback. */\r\nexport function translate(lang: string, key: string): string {\r\n const dict = dictionaries[lang as Lang] ?? dictionaries.en\r\n return Object.prototype.hasOwnProperty.call(dict, key) ? dict[key] : (dictionaries.en[key] ?? key)\r\n}\r\n\r\n/** Bound translator reading the live <html lang> at each call. */\r\nexport function makeT(documentLike: DocumentLike): (key: string) => string {\r\n return (key) => translate(detectLanguage(documentLike), key)\r\n}\r\n", "/**\r\n * Panel stylesheet (plain string, injected as <style data-plugin=\"dsh-memoir\">).\r\n * Class names are literal memoir-* prefixes (no CSS-module hashing) so the\r\n * plain-DOM sidebar entry and the React panel share them. Dark mode follows\r\n * the shell's body[data-ds-dark-theme] marker via CSS only.\r\n */\r\n\r\nexport const PANEL_CSS = `\r\n[data-dsh-memoir-view] {\r\n display: none;\r\n position: absolute;\r\n inset: 0;\r\n z-index: 20;\r\n background: var(--bg-panel, #ffffff);\r\n color: var(--text-primary, #1f2328);\r\n font-size: 13px;\r\n}\r\nhtml[data-dsh-memoir-active] [data-dsh-memoir-view] {\r\n display: flex;\r\n flex-direction: column;\r\n}\r\nhtml[data-dsh-memoir-active] [data-pane=\"conversation\"] > *:not([data-dsh-memoir-view]) {\r\n display: none;\r\n}\r\n\r\n.memoir-panel { display: flex; flex-direction: column; height: 100%; overflow: hidden; }\r\n.memoir-header { display: flex; align-items: center; gap: 8px; padding: 12px 14px 8px; }\r\n.memoir-title { font-size: 15px; font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\r\n.memoir-subtitle { font-size: 11px; opacity: .65; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\r\n.memoir-iconbtn {\r\n border: 1px solid transparent; background: transparent; color: inherit;\r\n border-radius: 6px; padding: 4px 8px; cursor: pointer; font-size: 12px;\r\n display: inline-flex; align-items: center; gap: 4px;\r\n}\r\n.memoir-iconbtn:hover { background: var(--bg-hover, rgba(0,0,0,.06)); }\r\n\r\n.memoir-tabs { display: flex; gap: 4px; padding: 0 14px; border-bottom: 1px solid var(--border, rgba(0,0,0,.1)); }\r\n.memoir-tab {\r\n border: none; background: transparent; color: inherit; padding: 7px 12px;\r\n cursor: pointer; font-size: 13px; border-bottom: 2px solid transparent; opacity: .75;\r\n}\r\n.memoir-tab:hover { opacity: 1; }\r\n.memoir-tab[data-active=\"true\"] { opacity: 1; border-bottom-color: var(--accent, #3b82f6); font-weight: 600; }\r\n\r\n.memoir-toolbar { display: flex; gap: 8px; padding: 8px 14px; }\r\n.memoir-search {\r\n flex: 1; border: 1px solid var(--border, rgba(0,0,0,.15)); background: transparent;\r\n color: inherit; border-radius: 6px; padding: 6px 10px; font-size: 13px; outline: none;\r\n}\r\n.memoir-search:focus { border-color: var(--accent, #3b82f6); }\r\n.memoir-primary {\r\n border: 1px solid transparent; background: var(--accent, #3b82f6); color: #fff;\r\n border-radius: 6px; padding: 6px 12px; cursor: pointer; font-size: 13px;\r\n}\r\n.memoir-primary:hover { filter: brightness(1.05); }\r\n\r\n.memoir-body { flex: 1; overflow-y: auto; padding: 4px 14px 16px; }\r\n.memoir-section-title {\r\n font-size: 12px; font-weight: 600; opacity: .8; margin: 14px 0 6px;\r\n display: flex; align-items: center; gap: 6px;\r\n}\r\n.memoir-count { font-weight: 400; opacity: .6; }\r\n.memoir-entry {\r\n border: 1px solid var(--border, rgba(0,0,0,.08)); border-radius: 8px;\r\n padding: 8px 10px; margin-bottom: 8px; background: var(--bg-card, rgba(0,0,0,.02));\r\n}\r\n.memoir-entry-meta { display: flex; align-items: center; gap: 6px; font-size: 11px; opacity: .7; margin-bottom: 3px; }\r\n.memoir-chip {\r\n border-radius: 999px; padding: 0 7px; font-size: 11px; line-height: 18px;\r\n background: var(--chip-bg, rgba(59,130,246,.12)); color: var(--chip-fg, #2563eb);\r\n}\r\n.memoir-entry-title { font-weight: 600; margin-bottom: 2px; }\r\n.memoir-entry-content { white-space: pre-wrap; word-break: break-word; line-height: 1.5; }\r\n.memoir-delete {\r\n float: right; border: none; background: transparent; color: inherit; opacity: .45;\r\n cursor: pointer; border-radius: 4px; padding: 2px 6px; font-size: 12px;\r\n}\r\n.memoir-delete:hover { opacity: 1; background: var(--danger-bg, rgba(220,38,38,.12)); color: var(--danger, #dc2626); }\r\n\r\n.memoir-project-card { border: 1px solid var(--border, rgba(0,0,0,.1)); border-radius: 10px; padding: 10px 12px; margin-bottom: 12px; }\r\n.memoir-project-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 2px; }\r\n.memoir-project-title { font-weight: 600; font-size: 14px; }\r\n.memoir-project-path { font-size: 11px; opacity: .6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\r\n.memoir-project-meta { font-size: 11px; opacity: .6; margin-bottom: 6px; }\r\n\r\n.memoir-empty { text-align: center; padding: 48px 20px; opacity: .7; }\r\n.memoir-empty-title { font-size: 14px; font-weight: 600; margin-bottom: 6px; }\r\n.memoir-empty-hint { font-size: 12px; }\r\n.memoir-error { color: var(--danger, #dc2626); padding: 8px 14px; font-size: 12px; }\r\n\r\n.memoir-form { border: 1px solid var(--border, rgba(0,0,0,.1)); border-radius: 10px; padding: 12px; margin: 4px 14px 14px; }\r\n.memoir-form-row { display: flex; gap: 8px; margin-bottom: 8px; }\r\n.memoir-field { flex: 1; display: flex; flex-direction: column; gap: 4px; }\r\n.memoir-field label { font-size: 11px; opacity: .7; }\r\n.memoir-field input, .memoir-field select, .memoir-field textarea {\r\n border: 1px solid var(--border, rgba(0,0,0,.15)); background: transparent; color: inherit;\r\n border-radius: 6px; padding: 6px 8px; font-size: 13px; font-family: inherit; outline: none;\r\n}\r\n.memoir-field textarea { min-height: 72px; resize: vertical; }\r\n.memoir-form-actions { display: flex; justify-content: flex-end; gap: 8px; }\r\n\r\n/* Sidebar entry row (plain DOM, matches the shell's nav rows). */\r\n.memoir-entry-row {\r\n display: flex; align-items: center; gap: 8px; width: 100%;\r\n border: none; background: transparent; color: inherit; cursor: pointer;\r\n padding: 7px 10px; border-radius: 6px; font-size: 13px; text-align: left;\r\n}\r\n.memoir-entry-row:hover { background: var(--bg-hover, rgba(0,0,0,.06)); }\r\n.memoir-entry-row[data-active=\"true\"] { background: var(--bg-hover, rgba(0,0,0,.08)); }\r\n.memoir-entry-icon { display: inline-flex; }\r\n\r\n/* v0.4.2 ranked search */\r\n.memoir-ranked-note { font-size: 11px; opacity: .65; margin: 4px 0 8px; }\r\n.memoir-score {\r\n float: right; border-radius: 999px; padding: 0 7px; font-size: 10px; line-height: 16px;\r\n background: var(--chip-bg, rgba(59,130,246,.12)); color: var(--chip-fg, #2563eb); opacity: .8;\r\n}\r\n\r\n/* v0.4.2 Hot Memory Inspector */\r\n.memoir-inspector { margin: 0 14px; border-top: 1px solid var(--border, rgba(0,0,0,.1)); padding-top: 8px; }\r\n.memoir-inspector-body {\r\n font-size: 11px; opacity: .85; margin: 6px 0 0; padding: 8px 10px;\r\n border: 1px solid var(--border, rgba(0,0,0,.1)); border-radius: 8px;\r\n background: var(--bg-card, rgba(0,0,0,.02)); white-space: pre-wrap; word-break: break-word;\r\n font-family: inherit; max-height: 220px; overflow-y: auto;\r\n}\r\n\r\nbody[data-ds-dark-theme] [data-dsh-memoir-view] {\r\n background: var(--bg-panel, #16181d);\r\n color: var(--text-primary, #e6e6e6);\r\n}\r\n\r\n/* Memory Diagnostics (observability strip, v0.4). */\r\n.memoir-diagnostics { margin: 0 14px 14px; border-top: 1px solid var(--border, rgba(0,0,0,.1)); padding-top: 8px; }\r\n.memoir-diagnostics-toggle {\r\n border: none; background: transparent; color: inherit; cursor: pointer;\r\n font-size: 12px; opacity: .75; padding: 2px 0;\r\n}\r\n.memoir-diagnostics-toggle:hover { opacity: 1; }\r\n.memoir-diagnostics-body { font-size: 11px; opacity: .8; display: flex; flex-direction: column; gap: 3px; margin-top: 6px; }\r\n`\r\n", "/**\n * Sidebar entry injection (the dsh-ssh / task-board DOM-level precedent: the\n * shipped sidebar shell exposes no registration slot for these family\n * plugins). The row is plain DOM \u2014 React never manages it \u2014 and self-heals\n * through a MutationObserver when the shell re-renders. It is placed after\n * the family block ([data-dsh-taskboard-entry], [data-dsh-ssh-entry]) so the\n * three sibling entries keep a stable order.\n */\n\nimport type { PanelController } from './controller.js'\n\n/** Stable data attribute identifying the injected entry row. */\nexport const ENTRY_SELECTOR = '[data-dsh-memoir-entry]'\n\n/** Inline icon (matches the shell's 16px nav-icon look): an open-book glyph. */\nconst ICON = '<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M8 3.5c-1.6-1-3.6-1-5.2-.2-.5.3-.8.9-.8 1.4v8.6c0 .5.5.9 1 .7C4.6 13.4 6.4 13.5 8 14.5c1.6-1 3.4-1.1 5-.5.5.2 1-.2 1-.7V4.7c0-.5-.3-1.1-.8-1.4-1.6-.8-3.6-.8-5.2.2z\"/><path d=\"M8 3.5v11\"/></svg>'\n\nconst FAMILY_SELECTOR = '[data-dsh-taskboard-entry], [data-dsh-ssh-entry], [data-dsh-memoir-entry]'\n\n/** Find the sidebar shell root element, or undefined while not yet mounted. */\nfunction sidebarRoot(): HTMLElement | undefined {\n const column = document.querySelector<HTMLElement>('[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]')\n if (column === null) return undefined\n const logoOwner = column.querySelector<HTMLElement>('[class*=\"logoRow\"]')?.parentElement\n return logoOwner ?? (column.firstElementChild as HTMLElement | undefined)\n}\n\n/** The New Session button: nested in the logo row on current shells. */\nfunction newSessionButton(root: HTMLElement): HTMLButtonElement | undefined {\n const nested = root.querySelector<HTMLButtonElement>('button[class*=\"newSession\"]')\n if (nested !== null) return nested\n for (const child of root.children) {\n if (child.tagName === 'BUTTON') return child as HTMLButtonElement\n }\n return undefined\n}\n\n/** Build the entry row (a detached button; inserted once the shell is up). */\nfunction createEntry(controller: PanelController, t: (key: string) => string): HTMLButtonElement {\n const entry = document.createElement('button')\n entry.type = 'button'\n entry.dataset.dshMemoirEntry = ''\n entry.className = 'memoir-entry-row'\n entry.setAttribute('aria-label', t('entry.label'))\n entry.setAttribute('title', t('entry.tooltip'))\n entry.innerHTML = '<span class=\"memoir-entry-icon\">' + ICON + '</span><span>' + t('entry.label') + '</span>'\n entry.addEventListener('click', () => {\n controller.toggle()\n })\n return entry\n}\n\n/**\n * Re-insert the entry after the sibling family block (stable relative order).\n * @returns true when the entry is placed.\n */\nfunction placeEntry(root: HTMLElement, entry: HTMLButtonElement): boolean {\n const button = newSessionButton(root)\n if (button === undefined) return false\n if (entry.parentElement !== root) {\n const row = button.closest('[class*=\"logoRow\"]')\n const base = row !== null && row.parentElement === root ? row : button\n const family = Array.from(root.children).filter(\n (el): el is HTMLElement => el instanceof HTMLElement && el.matches(FAMILY_SELECTOR),\n )\n // memoir sits after the whole family block.\n const anchor = family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling\n root.insertBefore(entry, anchor)\n }\n return true\n}\n\n/**\n * Mount the sidebar entry, waiting for the shell and self-healing on re-renders.\n * @param controller - the panel controller the entry toggles.\n * @param t - the bound translator.\n * @returns disposer removing the entry and its observers.\n */\nexport function mountSidebarEntry(controller: PanelController, t: (key: string) => string): () => void {\n const entry = createEntry(controller, t)\n let root: HTMLElement | undefined\n let placed = false\n\n const tryPlace = (): void => {\n if (placed) return\n if (root !== undefined && !root.isConnected) {\n rootObserver.disconnect()\n root = undefined\n }\n root ??= sidebarRoot()\n if (root === undefined) return\n placed = placeEntry(root, entry)\n if (placed) {\n rootObserver.observe(root, { childList: true, subtree: true })\n waitObserver.disconnect()\n }\n }\n\n const waitObserver = new MutationObserver(() => {\n tryPlace()\n })\n waitObserver.observe(document.body, { childList: true, subtree: true })\n\n const rootObserver = new MutationObserver(() => {\n if (root === undefined || !root.isConnected) {\n placed = false\n tryPlace()\n return\n }\n if (!root.contains(entry)) {\n placed = placeEntry(root, entry)\n }\n })\n\n const unsubscribe = controller.subscribe(() => {\n entry.dataset.active = controller.getSnapshot().panelOpen ? 'true' : undefined\n })\n entry.dataset.active = controller.getSnapshot().panelOpen ? 'true' : undefined\n\n tryPlace()\n\n return () => {\n waitObserver.disconnect()\n rootObserver.disconnect()\n unsubscribe()\n entry.remove()\n }\n}\n", "/**\n * Panel view mounting (the dsh-ssh / task-board center-column precedent).\n * The panel takes over the center column at the DOM level: a container is\n * appended inside the [data-pane=\"conversation\"] grid item and a stylesheet\n * rule hides the conversation content while the panel is active. Toggling is\n * a data attribute on <html> \u2014 the conversation subtree stays mounted.\n *\n * Cross-plugin coordination: opening this panel evicts the sibling panels'\n * visibility attributes (data-dsh-ssh-active / data-dsh-taskboard-active)\n * and dispatches the shared `dsh-panel-activate` event; it closes itself when\n * a sibling dispatches its own activation.\n */\n\nimport { createRoot, type Root } from 'react-dom/client'\nimport { MemoirPanel } from './panel.jsx'\nimport type { MemoirApi } from './api.js'\nimport type { CwdTracker } from './cwd.js'\nimport type { PanelController } from './controller.js'\n\n/** The injected panel container (kept in the DOM, hidden when inactive). */\nexport const PANEL_VIEW_SELECTOR = '[data-dsh-memoir-view]'\n\nconst CONVERSATION_COLUMN_SELECTOR = '[data-pane=\"conversation\"]'\nconst ACTIVE_ATTR = 'data-dsh-memoir-active'\n/** Sibling panels' activation attributes, removed when this panel opens. */\nconst SIBLING_ATTRS = ['data-dsh-ssh-active', 'data-dsh-taskboard-active']\n/** Cross-plugin activation event; detail is the activating panel name. */\nconst ACTIVATE_EVENT = 'dsh-panel-activate'\nconst PANEL_NAME = 'memoir'\n\n/** Find the center column, or undefined while the frame is not mounted. */\nfunction conversationColumn(): HTMLElement | undefined {\n return document.querySelector<HTMLElement>(CONVERSATION_COLUMN_SELECTOR) ?? undefined\n}\n\n/**\n * Mount the panel React tree into the center column and bind its visibility\n * to the controller's panelOpen state.\n * @param controller - the panel controller driving the view.\n * @param api - the memoir API client the panel operates through.\n * @param cwdTracker - the active-workspace tracker (useSyncExternalStore-compatible).\n * @param t - the bound translator.\n * @returns disposer unmounting the tree and restoring the column.\n */\nexport function mountPanel(\n controller: PanelController,\n api: MemoirApi,\n cwdTracker: CwdTracker,\n t: (key: string) => string,\n): () => void {\n let root: Root | undefined\n let container: HTMLDivElement | undefined\n\n const ensure = (): void => {\n if (container !== undefined) {\n if (container.isConnected) return\n root?.unmount()\n root = undefined\n container.remove()\n container = undefined\n }\n const column = conversationColumn()\n if (column === undefined) return\n container = document.createElement('div')\n container.dataset.dshMemoirView = ''\n column.appendChild(container)\n root = createRoot(container)\n root.render(<MemoirPanel controller={controller} api={api} cwdTracker={cwdTracker} t={t} />)\n }\n\n // The frame mounts after boot settlement; watch for the column's arrival.\n const waitObserver = new MutationObserver(() => {\n ensure()\n })\n waitObserver.observe(document.body, { childList: true, subtree: true })\n\n const applyActive = (): void => {\n if (controller.getSnapshot().panelOpen) {\n for (const attr of SIBLING_ATTRS) document.documentElement.removeAttribute(attr)\n document.documentElement.setAttribute(ACTIVE_ATTR, '')\n document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }))\n } else {\n document.documentElement.removeAttribute(ACTIVE_ATTR)\n }\n }\n\n const onOtherActivate = (event: Event): void => {\n const detail = (event as CustomEvent).detail as string | undefined\n if ((detail === 'ssh' || detail === 'taskboard') && controller.getSnapshot().panelOpen) {\n controller.close()\n }\n }\n\n // Hand the center column back to the conversation on sidebar context clicks.\n const SIDEBAR_ROW_SELECTOR = '[class*=\"sessionRow\"], [class*=\"projectRow\"], [class*=\"searchResultRow\"], [class*=\"searchResultWorkspace\"], [class*=\"newSession\"]'\n const onClickSidebarRow = (event: MouseEvent): void => {\n if (!controller.getSnapshot().panelOpen) return\n const target = event.target as HTMLElement | null\n if (target === null || typeof target.closest !== 'function') return\n if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.close()\n }\n\n document.addEventListener('click', onClickSidebarRow, true)\n document.addEventListener(ACTIVATE_EVENT, onOtherActivate)\n const unsubscribe = controller.subscribe(applyActive)\n applyActive()\n ensure()\n\n return () => {\n document.removeEventListener('click', onClickSidebarRow, true)\n document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)\n waitObserver.disconnect()\n unsubscribe()\n document.documentElement.removeAttribute(ACTIVE_ATTR)\n root?.unmount()\n root = undefined\n container?.remove()\n container = undefined\n }\n}\n", "/**\r\n * The memoir panel: project / global memory tabs, client-side search,\r\n * manual record form, per-entry delete. Rendered into the center-column\r\n * container by mount.tsx; visibility is CSS-driven (html data attribute).\r\n */\r\n\r\nimport { Fragment, useEffect, useMemo, useState, useSyncExternalStore } from 'react'\r\nimport { SECTION_KEYS } from './i18n.js'\r\nimport type { MemoirApi, WireDiagnostics, WireEntry, WireHotMemory, WireProject, WireSearchResult } from './api.js'\r\nimport type { CwdTracker } from './cwd.js'\r\nimport type { PanelController } from './controller.js'\r\nimport type { SectionKey } from './types.ts'\r\n\r\ninterface PanelProps {\r\n controller: PanelController\r\n api: MemoirApi\r\n cwdTracker: CwdTracker\r\n t: (key: string) => string\r\n}\r\n\r\n/** One entry's meta line: time, section chip, session id tooltip. */\r\nfunction EntryMeta({ entry, t }: { entry: WireEntry; t: (key: string) => string }) {\r\n const when = new Date(entry.time)\r\n const pad = (n: number) => String(n).padStart(2, '0')\r\n const timeText = `${when.getFullYear()}-${pad(when.getMonth() + 1)}-${pad(when.getDate())} ${pad(when.getHours())}:${pad(when.getMinutes())}`\r\n return (\r\n <div className=\"memoir-entry-meta\">\r\n <span>{timeText}</span>\r\n <span className=\"memoir-chip\">{t('sections.' + entry.section)}</span>\r\n {entry.sessionId !== undefined\r\n ? <span title={`${t('session')}: ${entry.sessionId}`}>{entry.sessionId.slice(0, 12)}</span>\r\n : null}\r\n </div>\r\n )\r\n}\r\n\r\nfunction EntryCard({ entry, t, onDelete, score }: { entry: WireEntry; t: (key: string) => string; onDelete: (entry: WireEntry) => void; score?: number }) {\r\n return (\r\n <div className=\"memoir-entry\">\r\n <button type=\"button\" className=\"memoir-delete\" title={t('delete.confirm')} onClick={() => onDelete(entry)}>\u00D7</button>\r\n <EntryMeta entry={entry} t={t} />\r\n {score !== undefined\r\n ? <span className=\"memoir-score\" title={t('search.ranked')}>{score.toFixed(3)}</span>\r\n : null}\r\n {entry.title !== undefined ? <div className=\"memoir-entry-title\">{entry.title}</div> : null}\r\n <div className=\"memoir-entry-content\">{entry.content}</div>\r\n </div>\r\n )\r\n}\r\n\r\n/** Sectioned entry list in canonical order. */\r\nfunction SectionedEntries({ entries, t, onDelete }: { entries: WireEntry[]; t: (key: string) => string; onDelete: (entry: WireEntry) => void }) {\r\n const groups = useMemo(\r\n () => SECTION_KEYS.map((key) => ({ key, entries: entries.filter((e) => e.section === key) })).filter((g) => g.entries.length > 0),\r\n [entries],\r\n )\r\n if (groups.length === 0) {\r\n return (\r\n <div className=\"memoir-empty\">\r\n <div className=\"memoir-empty-title\">{t('empty.search')}</div>\r\n </div>\r\n )\r\n }\r\n return (\r\n <Fragment>\r\n {groups.map((group) => (\r\n <Fragment key={group.key}>\r\n <div className=\"memoir-section-title\">\r\n {t('sections.' + group.key)}\r\n <span className=\"memoir-count\">({group.entries.length})</span>\r\n </div>\r\n {group.entries.map((entry) => (\r\n <EntryCard key={entry.id} entry={entry} t={t} onDelete={onDelete} />\r\n ))}\r\n </Fragment>\r\n ))}\r\n </Fragment>\r\n )\r\n}\r\n\r\n/**\r\n * Ranked search results (v0.4.2): the host RetrievalEngine order, flat for\r\n * the project tab or grouped by project for the global tab. Scores are\r\n * shown as chips so the ranking is inspectable.\r\n */\r\nfunction RankedResults({ results, pending, grouped, t, onDelete }: {\r\n results: WireSearchResult[]\r\n pending: boolean\r\n grouped: boolean\r\n t: (key: string) => string\r\n onDelete: (entry: WireEntry, path: string) => void\r\n}) {\r\n if (pending) return <div className=\"memoir-empty\">\u2026</div>\r\n if (results.length === 0) {\r\n return (\r\n <div className=\"memoir-empty\">\r\n <div className=\"memoir-empty-title\">{t('empty.search')}</div>\r\n <div className=\"memoir-empty-hint\">{t('search.ranked')}</div>\r\n </div>\r\n )\r\n }\r\n if (!grouped) {\r\n return (\r\n <Fragment>\r\n <div className=\"memoir-ranked-note\">{t('search.ranked')}</div>\r\n {results.map((r) => (\r\n <EntryCard key={r.entry.id} entry={r.entry} score={r.score} t={t} onDelete={(entry) => onDelete(entry, r.projectPath)} />\r\n ))}\r\n </Fragment>\r\n )\r\n }\r\n const groups = new Map<string, WireSearchResult[]>()\r\n for (const result of results) {\r\n const bucket = groups.get(result.projectPath) ?? []\r\n bucket.push(result)\r\n groups.set(result.projectPath, bucket)\r\n }\r\n return (\r\n <Fragment>\r\n {[...groups.entries()].map(([path, bucket]) => (\r\n <div className=\"memoir-project-card\" key={path}>\r\n <div className=\"memoir-project-head\">\r\n <span className=\"memoir-project-title\">{path.split('/').filter(Boolean).pop() || path}</span>\r\n <span className=\"memoir-project-path\">{path}</span>\r\n </div>\r\n {bucket.map((r) => (\r\n <EntryCard key={r.entry.id} entry={r.entry} score={r.score} t={t} onDelete={(entry) => onDelete(entry, path)} />\r\n ))}\r\n </div>\r\n ))}\r\n </Fragment>\r\n )\r\n}\r\n\r\nfunction AddForm({ t, onSubmit, onCancel }: { t: (key: string) => string; onSubmit: (payload: { section: SectionKey; title?: string; content: string }) => void; onCancel: () => void }) {\r\n const [section, setSection] = useState<SectionKey>('lessons')\r\n const [title, setTitle] = useState('')\r\n const [content, setContent] = useState('')\r\n const submit = () => {\r\n if (content.trim() === '') return\r\n onSubmit({ section, title: title.trim() === '' ? undefined : title.trim(), content: content.trim() })\r\n }\r\n return (\r\n <div className=\"memoir-form\">\r\n <div className=\"memoir-form-row\">\r\n <div className=\"memoir-field\">\r\n <label>{t('form.section')}</label>\r\n <select value={section} onChange={(e) => setSection(e.target.value as SectionKey)}>\r\n {SECTION_KEYS.map((key) => (\r\n <option key={key} value={key}>{t('sections.' + key)}</option>\r\n ))}\r\n </select>\r\n </div>\r\n <div className=\"memoir-field\">\r\n <label>{t('form.title')}</label>\r\n <input value={title} placeholder={t('form.placeholder.title')} onChange={(e) => setTitle(e.target.value)} />\r\n </div>\r\n </div>\r\n <div className=\"memoir-field\">\r\n <label>{t('form.content')}</label>\r\n <textarea value={content} placeholder={t('form.placeholder.content')} onChange={(e) => setContent(e.target.value)} />\r\n </div>\r\n <div className=\"memoir-form-actions\">\r\n <button type=\"button\" className=\"memoir-iconbtn\" onClick={onCancel}>{t('toolbar.cancel')}</button>\r\n <button type=\"button\" className=\"memoir-primary\" onClick={submit}>{t('form.submit')}</button>\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\nexport function MemoirPanel({ controller, api, cwdTracker, t }: PanelProps) {\r\n const cwd = useSyncExternalStore(cwdTracker.subscribe, cwdTracker.getSnapshot)\r\n const [tab, setTab] = useState<'project' | 'global'>('project')\r\n const [query, setQuery] = useState('')\r\n const [formOpen, setFormOpen] = useState(false)\r\n const [refreshKey, setRefreshKey] = useState(0)\r\n const [project, setProject] = useState<WireProject | null>(null)\r\n const [projects, setProjects] = useState<WireProject[]>([])\r\n const [loading, setLoading] = useState(false)\r\n const [error, setError] = useState<string | null>(null)\r\n const [busy, setBusy] = useState(false)\r\n const [diag, setDiag] = useState<WireDiagnostics | null>(null)\r\n const [diagOpen, setDiagOpen] = useState(false)\r\n const [searchResults, setSearchResults] = useState<WireSearchResult[] | null>(null)\r\n const [hotMemory, setHotMemory] = useState<WireHotMemory | null>(null)\r\n const [inspectorOpen, setInspectorOpen] = useState(false)\r\n\r\n const q = query.trim().toLowerCase()\r\n\r\n useEffect(() => {\r\n let cancelled = false\r\n setError(null)\r\n if (tab === 'project') {\r\n if (cwd === '') {\r\n setProject(null)\r\n return undefined\r\n }\r\n setLoading(true)\r\n api.project(cwd)\r\n .then((value) => { if (!cancelled) setProject(value.project) })\r\n .catch((e: Error) => { if (!cancelled) setError(e.message) })\r\n .finally(() => { if (!cancelled) setLoading(false) })\r\n } else {\r\n setLoading(true)\r\n api.global()\r\n .then((value) => { if (!cancelled) setProjects(value.projects) })\r\n .catch((e: Error) => { if (!cancelled) setError(e.message) })\r\n .finally(() => { if (!cancelled) setLoading(false) })\r\n }\r\n // Diagnostics follow the active workspace (observability, roadmap \u00A74).\r\n api.diagnostics(cwd === '' ? undefined : cwd)\r\n .then((value) => { if (!cancelled) setDiag(value) })\r\n .catch(() => { if (!cancelled) setDiag(null) })\r\n // Hot-memory inspector preview (what the next session inherits).\r\n if (cwd === '') {\r\n setHotMemory(null)\r\n } else {\r\n api.hotMemory(cwd)\r\n .then((value) => { if (!cancelled) setHotMemory(value.hotMemory) })\r\n .catch(() => { if (!cancelled) setHotMemory(null) })\r\n }\r\n return () => { cancelled = true }\r\n }, [tab, cwd, refreshKey])\r\n\r\n // v0.4.2: a non-empty query searches through the host RetrievalEngine \u2014\r\n // the same BM25 ranking memoir_read uses. Debounced; empty query resets.\r\n useEffect(() => {\r\n if (q === '') {\r\n setSearchResults(null)\r\n return undefined\r\n }\r\n let cancelled = false\r\n const timer = setTimeout(() => {\r\n const scope = tab === 'project' ? 'project' : 'global'\r\n api.search({ scope, path: tab === 'project' && cwd !== '' ? cwd : undefined, query: q })\r\n .then((value) => { if (!cancelled) setSearchResults(value.results) })\r\n .catch((e: Error) => {\r\n if (!cancelled) {\r\n setSearchResults([])\r\n setError(e.message)\r\n }\r\n })\r\n }, 200)\r\n return () => { cancelled = true; clearTimeout(timer) }\r\n }, [q, tab, cwd, refreshKey])\r\n\r\n const filterEntries = (entries: WireEntry[]) =>\r\n q === '' ? entries : entries.filter((e) => `${e.title ?? ''} ${e.content}`.toLowerCase().includes(q))\r\n\r\n const reload = () => setRefreshKey((k) => k + 1)\r\n\r\n const onDelete = (entry: WireEntry, entryPath: string) => {\r\n if (!window.confirm(t('delete.confirm'))) return\r\n setBusy(true)\r\n api.remove({ path: entryPath, id: entry.id })\r\n .then(() => reload())\r\n .catch((e: Error) => setError(`${t('delete.failed')}: ${e.message}`))\r\n .finally(() => setBusy(false))\r\n }\r\n\r\n const onRecord = (payload: { section: SectionKey; title?: string; content: string }) => {\r\n setBusy(true)\r\n api.record({ path: cwd, ...payload })\r\n .then(() => {\r\n setFormOpen(false)\r\n reload()\r\n })\r\n .catch((e: Error) => setError(`${t('record.failed')}: ${e.message}`))\r\n .finally(() => setBusy(false))\r\n }\r\n\r\n const projectEntries = project === null ? [] : filterEntries(project.entries)\r\n\r\n return (\r\n <div className=\"memoir-panel\">\r\n <div className=\"memoir-header\">\r\n <div className=\"memoir-title\">\r\n {t('panel.title')}\r\n <div className=\"memoir-subtitle\">{tab === 'project' ? (cwd === '' ? t('empty.workspace') : cwd) : t('tab.global')}</div>\r\n </div>\r\n <button type=\"button\" className=\"memoir-iconbtn\" title={t('panel.refresh')} onClick={reload}>\u27F3</button>\r\n <button type=\"button\" className=\"memoir-iconbtn\" title={t('panel.close')} onClick={() => controller.close()}>\u00D7</button>\r\n </div>\r\n <div className=\"memoir-tabs\">\r\n <button type=\"button\" className=\"memoir-tab\" data-active={tab === 'project' ? 'true' : undefined} onClick={() => setTab('project')}>{t('tab.project')}</button>\r\n <button type=\"button\" className=\"memoir-tab\" data-active={tab === 'global' ? 'true' : undefined} onClick={() => setTab('global')}>{t('tab.global')}</button>\r\n </div>\r\n <div className=\"memoir-toolbar\">\r\n <input className=\"memoir-search\" placeholder={t('toolbar.search')} value={query} onChange={(e) => setQuery(e.target.value)} />\r\n {tab === 'project' && cwd !== ''\r\n ? <button type=\"button\" className=\"memoir-primary\" onClick={() => setFormOpen((v) => !v)}>{formOpen ? t('toolbar.cancel') : t('toolbar.add')}</button>\r\n : null}\r\n </div>\r\n {formOpen && tab === 'project' && cwd !== ''\r\n ? <AddForm t={t} onSubmit={onRecord} onCancel={() => setFormOpen(false)} />\r\n : null}\r\n {error !== null ? <div className=\"memoir-error\">{error}</div> : null}\r\n <div className=\"memoir-body\">\r\n {loading\r\n ? <div className=\"memoir-empty\">\u2026</div>\r\n : q !== ''\r\n ? (\r\n <RankedResults\r\n results={searchResults ?? []}\r\n pending={searchResults === null}\r\n grouped={tab === 'global'}\r\n t={t}\r\n onDelete={(entry, path) => onDelete(entry, path)}\r\n />\r\n )\r\n : tab === 'project'\r\n ? cwd === ''\r\n ? (\r\n <div className=\"memoir-empty\">\r\n <div className=\"memoir-empty-title\">{t('empty.workspace')}</div>\r\n <div className=\"memoir-empty-hint\">{t('empty.workspaceHint')}</div>\r\n </div>\r\n )\r\n : projectEntries.length === 0\r\n ? (\r\n <div className=\"memoir-empty\">\r\n <div className=\"memoir-empty-title\">{t('empty.project')}</div>\r\n <div className=\"memoir-empty-hint\">{t('empty.projectHint')}</div>\r\n </div>\r\n )\r\n : <SectionedEntries entries={projectEntries} t={t} onDelete={(entry) => onDelete(entry, project?.path ?? cwd)} />\r\n : projects.length === 0\r\n ? (\r\n <div className=\"memoir-empty\">\r\n <div className=\"memoir-empty-title\">{t('empty.global')}</div>\r\n <div className=\"memoir-empty-hint\">{t('empty.globalHint')}</div>\r\n </div>\r\n )\r\n : projects.map((p) => {\r\n const entries = filterEntries(p.entries)\r\n if (entries.length === 0) return null\r\n const when = new Date(p.updatedAt)\r\n return (\r\n <div className=\"memoir-project-card\" key={p.key}>\r\n <div className=\"memoir-project-head\">\r\n <span className=\"memoir-project-title\">{p.title}</span>\r\n <span className=\"memoir-project-path\">{p.path}</span>\r\n </div>\r\n <div className=\"memoir-project-meta\">\r\n {`${t('updated')} ${when.toISOString().slice(0, 16).replace('T', ' ')} \u00B7 ${entries.length} ${t('entries')}`}\r\n </div>\r\n <SectionedEntries entries={entries} t={t} onDelete={(entry) => onDelete(entry, p.path)} />\r\n </div>\r\n )\r\n })}\r\n </div>\r\n {busy ? <div className=\"memoir-empty\">\u2026</div> : null}\r\n <div className=\"memoir-inspector\">\r\n <button type=\"button\" className=\"memoir-diagnostics-toggle\" onClick={() => setInspectorOpen((v) => !v)}>\r\n {t('inspector.title')} {inspectorOpen ? '\u25BE' : '\u25B8'}\r\n </button>\r\n {inspectorOpen\r\n ? hotMemory === null || hotMemory.text === ''\r\n ? <div className=\"memoir-inspector-body\">{t('inspector.empty')}</div>\r\n : <pre className=\"memoir-inspector-body\">{hotMemory.text}</pre>\r\n : null}\r\n </div>\r\n <div className=\"memoir-diagnostics\">\r\n <button type=\"button\" className=\"memoir-diagnostics-toggle\" onClick={() => setDiagOpen((v) => !v)}>\r\n {t('diag.title')} {diagOpen ? '\u25BE' : '\u25B8'}\r\n </button>\r\n {diagOpen && diag !== null\r\n ? (\r\n <div className=\"memoir-diagnostics-body\">\r\n <div>{t('diag.revision')}: {diag.storeRevision} \u00B7 {t('diag.snapshot')}: {diag.snapshotCount}/{diag.snapshotMax}</div>\r\n <div>{t('diag.cache')}: {diag.cache.hits}/{diag.cache.loads} \u547D\u4E2D ({Math.round(diag.cache.hitRate * 100)}%) \u00B7 {t('diag.render')}: {Math.round(diag.cache.renderHitRate * 100)}%</div>\r\n {diag.hotMemory !== null\r\n ? <div>{t('diag.hot')}: {diag.hotMemory.selected}/{diag.hotMemory.total} \u6761 \u00B7 ~{diag.hotMemory.estimatedTokens} tokens\uFF08\u9884\u7B97 {diag.config.hotMemoryTokens}/{diag.config.hotMemoryMaxTokens}\uFF09</div>\r\n : null}\r\n <div>{t('diag.retrieval')}: {diag.retrieval.index === null ? '\u2014' : `${diag.retrieval.index.docs} docs \u00B7 ${diag.retrieval.index.terms} terms \u00B7 epoch ${diag.retrieval.index.epoch}`}</div>\r\n <div>{t('diag.qcache')}: {diag.retrieval.cache.hits} hits / {diag.retrieval.cache.misses} misses ({Math.round(diag.retrieval.cache.hitRate * 100)}%) \u00B7 {diag.retrieval.cache.size}/{diag.retrieval.cache.capacity} \u00B7 {diag.retrieval.cache.evictions} evicted</div>\r\n {diag.retrieval.lastQuery !== null\r\n ? <div>{t('diag.lastQuery')}: {diag.retrieval.lastQuery.latencyMs.toFixed(1)} ms \u00B7 {diag.retrieval.lastQuery.returned}/{diag.retrieval.lastQuery.candidates} returned</div>\r\n : null}\r\n {diag.snapshot !== null\r\n ? <div>{t('diag.snapshotInfo')}: {diag.snapshot.hash} \u00B7 rev {diag.snapshot.storeRevision}</div>\r\n : null}\r\n </div>\r\n )\r\n : null}\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["query", "import_jsx_runtime"]
|
|
7
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-memoir — host half.
|
|
3
|
+
*
|
|
4
|
+
* 把「一个会话做了什么 / 踩了什么坑 / 下一步怎么走」沉淀为项目持久化记忆,
|
|
5
|
+
* 并作为未来 AGENTS 的行动指南:
|
|
6
|
+
* - 项目级记忆:<工作区>/PROJECT_MEMORY.md(随 git 提交,会话开始时自动注入)
|
|
7
|
+
* - 全局索引:~/.dsh/dsh-memoir.json(结构化源数据,跨项目检索)
|
|
8
|
+
* - 面板 API:/api/dsh-memoir/*(浏览器「记忆」面板读写 + diagnostics)
|
|
9
|
+
* - 自动收尾:每轮有实际工作的 turn 结束时,steer 一句归纳提示
|
|
10
|
+
*
|
|
11
|
+
* v0.4.0 cache-aware injection:
|
|
12
|
+
* - system prompt 只注入 selector 选出的 Hot Memory(token 预算),
|
|
13
|
+
* 不再注入完整 markdown;
|
|
14
|
+
* - 每个 session 的注入文本由 MemorySnapshotManager 冻结一次,
|
|
15
|
+
* 同一 session 后续 assembly 复用同一 snapshot(prompt 前缀稳定,
|
|
16
|
+
* 最大化 prompt-prefix cache 命中);新 session 才重建。
|
|
17
|
+
*
|
|
18
|
+
* 提供的 agent 工具:
|
|
19
|
+
* - memoir_record(section, title?, content) 记录一条记忆
|
|
20
|
+
* - memoir_read(scope?, section?, query?, limit?, detail?) 读取记忆
|
|
21
|
+
*/
|
|
22
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
23
|
+
import { MemoirStore } from './store.js';
|
|
24
|
+
import { MemorySnapshotManager } from './snapshot.js';
|
|
25
|
+
import type { MemoryBudget } from './selector.js';
|
|
26
|
+
/** Stable cordis plugin name. */
|
|
27
|
+
export declare const name = "memoir";
|
|
28
|
+
/** Services required before the memory surfaces can mount. */
|
|
29
|
+
export declare const inject: string[];
|
|
30
|
+
/** Plugin config (validated softly at apply; defaults apply when absent). */
|
|
31
|
+
export interface Config {
|
|
32
|
+
/** Master switch for the plugin (tools, routes, prompt section). */
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
/** When true (default), a system-prompt section announces the plugin. */
|
|
35
|
+
announceToAgent?: boolean;
|
|
36
|
+
/** When true (default), turns with real work are auto-distilled at turn end. */
|
|
37
|
+
autoDistill?: boolean;
|
|
38
|
+
/** Hot-memory soft target tokens (default 900). */
|
|
39
|
+
hotMemoryTokens?: number;
|
|
40
|
+
/** Hot-memory hard ceiling tokens (default 1200). */
|
|
41
|
+
hotMemoryMaxTokens?: number;
|
|
42
|
+
/** memoir_read default result count (default 8). */
|
|
43
|
+
readDefaultLimit?: number;
|
|
44
|
+
/** memoir_read maximum result count (default 30). */
|
|
45
|
+
readMaxLimit?: number;
|
|
46
|
+
/** Per-session snapshot LRU cap (default 128). */
|
|
47
|
+
sessionSnapshotMax?: number;
|
|
48
|
+
/** memoir_read ranked-query LRU cache size (default 128). */
|
|
49
|
+
queryCacheSize?: number;
|
|
50
|
+
}
|
|
51
|
+
/** Model-facing announcement: minimal by design (roadmap §2.6) — parameter
|
|
52
|
+
* details live in the tool schemas, not in every prompt. */
|
|
53
|
+
export declare const MEMOIR_GUIDANCE: string;
|
|
54
|
+
/** The injected-memory heading (kept stable across versions). */
|
|
55
|
+
export declare const MEMOIR_SECTION_HEADING = "## \u9879\u76EE\u6301\u4E45\u8BB0\u5FC6\uFF08\u81EA\u52A8\u6CE8\u5165\uFF09";
|
|
56
|
+
/** Resolved runtime switches (schema defaults applied). */
|
|
57
|
+
export interface ResolvedConfig {
|
|
58
|
+
enabled: boolean;
|
|
59
|
+
announceToAgent: boolean;
|
|
60
|
+
autoDistill: boolean;
|
|
61
|
+
budget: MemoryBudget;
|
|
62
|
+
readDefaultLimit: number;
|
|
63
|
+
readMaxLimit: number;
|
|
64
|
+
sessionSnapshotMax: number;
|
|
65
|
+
queryCacheSize: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Per-assembly prompt section text: minimal guidance plus the session's
|
|
69
|
+
* frozen hot-memory snapshot. Pure — exported for tests.
|
|
70
|
+
*
|
|
71
|
+
* Freezing rules (roadmap §1.2 A / §2.2): the FIRST assembly of a session
|
|
72
|
+
* builds the hot memory under the token budget and the manager freezes it;
|
|
73
|
+
* later assemblies — even after memoir_record changed the store — return the
|
|
74
|
+
* same text, keeping the prompt prefix stable. New sessions rebuild.
|
|
75
|
+
*
|
|
76
|
+
* @param store - the structured memory store.
|
|
77
|
+
* @param context - the system-prompt assemble context (may carry `agent`).
|
|
78
|
+
* @param manager - optional snapshot manager (no freezing when absent).
|
|
79
|
+
* @param budget - token budget (defaults to DEFAULT_MEMORY_BUDGET).
|
|
80
|
+
*/
|
|
81
|
+
export declare function memoirSectionText(store: MemoirStore, context: {
|
|
82
|
+
agent?: {
|
|
83
|
+
id?: string;
|
|
84
|
+
session?: {
|
|
85
|
+
id?: string;
|
|
86
|
+
header?: {
|
|
87
|
+
cwd?: string;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
}, manager?: MemorySnapshotManager, budget?: MemoryBudget): string;
|
|
92
|
+
/**
|
|
93
|
+
* Mount the memory tools, the panel routes, the auto-distill listener, and
|
|
94
|
+
* the per-project announcement.
|
|
95
|
+
* @param ctx - host plugin context carrying tools/systemPrompt/webServer.
|
|
96
|
+
* @param config - resolved plugin config (defaults apply when absent).
|
|
97
|
+
*/
|
|
98
|
+
export declare function apply(ctx: Context, config?: Config): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-memoir — host half.
|
|
3
|
+
*
|
|
4
|
+
* 把「一个会话做了什么 / 踩了什么坑 / 下一步怎么走」沉淀为项目持久化记忆,
|
|
5
|
+
* 并作为未来 AGENTS 的行动指南:
|
|
6
|
+
* - 项目级记忆:<工作区>/PROJECT_MEMORY.md(随 git 提交,会话开始时自动注入)
|
|
7
|
+
* - 全局索引:~/.dsh/dsh-memoir.json(结构化源数据,跨项目检索)
|
|
8
|
+
* - 面板 API:/api/dsh-memoir/*(浏览器「记忆」面板读写 + diagnostics)
|
|
9
|
+
* - 自动收尾:每轮有实际工作的 turn 结束时,steer 一句归纳提示
|
|
10
|
+
*
|
|
11
|
+
* v0.4.0 cache-aware injection:
|
|
12
|
+
* - system prompt 只注入 selector 选出的 Hot Memory(token 预算),
|
|
13
|
+
* 不再注入完整 markdown;
|
|
14
|
+
* - 每个 session 的注入文本由 MemorySnapshotManager 冻结一次,
|
|
15
|
+
* 同一 session 后续 assembly 复用同一 snapshot(prompt 前缀稳定,
|
|
16
|
+
* 最大化 prompt-prefix cache 命中);新 session 才重建。
|
|
17
|
+
*
|
|
18
|
+
* 提供的 agent 工具:
|
|
19
|
+
* - memoir_record(section, title?, content) 记录一条记忆
|
|
20
|
+
* - memoir_read(scope?, section?, query?, limit?, detail?) 读取记忆
|
|
21
|
+
*/
|
|
22
|
+
import { MemoirStore, projectKey } from './store.js';
|
|
23
|
+
import { memoirReadTool, memoirRecordTool } from './tools.js';
|
|
24
|
+
import { makeRoutes } from './routes.js';
|
|
25
|
+
import { installAutoDistill } from './autodistill.js';
|
|
26
|
+
import { MemorySnapshotManager, sessionKeyOf } from './snapshot.js';
|
|
27
|
+
import { DEFAULT_MEMORY_BUDGET, selectHotMemory } from './selector.js';
|
|
28
|
+
import { RetrievalEngine } from './retrieval.js';
|
|
29
|
+
/** Stable cordis plugin name. */
|
|
30
|
+
export const name = 'memoir';
|
|
31
|
+
/** Services required before the memory surfaces can mount. */
|
|
32
|
+
export const inject = ['tools', 'systemPrompt', 'webServer'];
|
|
33
|
+
/** Order of the announcement section within the tool-guidance band. */
|
|
34
|
+
const SECTION_ORDER = 150;
|
|
35
|
+
const DEFAULT_ENABLED = true;
|
|
36
|
+
const DEFAULT_ANNOUNCE = true;
|
|
37
|
+
const DEFAULT_AUTO_DISTILL = true;
|
|
38
|
+
/** Model-facing announcement: minimal by design (roadmap §2.6) — parameter
|
|
39
|
+
* details live in the tool schemas, not in every prompt. */
|
|
40
|
+
export const MEMOIR_GUIDANCE = 'dsh-memoir 提供项目持久记忆。下方仅注入本项目高优先级记忆;' +
|
|
41
|
+
'需要历史细节时调用 memoir_read;产生可复用的工作结论、经验或后续行动时调用 memoir_record。';
|
|
42
|
+
/** The injected-memory heading (kept stable across versions). */
|
|
43
|
+
export const MEMOIR_SECTION_HEADING = '## 项目持久记忆(自动注入)';
|
|
44
|
+
function resolveConfig(config) {
|
|
45
|
+
const target = Math.max(1, Math.floor(config?.hotMemoryTokens ?? DEFAULT_MEMORY_BUDGET.targetTokens));
|
|
46
|
+
const hardMax = Math.max(target, Math.floor(config?.hotMemoryMaxTokens ?? DEFAULT_MEMORY_BUDGET.hardMaxTokens));
|
|
47
|
+
const readDefaultLimit = Math.max(1, Math.floor(config?.readDefaultLimit ?? 8));
|
|
48
|
+
const readMaxLimit = Math.max(readDefaultLimit, Math.floor(config?.readMaxLimit ?? 30));
|
|
49
|
+
return {
|
|
50
|
+
enabled: config?.enabled ?? DEFAULT_ENABLED,
|
|
51
|
+
announceToAgent: config?.announceToAgent ?? DEFAULT_ANNOUNCE,
|
|
52
|
+
autoDistill: config?.autoDistill ?? DEFAULT_AUTO_DISTILL,
|
|
53
|
+
budget: { targetTokens: target, hardMaxTokens: hardMax },
|
|
54
|
+
readDefaultLimit,
|
|
55
|
+
readMaxLimit,
|
|
56
|
+
sessionSnapshotMax: Math.max(1, Math.floor(config?.sessionSnapshotMax ?? 128)),
|
|
57
|
+
queryCacheSize: Math.max(1, Math.floor(config?.queryCacheSize ?? 128)),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Per-assembly prompt section text: minimal guidance plus the session's
|
|
62
|
+
* frozen hot-memory snapshot. Pure — exported for tests.
|
|
63
|
+
*
|
|
64
|
+
* Freezing rules (roadmap §1.2 A / §2.2): the FIRST assembly of a session
|
|
65
|
+
* builds the hot memory under the token budget and the manager freezes it;
|
|
66
|
+
* later assemblies — even after memoir_record changed the store — return the
|
|
67
|
+
* same text, keeping the prompt prefix stable. New sessions rebuild.
|
|
68
|
+
*
|
|
69
|
+
* @param store - the structured memory store.
|
|
70
|
+
* @param context - the system-prompt assemble context (may carry `agent`).
|
|
71
|
+
* @param manager - optional snapshot manager (no freezing when absent).
|
|
72
|
+
* @param budget - token budget (defaults to DEFAULT_MEMORY_BUDGET).
|
|
73
|
+
*/
|
|
74
|
+
export function memoirSectionText(store, context, manager, budget = DEFAULT_MEMORY_BUDGET) {
|
|
75
|
+
const cwd = context?.agent?.session?.header?.cwd;
|
|
76
|
+
if (typeof cwd !== 'string' || cwd === '')
|
|
77
|
+
return MEMOIR_GUIDANCE;
|
|
78
|
+
const build = () => {
|
|
79
|
+
const entries = store.entries(cwd);
|
|
80
|
+
const hot = selectHotMemory(entries, budget);
|
|
81
|
+
if (hot.selected.length === 0) {
|
|
82
|
+
return { storeRevision: store.currentRevision(), text: MEMOIR_GUIDANCE };
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
storeRevision: store.currentRevision(),
|
|
86
|
+
text: MEMOIR_GUIDANCE + '\n\n' + MEMOIR_SECTION_HEADING + '\n' + hot.text,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const key = sessionKeyOf(context);
|
|
90
|
+
if (key === undefined || manager === undefined)
|
|
91
|
+
return build().text;
|
|
92
|
+
return manager.getOrCreate(key, build).text;
|
|
93
|
+
}
|
|
94
|
+
/** Bridge the cordis context onto the autodistill wire contract. */
|
|
95
|
+
function autoDistillWire(ctx) {
|
|
96
|
+
return {
|
|
97
|
+
on: (name, listener) => ctx.on(name, listener),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Mount the memory tools, the panel routes, the auto-distill listener, and
|
|
102
|
+
* the per-project announcement.
|
|
103
|
+
* @param ctx - host plugin context carrying tools/systemPrompt/webServer.
|
|
104
|
+
* @param config - resolved plugin config (defaults apply when absent).
|
|
105
|
+
*/
|
|
106
|
+
export function apply(ctx, config) {
|
|
107
|
+
const value = resolveConfig(config);
|
|
108
|
+
if (!value.enabled)
|
|
109
|
+
return;
|
|
110
|
+
const store = new MemoirStore();
|
|
111
|
+
const snapshotManager = new MemorySnapshotManager({ max: value.sessionSnapshotMax });
|
|
112
|
+
const retrieval = new RetrievalEngine(store, { cacheSize: value.queryCacheSize });
|
|
113
|
+
// Workspaces seen through system-prompt assemblies / panel requests: the
|
|
114
|
+
// write-authorization guard allows only these (plus existing store
|
|
115
|
+
// projects) — a browser-supplied absolute path is not authorization.
|
|
116
|
+
const recentWorkspaces = new Set();
|
|
117
|
+
const touchWorkspace = (path) => {
|
|
118
|
+
if (typeof path === 'string' && path !== '')
|
|
119
|
+
recentWorkspaces.add(projectKey(path));
|
|
120
|
+
};
|
|
121
|
+
ctx.effect(() => {
|
|
122
|
+
const tools = [
|
|
123
|
+
memoirRecordTool(store),
|
|
124
|
+
memoirReadTool(store, { defaultLimit: value.readDefaultLimit, maxLimit: value.readMaxLimit }, retrieval),
|
|
125
|
+
];
|
|
126
|
+
const disposers = tools.map((tool) => ctx.tools.register(tool));
|
|
127
|
+
return () => {
|
|
128
|
+
for (const dispose of disposers)
|
|
129
|
+
dispose();
|
|
130
|
+
};
|
|
131
|
+
}, 'dsh-memoir: tools');
|
|
132
|
+
ctx.effect(() => {
|
|
133
|
+
const diagnostics = (path) => {
|
|
134
|
+
const entries = typeof path === 'string' && path !== '' ? store.entries(path) : [];
|
|
135
|
+
const hot = entries.length === 0 ? null : selectHotMemory(entries, value.budget);
|
|
136
|
+
const stats = store.stats();
|
|
137
|
+
const latest = snapshotManager.latest();
|
|
138
|
+
return {
|
|
139
|
+
storeRevision: stats.revision,
|
|
140
|
+
snapshotEpoch: stats.epoch,
|
|
141
|
+
cache: stats,
|
|
142
|
+
snapshotCount: snapshotManager.size,
|
|
143
|
+
snapshotMax: snapshotManager.cap,
|
|
144
|
+
hotMemory: hot === null ? null : {
|
|
145
|
+
selected: hot.selected.length,
|
|
146
|
+
total: hot.total,
|
|
147
|
+
estimatedTokens: hot.estimatedTokens,
|
|
148
|
+
},
|
|
149
|
+
retrieval: retrieval.diagnostics(),
|
|
150
|
+
snapshot: latest === undefined ? null : {
|
|
151
|
+
hash: latest.hash,
|
|
152
|
+
createdAt: latest.createdAt,
|
|
153
|
+
storeRevision: latest.storeRevision,
|
|
154
|
+
},
|
|
155
|
+
config: {
|
|
156
|
+
hotMemoryTokens: value.budget.targetTokens,
|
|
157
|
+
hotMemoryMaxTokens: value.budget.hardMaxTokens,
|
|
158
|
+
readDefaultLimit: value.readDefaultLimit,
|
|
159
|
+
readMaxLimit: value.readMaxLimit,
|
|
160
|
+
sessionSnapshotMax: value.sessionSnapshotMax,
|
|
161
|
+
queryCacheSize: value.queryCacheSize,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
const hotMemoryPreview = (path) => {
|
|
166
|
+
if (typeof path !== 'string' || path === '')
|
|
167
|
+
return null;
|
|
168
|
+
const entries = store.entries(path);
|
|
169
|
+
if (entries.length === 0)
|
|
170
|
+
return null;
|
|
171
|
+
const hot = selectHotMemory(entries, value.budget);
|
|
172
|
+
return {
|
|
173
|
+
text: hot.text,
|
|
174
|
+
selected: hot.selected,
|
|
175
|
+
total: hot.total,
|
|
176
|
+
estimatedTokens: hot.estimatedTokens,
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
const allowedWorkspace = (path) => recentWorkspaces.has(projectKey(path)) || store.project(path) !== undefined;
|
|
180
|
+
const disposers = makeRoutes(store, diagnostics, retrieval, hotMemoryPreview, allowedWorkspace, touchWorkspace).map((route) => ctx.webServer.register(route));
|
|
181
|
+
return () => {
|
|
182
|
+
for (const dispose of disposers)
|
|
183
|
+
dispose();
|
|
184
|
+
};
|
|
185
|
+
}, 'dsh-memoir: routes');
|
|
186
|
+
if (value.autoDistill) {
|
|
187
|
+
ctx.effect(() => installAutoDistill(autoDistillWire(ctx), { enabled: () => value.autoDistill }), 'dsh-memoir: auto-distill');
|
|
188
|
+
}
|
|
189
|
+
if (value.announceToAgent) {
|
|
190
|
+
ctx.systemPrompt.section({
|
|
191
|
+
name: 'plugin:dsh-memoir',
|
|
192
|
+
order: SECTION_ORDER,
|
|
193
|
+
// text is a provider evaluated per assembly, so each project's memory is
|
|
194
|
+
// injected for its own session and not for unrelated workspaces. The
|
|
195
|
+
// assembly also registers the workspace for panel write authorization.
|
|
196
|
+
text: (context) => {
|
|
197
|
+
const cwd = context?.agent?.session?.header?.cwd;
|
|
198
|
+
if (typeof cwd === 'string' && cwd !== '')
|
|
199
|
+
touchWorkspace(cwd);
|
|
200
|
+
return memoirSectionText(store, context, snapshotManager, value.budget);
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|