@flowingspring/dsh-workspace-memory 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -13,7 +13,7 @@ window.__ModuleLoader__.load({
13
13
  refresh: "刷新",
14
14
  global: "全局记忆",
15
15
  workspace: "项目记忆",
16
- current: "当前项目",
16
+ scope: "选择记忆范围",
17
17
  emptyScopes: "还没有已记录的项目记忆",
18
18
  summary: "摘要记忆",
19
19
  entries: "结构化记忆",
@@ -24,6 +24,11 @@ window.__ModuleLoader__.load({
24
24
  loading: "正在读取…",
25
25
  failed: "读取记忆失败",
26
26
  retry: "重试",
27
+ activeView: "记忆",
28
+ archiveView: "回收区",
29
+ archiveEmpty: "回收区为空",
30
+ purge: "永久删除",
31
+ purgeConfirm: "确定永久删除这个项目的全部记忆吗?此操作无法撤销。",
27
32
  pending: "待整理",
28
33
  checkpoints: "整理次数",
29
34
  updated: "最近更新",
@@ -36,7 +41,7 @@ window.__ModuleLoader__.load({
36
41
  refresh: "Refresh",
37
42
  global: "Global memory",
38
43
  workspace: "Project memory",
39
- current: "Current project",
44
+ scope: "Memory scope",
40
45
  emptyScopes: "No project memories recorded yet",
41
46
  summary: "Summary memory",
42
47
  entries: "Structured memory",
@@ -47,21 +52,20 @@ window.__ModuleLoader__.load({
47
52
  loading: "Loading…",
48
53
  failed: "Unable to read memory",
49
54
  retry: "Retry",
55
+ activeView: "Memory",
56
+ archiveView: "Recycle bin",
57
+ archiveEmpty: "Recycle bin is empty",
58
+ purge: "Delete permanently",
59
+ purgeConfirm: "Permanently delete all memory for this project? This cannot be undone.",
50
60
  pending: "Pending",
51
61
  checkpoints: "Checkpoints",
52
62
  updated: "Last updated",
53
63
  all: "All",
54
64
  count: ""
55
65
  };
56
- function useSnapshot(source) {
57
- return (0, react.useSyncExternalStore)(source.subscribe, source.getSnapshot, source.getSnapshot);
58
- }
59
66
  function workspaceName(cwd, fallback) {
60
67
  return cwd.replaceAll("\\", "/").replace(/\/+$/u, "").split("/").filter(Boolean).at(-1) ?? fallback;
61
68
  }
62
- function normalizeCwd(cwd) {
63
- return cwd.replaceAll("\\", "/").replace(/\/+$/u, "").toLocaleLowerCase();
64
- }
65
69
  function formatDate(value) {
66
70
  if (value === void 0 || value === "") return "";
67
71
  const date = new Date(value);
@@ -88,12 +92,14 @@ window.__ModuleLoader__.load({
88
92
  strokeLinejoin: "round"
89
93
  }));
90
94
  }
91
- function MemorySection({ t, sessions }) {
92
- const sessionList = useSnapshot(sessions.list);
93
- const currentCwd = sessionList.current === void 0 ? "" : sessionList.byId[sessionList.current]?.cwd ?? "";
95
+ function MemorySection({ t }) {
94
96
  const [scopes, setScopes] = (0, react.useState)([]);
95
97
  const [selectedKey, setSelectedKey] = (0, react.useState)("global");
96
98
  const [payload, setPayload] = (0, react.useState)();
99
+ const [archivedScopes, setArchivedScopes] = (0, react.useState)([]);
100
+ const [archivedSelectedKey, setArchivedSelectedKey] = (0, react.useState)("");
101
+ const [archivedPayload, setArchivedPayload] = (0, react.useState)();
102
+ const [view, setView] = (0, react.useState)("active");
97
103
  const [query, setQuery] = (0, react.useState)("");
98
104
  const [loading, setLoading] = (0, react.useState)(true);
99
105
  const [error, setError] = (0, react.useState)(false);
@@ -108,13 +114,24 @@ window.__ModuleLoader__.load({
108
114
  if (!response.ok) throw new Error(`scope read failed (${response.status})`);
109
115
  return await response.json();
110
116
  };
117
+ const loadArchivedScopes = async () => {
118
+ const response = await fetch("/workspace-memory/api/v1/archived-scopes");
119
+ if (!response.ok) throw new Error(`archived scope list failed (${response.status})`);
120
+ const result = await response.json();
121
+ return Array.isArray(result.scopes) ? result.scopes : [];
122
+ };
123
+ const loadArchivedScope = async (key) => {
124
+ const response = await fetch(`/workspace-memory/api/v1/archived-scope?key=${encodeURIComponent(key)}`);
125
+ if (!response.ok) throw new Error(`archived scope read failed (${response.status})`);
126
+ return await response.json();
127
+ };
111
128
  const reload = async (cwd) => {
112
129
  setLoading(true);
113
130
  setError(false);
114
131
  try {
115
132
  const nextScopes = await loadScopes();
116
133
  setScopes(nextScopes);
117
- const next = await loadScope(cwd ?? payload?.scope.cwd ?? currentCwd);
134
+ const next = await loadScope(cwd ?? payload?.scope.cwd ?? "");
118
135
  setPayload(next);
119
136
  setSelectedKey(next.scope.key);
120
137
  } catch {
@@ -123,52 +140,76 @@ window.__ModuleLoader__.load({
123
140
  setLoading(false);
124
141
  }
125
142
  };
143
+ const reloadArchived = async (key) => {
144
+ setLoading(true);
145
+ setError(false);
146
+ try {
147
+ const nextScopes = await loadArchivedScopes();
148
+ setArchivedScopes(nextScopes);
149
+ const nextKey = key ?? (archivedSelectedKey !== "" ? archivedSelectedKey : nextScopes[0]?.key) ?? "";
150
+ if (nextKey === "") {
151
+ setArchivedSelectedKey("");
152
+ setArchivedPayload(void 0);
153
+ } else {
154
+ const next = await loadArchivedScope(nextKey);
155
+ setArchivedSelectedKey(next.scope.key);
156
+ setArchivedPayload(next);
157
+ }
158
+ } catch {
159
+ setError(true);
160
+ } finally {
161
+ setLoading(false);
162
+ }
163
+ };
126
164
  (0, react.useEffect)(() => {
127
- reload(currentCwd);
165
+ reload();
128
166
  }, []);
129
167
  const options = (0, react.useMemo)(() => {
130
- const known = new Map(scopes.map((scope) => [scope.key, scope]));
131
- if (payload !== void 0 && !known.has(payload.scope.key)) known.set(payload.scope.key, {
132
- key: payload.scope.key,
133
- cwd: payload.scope.cwd,
134
- kind: payload.scope.kind,
135
- entryCount: payload.entries.length,
136
- hasSummary: payload.summary.trim() !== "",
137
- pending: payload.state.pending,
138
- checkpointCount: payload.state.checkpointCount
139
- });
140
- const currentKey = `current:${currentCwd.toLocaleLowerCase()}`;
141
- if (currentCwd !== "" && !known.has(currentKey) && ![...known.values()].some((scope) => normalizeCwd(scope.cwd) === normalizeCwd(currentCwd))) known.set(currentKey, {
142
- key: currentKey,
143
- cwd: currentCwd,
144
- kind: "workspace",
145
- entryCount: 0,
146
- hasSummary: false,
147
- pending: 0,
148
- checkpointCount: 0
149
- });
150
- return [...known.values()];
151
- }, [
152
- scopes,
153
- currentCwd,
154
- payload
155
- ]);
168
+ return [...new Map(scopes.map((scope) => [scope.key, scope])).values()];
169
+ }, [scopes]);
170
+ const archivedOptions = (0, react.useMemo)(() => archivedScopes, [archivedScopes]);
171
+ const displayedPayload = view === "active" ? payload : archivedPayload;
172
+ const displayedOptions = view === "active" ? options : archivedOptions;
173
+ const displayedKey = view === "active" ? selectedKey : archivedSelectedKey;
156
174
  const filteredEntries = (0, react.useMemo)(() => {
157
175
  const normalized = query.trim().toLocaleLowerCase();
158
- if (normalized === "") return payload?.entries ?? [];
159
- return (payload?.entries ?? []).filter((entry) => [
176
+ if (normalized === "") return displayedPayload?.entries ?? [];
177
+ return (displayedPayload?.entries ?? []).filter((entry) => [
160
178
  entry.title,
161
179
  entry.description,
162
180
  entry.content,
163
181
  ...entry.tags
164
182
  ].join(" ").toLocaleLowerCase().includes(normalized));
165
- }, [payload, query]);
183
+ }, [displayedPayload, query]);
166
184
  const onSelect = (key) => {
167
185
  const option = options.find((scope) => scope.key === key);
168
186
  if (option === void 0) return;
169
187
  setSelectedKey(key);
170
188
  reload(option.cwd);
171
189
  };
190
+ const onSelectArchived = (key) => {
191
+ if (!archivedOptions.some((scope) => scope.key === key)) return;
192
+ setArchivedSelectedKey(key);
193
+ reloadArchived(key);
194
+ };
195
+ const showArchived = () => {
196
+ setView("archived");
197
+ if (archivedPayload === void 0) reloadArchived();
198
+ };
199
+ const purge = async () => {
200
+ const confirm = globalThis.confirm;
201
+ if (archivedSelectedKey === "" || confirm !== void 0 && !confirm(t("purgeConfirm"))) return;
202
+ setLoading(true);
203
+ setError(false);
204
+ try {
205
+ const response = await fetch(`/workspace-memory/api/v1/archived-scope?key=${encodeURIComponent(archivedSelectedKey)}`, { method: "DELETE" });
206
+ if (!response.ok) throw new Error(`archived scope delete failed (${response.status})`);
207
+ await reloadArchived();
208
+ } catch {
209
+ setError(true);
210
+ setLoading(false);
211
+ }
212
+ };
172
213
  const colors = {
173
214
  text: "var(--ds-color-text-primary, #f1f1f1)",
174
215
  secondary: "var(--ds-color-text-secondary, #a7a7ad)",
@@ -195,12 +236,12 @@ window.__ModuleLoader__.load({
195
236
  color: colors.secondary,
196
237
  fontSize: 12,
197
238
  wordBreak: "break-all"
198
- } }, payload?.scope.cwd || t("global"))), (0, react.createElement)("button", {
239
+ } }, displayedPayload?.scope.cwd || t("global"))), (0, react.createElement)("button", {
199
240
  type: "button",
200
241
  title: t("refresh"),
201
242
  "aria-label": t("refresh"),
202
243
  onClick: () => {
203
- reload();
244
+ view === "active" ? reload() : reloadArchived();
204
245
  },
205
246
  style: {
206
247
  display: "inline-flex",
@@ -215,16 +256,46 @@ window.__ModuleLoader__.load({
215
256
  cursor: "pointer"
216
257
  }
217
258
  }, iconRefresh())), (0, react.createElement)("div", { style: {
259
+ display: "flex",
260
+ gap: 6,
261
+ marginBottom: 16
262
+ } }, (0, react.createElement)("button", {
263
+ type: "button",
264
+ onClick: () => setView("active"),
265
+ "aria-pressed": view === "active",
266
+ style: {
267
+ padding: "7px 11px",
268
+ border: `1px solid ${colors.border}`,
269
+ borderRadius: 6,
270
+ background: view === "active" ? colors.surface : "transparent",
271
+ color: colors.text,
272
+ cursor: "pointer"
273
+ }
274
+ }, t("activeView")), (0, react.createElement)("button", {
275
+ type: "button",
276
+ onClick: showArchived,
277
+ "aria-pressed": view === "archived",
278
+ style: {
279
+ padding: "7px 11px",
280
+ border: `1px solid ${colors.border}`,
281
+ borderRadius: 6,
282
+ background: view === "archived" ? colors.surface : "transparent",
283
+ color: colors.text,
284
+ cursor: "pointer"
285
+ }
286
+ }, t("archiveView"))), (0, react.createElement)("div", { style: {
218
287
  display: "grid",
219
288
  gap: 8,
220
289
  marginBottom: 18
221
290
  } }, (0, react.createElement)("label", { style: {
222
291
  color: colors.secondary,
223
292
  fontSize: 12
224
- } }, t("current")), (0, react.createElement)("select", {
225
- value: selectedKey,
293
+ } }, t("scope")), (0, react.createElement)("select", {
294
+ value: displayedKey,
226
295
  onChange: (event) => {
227
- onSelect(event.target.value);
296
+ const key = event.target.value;
297
+ if (view === "active") onSelect(key);
298
+ else onSelectArchived(key);
228
299
  },
229
300
  style: {
230
301
  minHeight: 38,
@@ -234,7 +305,7 @@ window.__ModuleLoader__.load({
234
305
  border: `1px solid ${colors.border}`,
235
306
  borderRadius: 6
236
307
  }
237
- }, options.length === 0 ? (0, react.createElement)("option", { value: "" }, t("emptyScopes")) : options.map((scope) => (0, react.createElement)("option", {
308
+ }, displayedOptions.length === 0 ? (0, react.createElement)("option", { value: "" }, view === "active" ? t("emptyScopes") : t("archiveEmpty")) : displayedOptions.map((scope) => (0, react.createElement)("option", {
238
309
  key: scope.key,
239
310
  value: scope.key
240
311
  }, scope.kind === "global" ? t("global") : workspaceName(scope.cwd, t("workspace")))))), error && (0, react.createElement)("div", {
@@ -252,7 +323,7 @@ window.__ModuleLoader__.load({
252
323
  }, (0, react.createElement)("span", null, t("failed")), (0, react.createElement)("button", {
253
324
  type: "button",
254
325
  onClick: () => {
255
- reload();
326
+ view === "active" ? reload() : reloadArchived();
256
327
  },
257
328
  style: {
258
329
  color: colors.accent,
@@ -269,7 +340,21 @@ window.__ModuleLoader__.load({
269
340
  color: colors.secondary,
270
341
  fontSize: 12,
271
342
  flexWrap: "wrap"
272
- } }, (0, react.createElement)("span", null, `${payload?.entries.length ?? 0}${t("count")}`), (0, react.createElement)("span", null, `${t("pending")}: ${payload?.state.pending ?? 0}`), (0, react.createElement)("span", null, `${t("checkpoints")}: ${payload?.state.checkpointCount ?? 0}`)), (0, react.createElement)("article", { style: {
343
+ } }, (0, react.createElement)("span", null, `${displayedPayload?.entries.length ?? 0}${t("count")}`), (0, react.createElement)("span", null, `${t("pending")}: ${displayedPayload?.state.pending ?? 0}`), (0, react.createElement)("span", null, `${t("checkpoints")}: ${displayedPayload?.state.checkpointCount ?? 0}`)), view === "archived" && displayedPayload !== void 0 && (0, react.createElement)("button", {
344
+ type: "button",
345
+ onClick: () => {
346
+ purge();
347
+ },
348
+ style: {
349
+ justifySelf: "start",
350
+ color: "#ff8f8f",
351
+ background: "transparent",
352
+ border: `1px solid ${colors.border}`,
353
+ borderRadius: 6,
354
+ padding: "7px 11px",
355
+ cursor: "pointer"
356
+ }
357
+ }, t("purge")), (0, react.createElement)("article", { style: {
273
358
  border: `1px solid ${colors.border}`,
274
359
  borderRadius: 6,
275
360
  padding: 14,
@@ -284,7 +369,7 @@ window.__ModuleLoader__.load({
284
369
  wordBreak: "break-word",
285
370
  font: "inherit",
286
371
  lineHeight: 1.55
287
- } }, payload?.summary.trim() || t("noSummary"))), (0, react.createElement)("div", { style: {
372
+ } }, displayedPayload?.summary.trim() || t("noSummary"))), (0, react.createElement)("div", { style: {
288
373
  display: "grid",
289
374
  gap: 10
290
375
  } }, (0, react.createElement)("div", { style: {
@@ -348,11 +433,7 @@ window.__ModuleLoader__.load({
348
433
  fontSize: 11
349
434
  } }, `${t("updated")}: ${formatDate(entry.updatedAt)}`))))));
350
435
  }
351
- const inject = [
352
- "slots",
353
- "locale",
354
- "sessions"
355
- ];
436
+ const inject = ["slots", "locale"];
356
437
  function apply(ctx) {
357
438
  ctx.effect(() => ctx.locale.register(NS, {
358
439
  zh,
@@ -368,10 +449,7 @@ window.__ModuleLoader__.load({
368
449
  order: 50,
369
450
  label: () => t("nav"),
370
451
  locale: NS
371
- }, () => (0, react.createElement)(MemorySection, {
372
- t,
373
- sessions: ctx.sessions
374
- })));
452
+ }, () => (0, react.createElement)(MemorySection, { t })));
375
453
  }
376
454
  //#endregion
377
455
  exports.apply = apply;
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["useSyncExternalStore","h","useState","useMemo"],"sources":["../src/client/index.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, useSyncExternalStore } from 'react'\nimport { createElement as h } from 'react'\nimport type { SettingsSectionOwnerProps } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { SessionListState } from '@deepseek-ai/dsh-client-runtime/client'\n\nconst NS = 'workspace-memory'\n\nconst zh = {\n nav: '记忆',\n title: '记忆',\n refresh: '刷新',\n global: '全局记忆',\n workspace: '项目记忆',\n current: '当前项目',\n emptyScopes: '还没有已记录的项目记忆',\n summary: '摘要记忆',\n entries: '结构化记忆',\n search: '筛选记忆',\n searchPlaceholder: '搜索标题、内容或标签',\n noSummary: '暂无摘要',\n noEntries: '暂无结构化记忆',\n loading: '正在读取…',\n failed: '读取记忆失败',\n retry: '重试',\n pending: '待整理',\n checkpoints: '整理次数',\n updated: '最近更新',\n all: '全部',\n count: '条',\n}\n\nconst en = {\n nav: 'Memory',\n title: 'Memory',\n refresh: 'Refresh',\n global: 'Global memory',\n workspace: 'Project memory',\n current: 'Current project',\n emptyScopes: 'No project memories recorded yet',\n summary: 'Summary memory',\n entries: 'Structured memory',\n search: 'Filter memory',\n searchPlaceholder: 'Search title, content, or tags',\n noSummary: 'No summary yet',\n noEntries: 'No structured memories yet',\n loading: 'Loading…',\n failed: 'Unable to read memory',\n retry: 'Retry',\n pending: 'Pending',\n checkpoints: 'Checkpoints',\n updated: 'Last updated',\n all: 'All',\n count: '',\n}\n\ntype Translate = (key: keyof typeof zh) => string\n\ninterface ScopeInfo {\n key: string\n cwd: string\n kind: 'global' | 'workspace'\n entryCount: number\n hasSummary: boolean\n pending: number\n checkpointCount: number\n updatedAt?: string\n}\n\ninterface MemoryEntry {\n id: string\n scope: string\n type: string\n title: string\n description: string\n content: string\n tags: string[]\n importance: number\n createdAt?: string\n updatedAt?: string\n}\n\ninterface ScopePayload {\n scope: { key: string; cwd: string; kind: 'global' | 'workspace' }\n summary: string\n entries: MemoryEntry[]\n state: { pending: number; checkpointCount: number; lastCheckpointAt: number }\n}\n\ninterface Snapshot<T> {\n getSnapshot(): T\n subscribe(listener: () => void): () => void\n}\n\ninterface SessionsLike {\n list: Snapshot<SessionListState>\n}\n\ninterface SlotsLike {\n inject(slot: string, register: () => unknown): void\n register(options: Record<string, unknown>, component: () => unknown): unknown\n}\n\ninterface LocaleLike {\n register(namespace: string, dictionaries: { zh: Record<string, string>; en: Record<string, string> }): unknown\n bind(namespace: string): (key: string) => string\n}\n\ninterface MemoryClientContext {\n effect(callback: () => unknown, label?: string): void\n slots: SlotsLike\n locale: LocaleLike\n sessions: SessionsLike\n}\n\nfunction useSnapshot<T>(source: Snapshot<T>): T {\n return useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot)\n}\n\nfunction workspaceName(cwd: string, fallback: string): string {\n const normalized = cwd.replaceAll('\\\\', '/').replace(/\\/+$/u, '')\n const name = normalized.split('/').filter(Boolean).at(-1)\n return name ?? fallback\n}\n\nfunction normalizeCwd(cwd: string): string {\n return cwd.replaceAll('\\\\', '/').replace(/\\/+$/u, '').toLocaleLowerCase()\n}\n\nfunction formatDate(value: string | undefined): string {\n if (value === undefined || value === '') return ''\n const date = new Date(value)\n return Number.isFinite(date.getTime()) ? date.toLocaleString() : value\n}\n\nfunction iconRefresh(): ReturnType<typeof h> {\n return h('svg', { width: 16, height: 16, viewBox: '0 0 16 16', 'aria-hidden': true },\n h('path', { d: 'M13 4.8A5.5 5.5 0 1 0 13.5 9', fill: 'none', stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round' }),\n h('path', { d: 'M10.8 2.8h2.8v2.8', fill: 'none', stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round', strokeLinejoin: 'round' }),\n )\n}\n\nfunction MemorySection({ t, sessions }: { t: Translate; sessions: SessionsLike } & Partial<SettingsSectionOwnerProps>) {\n const sessionList = useSnapshot(sessions.list)\n const currentCwd = sessionList.current === undefined ? '' : sessionList.byId[sessionList.current]?.cwd ?? ''\n const [scopes, setScopes] = useState<ScopeInfo[]>([])\n const [selectedKey, setSelectedKey] = useState('global')\n const [payload, setPayload] = useState<ScopePayload | undefined>()\n const [query, setQuery] = useState('')\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState(false)\n\n const loadScopes = async (): Promise<ScopeInfo[]> => {\n const response = await fetch('/workspace-memory/api/v1/scopes')\n if (!response.ok) throw new Error(`scope list failed (${response.status})`)\n const result = await response.json() as { scopes?: ScopeInfo[] }\n return Array.isArray(result.scopes) ? result.scopes : []\n }\n\n const loadScope = async (cwd: string): Promise<ScopePayload> => {\n const response = await fetch(`/workspace-memory/api/v1/scope?cwd=${encodeURIComponent(cwd)}`)\n if (!response.ok) throw new Error(`scope read failed (${response.status})`)\n return await response.json() as ScopePayload\n }\n\n const reload = async (cwd?: string): Promise<void> => {\n setLoading(true)\n setError(false)\n try {\n const nextScopes = await loadScopes()\n setScopes(nextScopes)\n const next = await loadScope(cwd ?? payload?.scope.cwd ?? currentCwd)\n setPayload(next)\n setSelectedKey(next.scope.key)\n } catch {\n setError(true)\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n void reload(currentCwd)\n // The initial project follows the current session; later changes are user-selected.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const options = useMemo(() => {\n const known = new Map(scopes.map(scope => [scope.key, scope]))\n if (payload !== undefined && !known.has(payload.scope.key)) {\n known.set(payload.scope.key, {\n key: payload.scope.key,\n cwd: payload.scope.cwd,\n kind: payload.scope.kind,\n entryCount: payload.entries.length,\n hasSummary: payload.summary.trim() !== '',\n pending: payload.state.pending,\n checkpointCount: payload.state.checkpointCount,\n })\n }\n const currentKey = `current:${currentCwd.toLocaleLowerCase()}`\n if (currentCwd !== '' && !known.has(currentKey) && ![...known.values()].some(scope => normalizeCwd(scope.cwd) === normalizeCwd(currentCwd))) {\n known.set(currentKey, {\n key: currentKey,\n cwd: currentCwd,\n kind: 'workspace',\n entryCount: 0,\n hasSummary: false,\n pending: 0,\n checkpointCount: 0,\n })\n }\n return [...known.values()]\n }, [scopes, currentCwd, payload])\n\n const filteredEntries = useMemo(() => {\n const normalized = query.trim().toLocaleLowerCase()\n if (normalized === '') return payload?.entries ?? []\n return (payload?.entries ?? []).filter(entry => [entry.title, entry.description, entry.content, ...entry.tags]\n .join(' ').toLocaleLowerCase().includes(normalized))\n }, [payload, query])\n\n const onSelect = (key: string): void => {\n const option = options.find(scope => scope.key === key)\n if (option === undefined) return\n setSelectedKey(key)\n void reload(option.cwd)\n }\n\n const colors = {\n text: 'var(--ds-color-text-primary, #f1f1f1)',\n secondary: 'var(--ds-color-text-secondary, #a7a7ad)',\n border: 'var(--ds-color-border, rgba(255,255,255,.12))',\n surface: 'var(--ds-color-bg-secondary, rgba(255,255,255,.045))',\n accent: 'var(--ds-color-primary, #8ab4ff)',\n }\n\n return h('section', { style: { color: colors.text, maxWidth: 820, paddingBottom: 24 } },\n h('header', { style: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, marginBottom: 18 } },\n h('div', null,\n h('h2', { style: { margin: 0, fontSize: 20, fontWeight: 650 } }, t('title')),\n h('p', { style: { margin: '6px 0 0', color: colors.secondary, fontSize: 12, wordBreak: 'break-all' } }, payload?.scope.cwd || t('global')),\n ),\n h('button', {\n type: 'button', title: t('refresh'), 'aria-label': t('refresh'), onClick: () => { void reload() },\n style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 34, height: 34, border: `1px solid ${colors.border}`, borderRadius: 6, background: 'transparent', color: colors.text, cursor: 'pointer' },\n }, iconRefresh()),\n ),\n h('div', { style: { display: 'grid', gap: 8, marginBottom: 18 } },\n h('label', { style: { color: colors.secondary, fontSize: 12 } }, t('current')),\n h('select', {\n value: selectedKey, onChange: (event: Event) => { onSelect((event.target as unknown as { value: string }).value) },\n style: { minHeight: 38, padding: '0 10px', color: colors.text, background: colors.surface, border: `1px solid ${colors.border}`, borderRadius: 6 },\n }, options.length === 0\n ? h('option', { value: '' }, t('emptyScopes'))\n : options.map(scope => h('option', { key: scope.key, value: scope.key }, scope.kind === 'global' ? t('global') : workspaceName(scope.cwd, t('workspace')))),\n ),\n ),\n error && h('div', { role: 'alert', style: { padding: 12, border: `1px solid ${colors.border}`, borderRadius: 6, color: colors.secondary, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 } },\n h('span', null, t('failed')),\n h('button', { type: 'button', onClick: () => { void reload() }, style: { color: colors.accent, background: 'transparent', border: 0, cursor: 'pointer' } }, t('retry')),\n ),\n loading && h('p', { style: { color: colors.secondary } }, t('loading')),\n !loading && !error && h('div', { style: { display: 'grid', gap: 16 } },\n h('div', { style: { display: 'flex', gap: 16, color: colors.secondary, fontSize: 12, flexWrap: 'wrap' } },\n h('span', null, `${payload?.entries.length ?? 0}${t('count')}`),\n h('span', null, `${t('pending')}: ${payload?.state.pending ?? 0}`),\n h('span', null, `${t('checkpoints')}: ${payload?.state.checkpointCount ?? 0}`),\n ),\n h('article', { style: { border: `1px solid ${colors.border}`, borderRadius: 6, padding: 14, background: colors.surface } },\n h('h3', { style: { margin: '0 0 10px', fontSize: 14 } }, t('summary')),\n h('pre', { style: { margin: 0, color: colors.secondary, whiteSpace: 'pre-wrap', wordBreak: 'break-word', font: 'inherit', lineHeight: 1.55 } }, payload?.summary.trim() || t('noSummary')),\n ),\n h('div', { style: { display: 'grid', gap: 10 } },\n h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 } },\n h('h3', { style: { margin: 0, fontSize: 14 } }, `${t('entries')} (${filteredEntries.length})`),\n h('input', { type: 'search', value: query, placeholder: t('searchPlaceholder'), 'aria-label': t('search'), onChange: (event: Event) => { setQuery((event.target as unknown as { value: string }).value) }, style: { minHeight: 32, width: 230, maxWidth: '48%', padding: '0 9px', color: colors.text, background: 'transparent', border: `1px solid ${colors.border}`, borderRadius: 6 } }),\n ),\n filteredEntries.length === 0 && h('p', { style: { color: colors.secondary, margin: 0 } }, t('noEntries')),\n filteredEntries.map(entry => h('details', { key: entry.id, style: { border: `1px solid ${colors.border}`, borderRadius: 6, padding: '10px 12px', background: colors.surface } },\n h('summary', { style: { cursor: 'pointer', display: 'flex', gap: 8, alignItems: 'baseline' } },\n h('strong', { style: { fontSize: 13 } }, entry.title),\n h('span', { style: { color: colors.secondary, fontSize: 11 } }, entry.type),\n ),\n h('p', { style: { margin: '10px 0 0', color: colors.secondary, lineHeight: 1.5, whiteSpace: 'pre-wrap', wordBreak: 'break-word' } }, entry.content),\n entry.tags.length > 0 && h('div', { style: { marginTop: 10, color: colors.accent, fontSize: 11 } }, entry.tags.join(' · ')),\n entry.updatedAt !== undefined && h('div', { style: { marginTop: 8, color: colors.secondary, fontSize: 11 } }, `${t('updated')}: ${formatDate(entry.updatedAt)}`),\n )),\n ),\n ),\n )\n}\n\nexport const inject = ['slots', 'locale', 'sessions']\n\nexport function apply(ctx: MemoryClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'workspace-memory: dictionaries')\n const translate = ctx.locale.bind(NS)\n const t = (key: keyof typeof zh): string => {\n const translated = translate(key)\n return translated || zh[key]\n }\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'workspace-memory',\n order: 50,\n label: () => t('nav'),\n locale: NS,\n }, () => h(MemorySection, { t, sessions: ctx.sessions })))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["h","useState","useMemo"],"sources":["../src/client/index.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react'\nimport { createElement as h } from 'react'\nimport type { SettingsSectionOwnerProps } from '@deepseek-ai/dsh-client-ui-settings/client'\n\nconst NS = 'workspace-memory'\n\nconst zh = {\n nav: '记忆',\n title: '记忆',\n refresh: '刷新',\n global: '全局记忆',\n workspace: '项目记忆',\n scope: '选择记忆范围',\n emptyScopes: '还没有已记录的项目记忆',\n summary: '摘要记忆',\n entries: '结构化记忆',\n search: '筛选记忆',\n searchPlaceholder: '搜索标题、内容或标签',\n noSummary: '暂无摘要',\n noEntries: '暂无结构化记忆',\n loading: '正在读取…',\n failed: '读取记忆失败',\n retry: '重试',\n activeView: '记忆',\n archiveView: '回收区',\n archiveEmpty: '回收区为空',\n purge: '永久删除',\n purgeConfirm: '确定永久删除这个项目的全部记忆吗?此操作无法撤销。',\n pending: '待整理',\n checkpoints: '整理次数',\n updated: '最近更新',\n all: '全部',\n count: '条',\n}\n\nconst en = {\n nav: 'Memory',\n title: 'Memory',\n refresh: 'Refresh',\n global: 'Global memory',\n workspace: 'Project memory',\n scope: 'Memory scope',\n emptyScopes: 'No project memories recorded yet',\n summary: 'Summary memory',\n entries: 'Structured memory',\n search: 'Filter memory',\n searchPlaceholder: 'Search title, content, or tags',\n noSummary: 'No summary yet',\n noEntries: 'No structured memories yet',\n loading: 'Loading…',\n failed: 'Unable to read memory',\n retry: 'Retry',\n activeView: 'Memory',\n archiveView: 'Recycle bin',\n archiveEmpty: 'Recycle bin is empty',\n purge: 'Delete permanently',\n purgeConfirm: 'Permanently delete all memory for this project? This cannot be undone.',\n pending: 'Pending',\n checkpoints: 'Checkpoints',\n updated: 'Last updated',\n all: 'All',\n count: '',\n}\n\ntype Translate = (key: keyof typeof zh) => string\n\ninterface ScopeInfo {\n key: string\n cwd: string\n kind: 'global' | 'workspace'\n entryCount: number\n hasSummary: boolean\n pending: number\n checkpointCount: number\n updatedAt?: string\n}\n\ninterface MemoryEntry {\n id: string\n scope: string\n type: string\n title: string\n description: string\n content: string\n tags: string[]\n importance: number\n createdAt?: string\n updatedAt?: string\n}\n\ninterface ScopePayload {\n scope: { key: string; cwd: string; kind: 'global' | 'workspace' }\n summary: string\n entries: MemoryEntry[]\n state: { pending: number; checkpointCount: number; lastCheckpointAt: number }\n}\n\ntype MemoryView = 'active' | 'archived'\n\ninterface SlotsLike {\n inject(slot: string, register: () => unknown): void\n register(options: Record<string, unknown>, component: () => unknown): unknown\n}\n\ninterface LocaleLike {\n register(namespace: string, dictionaries: { zh: Record<string, string>; en: Record<string, string> }): unknown\n bind(namespace: string): (key: string) => string\n}\n\ninterface MemoryClientContext {\n effect(callback: () => unknown, label?: string): void\n slots: SlotsLike\n locale: LocaleLike\n}\n\nfunction workspaceName(cwd: string, fallback: string): string {\n const normalized = cwd.replaceAll('\\\\', '/').replace(/\\/+$/u, '')\n const name = normalized.split('/').filter(Boolean).at(-1)\n return name ?? fallback\n}\n\nfunction formatDate(value: string | undefined): string {\n if (value === undefined || value === '') return ''\n const date = new Date(value)\n return Number.isFinite(date.getTime()) ? date.toLocaleString() : value\n}\n\nfunction iconRefresh(): ReturnType<typeof h> {\n return h('svg', { width: 16, height: 16, viewBox: '0 0 16 16', 'aria-hidden': true },\n h('path', { d: 'M13 4.8A5.5 5.5 0 1 0 13.5 9', fill: 'none', stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round' }),\n h('path', { d: 'M10.8 2.8h2.8v2.8', fill: 'none', stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round', strokeLinejoin: 'round' }),\n )\n}\n\nfunction MemorySection({ t }: { t: Translate } & Partial<SettingsSectionOwnerProps>) {\n const [scopes, setScopes] = useState<ScopeInfo[]>([])\n const [selectedKey, setSelectedKey] = useState('global')\n const [payload, setPayload] = useState<ScopePayload | undefined>()\n const [archivedScopes, setArchivedScopes] = useState<ScopeInfo[]>([])\n const [archivedSelectedKey, setArchivedSelectedKey] = useState('')\n const [archivedPayload, setArchivedPayload] = useState<ScopePayload | undefined>()\n const [view, setView] = useState<MemoryView>('active')\n const [query, setQuery] = useState('')\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState(false)\n\n const loadScopes = async (): Promise<ScopeInfo[]> => {\n const response = await fetch('/workspace-memory/api/v1/scopes')\n if (!response.ok) throw new Error(`scope list failed (${response.status})`)\n const result = await response.json() as { scopes?: ScopeInfo[] }\n return Array.isArray(result.scopes) ? result.scopes : []\n }\n\n const loadScope = async (cwd: string): Promise<ScopePayload> => {\n const response = await fetch(`/workspace-memory/api/v1/scope?cwd=${encodeURIComponent(cwd)}`)\n if (!response.ok) throw new Error(`scope read failed (${response.status})`)\n return await response.json() as ScopePayload\n }\n\n const loadArchivedScopes = async (): Promise<ScopeInfo[]> => {\n const response = await fetch('/workspace-memory/api/v1/archived-scopes')\n if (!response.ok) throw new Error(`archived scope list failed (${response.status})`)\n const result = await response.json() as { scopes?: ScopeInfo[] }\n return Array.isArray(result.scopes) ? result.scopes : []\n }\n\n const loadArchivedScope = async (key: string): Promise<ScopePayload> => {\n const response = await fetch(`/workspace-memory/api/v1/archived-scope?key=${encodeURIComponent(key)}`)\n if (!response.ok) throw new Error(`archived scope read failed (${response.status})`)\n return await response.json() as ScopePayload\n }\n\n const reload = async (cwd?: string): Promise<void> => {\n setLoading(true)\n setError(false)\n try {\n const nextScopes = await loadScopes()\n setScopes(nextScopes)\n const next = await loadScope(cwd ?? payload?.scope.cwd ?? '')\n setPayload(next)\n setSelectedKey(next.scope.key)\n } catch {\n setError(true)\n } finally {\n setLoading(false)\n }\n }\n\n const reloadArchived = async (key?: string): Promise<void> => {\n setLoading(true)\n setError(false)\n try {\n const nextScopes = await loadArchivedScopes()\n setArchivedScopes(nextScopes)\n const nextKey = key ?? (archivedSelectedKey !== '' ? archivedSelectedKey : nextScopes[0]?.key) ?? ''\n if (nextKey === '') {\n setArchivedSelectedKey('')\n setArchivedPayload(undefined)\n } else {\n const next = await loadArchivedScope(nextKey)\n setArchivedSelectedKey(next.scope.key)\n setArchivedPayload(next)\n }\n } catch {\n setError(true)\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n void reload()\n // Memory browsing is independent from the currently open session.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const options = useMemo(() => {\n const known = new Map(scopes.map(scope => [scope.key, scope]))\n return [...known.values()]\n }, [scopes])\n\n const archivedOptions = useMemo(() => archivedScopes, [archivedScopes])\n\n const displayedPayload = view === 'active' ? payload : archivedPayload\n const displayedOptions = view === 'active' ? options : archivedOptions\n const displayedKey = view === 'active' ? selectedKey : archivedSelectedKey\n\n const filteredEntries = useMemo(() => {\n const normalized = query.trim().toLocaleLowerCase()\n if (normalized === '') return displayedPayload?.entries ?? []\n return (displayedPayload?.entries ?? []).filter(entry => [entry.title, entry.description, entry.content, ...entry.tags]\n .join(' ').toLocaleLowerCase().includes(normalized))\n }, [displayedPayload, query])\n\n const onSelect = (key: string): void => {\n const option = options.find(scope => scope.key === key)\n if (option === undefined) return\n setSelectedKey(key)\n void reload(option.cwd)\n }\n\n const onSelectArchived = (key: string): void => {\n if (!archivedOptions.some(scope => scope.key === key)) return\n setArchivedSelectedKey(key)\n void reloadArchived(key)\n }\n\n const showArchived = (): void => {\n setView('archived')\n if (archivedPayload === undefined) void reloadArchived()\n }\n\n const purge = async (): Promise<void> => {\n const confirm = (globalThis as unknown as { confirm?: (message: string) => boolean }).confirm\n if (archivedSelectedKey === '' || (confirm !== undefined && !confirm(t('purgeConfirm')))) return\n setLoading(true)\n setError(false)\n try {\n const response = await fetch(`/workspace-memory/api/v1/archived-scope?key=${encodeURIComponent(archivedSelectedKey)}`, { method: 'DELETE' })\n if (!response.ok) throw new Error(`archived scope delete failed (${response.status})`)\n await reloadArchived()\n } catch {\n setError(true)\n setLoading(false)\n }\n }\n\n const colors = {\n text: 'var(--ds-color-text-primary, #f1f1f1)',\n secondary: 'var(--ds-color-text-secondary, #a7a7ad)',\n border: 'var(--ds-color-border, rgba(255,255,255,.12))',\n surface: 'var(--ds-color-bg-secondary, rgba(255,255,255,.045))',\n accent: 'var(--ds-color-primary, #8ab4ff)',\n }\n\n return h('section', { style: { color: colors.text, maxWidth: 820, paddingBottom: 24 } },\n h('header', { style: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, marginBottom: 18 } },\n h('div', null,\n h('h2', { style: { margin: 0, fontSize: 20, fontWeight: 650 } }, t('title')),\n h('p', { style: { margin: '6px 0 0', color: colors.secondary, fontSize: 12, wordBreak: 'break-all' } }, displayedPayload?.scope.cwd || t('global')),\n ),\n h('button', {\n type: 'button', title: t('refresh'), 'aria-label': t('refresh'), onClick: () => { void (view === 'active' ? reload() : reloadArchived()) },\n style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 34, height: 34, border: `1px solid ${colors.border}`, borderRadius: 6, background: 'transparent', color: colors.text, cursor: 'pointer' },\n }, iconRefresh()),\n ),\n h('div', { style: { display: 'flex', gap: 6, marginBottom: 16 } },\n h('button', { type: 'button', onClick: () => setView('active'), 'aria-pressed': view === 'active', style: { padding: '7px 11px', border: `1px solid ${colors.border}`, borderRadius: 6, background: view === 'active' ? colors.surface : 'transparent', color: colors.text, cursor: 'pointer' } }, t('activeView')),\n h('button', { type: 'button', onClick: showArchived, 'aria-pressed': view === 'archived', style: { padding: '7px 11px', border: `1px solid ${colors.border}`, borderRadius: 6, background: view === 'archived' ? colors.surface : 'transparent', color: colors.text, cursor: 'pointer' } }, t('archiveView')),\n ),\n h('div', { style: { display: 'grid', gap: 8, marginBottom: 18 } },\n h('label', { style: { color: colors.secondary, fontSize: 12 } }, t('scope')),\n h('select', {\n value: displayedKey, onChange: (event: Event) => { const key = (event.target as unknown as { value: string }).value; if (view === 'active') onSelect(key); else onSelectArchived(key) },\n style: { minHeight: 38, padding: '0 10px', color: colors.text, background: colors.surface, border: `1px solid ${colors.border}`, borderRadius: 6 },\n }, displayedOptions.length === 0\n ? h('option', { value: '' }, view === 'active' ? t('emptyScopes') : t('archiveEmpty'))\n : displayedOptions.map(scope => h('option', { key: scope.key, value: scope.key }, scope.kind === 'global' ? t('global') : workspaceName(scope.cwd, t('workspace')))),\n ),\n ),\n error && h('div', { role: 'alert', style: { padding: 12, border: `1px solid ${colors.border}`, borderRadius: 6, color: colors.secondary, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 } },\n h('span', null, t('failed')),\n h('button', { type: 'button', onClick: () => { void (view === 'active' ? reload() : reloadArchived()) }, style: { color: colors.accent, background: 'transparent', border: 0, cursor: 'pointer' } }, t('retry')),\n ),\n loading && h('p', { style: { color: colors.secondary } }, t('loading')),\n !loading && !error && h('div', { style: { display: 'grid', gap: 16 } },\n h('div', { style: { display: 'flex', gap: 16, color: colors.secondary, fontSize: 12, flexWrap: 'wrap' } },\n h('span', null, `${displayedPayload?.entries.length ?? 0}${t('count')}`),\n h('span', null, `${t('pending')}: ${displayedPayload?.state.pending ?? 0}`),\n h('span', null, `${t('checkpoints')}: ${displayedPayload?.state.checkpointCount ?? 0}`),\n ),\n view === 'archived' && displayedPayload !== undefined && h('button', { type: 'button', onClick: () => { void purge() }, style: { justifySelf: 'start', color: '#ff8f8f', background: 'transparent', border: `1px solid ${colors.border}`, borderRadius: 6, padding: '7px 11px', cursor: 'pointer' } }, t('purge')),\n h('article', { style: { border: `1px solid ${colors.border}`, borderRadius: 6, padding: 14, background: colors.surface } },\n h('h3', { style: { margin: '0 0 10px', fontSize: 14 } }, t('summary')),\n h('pre', { style: { margin: 0, color: colors.secondary, whiteSpace: 'pre-wrap', wordBreak: 'break-word', font: 'inherit', lineHeight: 1.55 } }, displayedPayload?.summary.trim() || t('noSummary')),\n ),\n h('div', { style: { display: 'grid', gap: 10 } },\n h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 } },\n h('h3', { style: { margin: 0, fontSize: 14 } }, `${t('entries')} (${filteredEntries.length})`),\n h('input', { type: 'search', value: query, placeholder: t('searchPlaceholder'), 'aria-label': t('search'), onChange: (event: Event) => { setQuery((event.target as unknown as { value: string }).value) }, style: { minHeight: 32, width: 230, maxWidth: '48%', padding: '0 9px', color: colors.text, background: 'transparent', border: `1px solid ${colors.border}`, borderRadius: 6 } }),\n ),\n filteredEntries.length === 0 && h('p', { style: { color: colors.secondary, margin: 0 } }, t('noEntries')),\n filteredEntries.map(entry => h('details', { key: entry.id, style: { border: `1px solid ${colors.border}`, borderRadius: 6, padding: '10px 12px', background: colors.surface } },\n h('summary', { style: { cursor: 'pointer', display: 'flex', gap: 8, alignItems: 'baseline' } },\n h('strong', { style: { fontSize: 13 } }, entry.title),\n h('span', { style: { color: colors.secondary, fontSize: 11 } }, entry.type),\n ),\n h('p', { style: { margin: '10px 0 0', color: colors.secondary, lineHeight: 1.5, whiteSpace: 'pre-wrap', wordBreak: 'break-word' } }, entry.content),\n entry.tags.length > 0 && h('div', { style: { marginTop: 10, color: colors.accent, fontSize: 11 } }, entry.tags.join(' · ')),\n entry.updatedAt !== undefined && h('div', { style: { marginTop: 8, color: colors.secondary, fontSize: 11 } }, `${t('updated')}: ${formatDate(entry.updatedAt)}`),\n )),\n ),\n ),\n )\n}\n\nexport const inject = ['slots', 'locale']\n\nexport function apply(ctx: MemoryClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'workspace-memory: dictionaries')\n const translate = ctx.locale.bind(NS)\n const t = (key: keyof typeof zh): string => {\n const translated = translate(key)\n return translated || zh[key]\n }\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'workspace-memory',\n order: 50,\n label: () => t('nav'),\n locale: NS,\n }, () => h(MemorySection, { t })))\n}\n"],"mappings":"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"}
package/lib/core.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { existsSync } from 'node:fs';
3
- import { mkdir, readFile, readdir, rename, unlink, writeFile } from 'node:fs/promises';
3
+ import { mkdir, readFile, readdir, rename, rm, unlink, writeFile } from 'node:fs/promises';
4
4
  import { dirname, isAbsolute, join, normalize, resolve } from 'node:path';
5
5
  export const SUMMARY_FILE = 'memory_summary.md';
6
6
  export const ENTRIES_FILE = 'memory_entries.json';
7
7
  export const STATE_FILE = 'state.json';
8
8
  export const CHECKPOINT_DIR = 'checkpoints';
9
9
  export const HISTORY_DIR = 'summary_history';
10
+ export const ARCHIVE_DIR = 'archived';
10
11
  const DEFAULT_STATE = Object.freeze({
11
12
  version: 0,
12
13
  checkpointCount: 0,
@@ -187,6 +188,9 @@ function validMessage(value) {
187
188
  function scopeDirectory(root, key) {
188
189
  return key === 'global' ? join(root, 'global') : join(root, 'scopes', key);
189
190
  }
191
+ function archivedScopeDirectory(root, key) {
192
+ return join(root, ARCHIVE_DIR, key);
193
+ }
190
194
  function deterministicSummary(entries, maxBytes, heading = '# Workspace memory') {
191
195
  const active = entries
192
196
  .filter(entry => entry.status !== 'deleted')
@@ -322,6 +326,54 @@ export class WorkspaceMemoryStore {
322
326
  }
323
327
  return result.sort((left, right) => left.key === 'global' ? -1 : right.key === 'global' ? 1 : left.cwd.localeCompare(right.cwd));
324
328
  }
329
+ /** List workspace scopes moved to the recoverable archive. */
330
+ async listArchivedScopes() {
331
+ const result = [];
332
+ const directories = await readdir(join(this.root, ARCHIVE_DIR), { withFileTypes: true }).catch(() => []);
333
+ for (const directory of directories) {
334
+ if (!directory.isDirectory() || !/^ws-[a-f0-9]{20}$/u.test(directory.name))
335
+ continue;
336
+ const descriptor = join(archivedScopeDirectory(this.root, directory.name), 'scope.json');
337
+ const raw = await readFile(descriptor, 'utf8').catch(() => '');
338
+ try {
339
+ const value = JSON.parse(raw);
340
+ if (!isRecord(value) || typeof value.cwd !== 'string' || value.cwd.trim() === '')
341
+ continue;
342
+ const scope = this.scope(value.cwd);
343
+ if (scope.key === directory.name)
344
+ result.push(scope);
345
+ }
346
+ catch {
347
+ // Ignore incomplete archive descriptors.
348
+ }
349
+ }
350
+ return result.sort((left, right) => left.cwd.localeCompare(right.cwd));
351
+ }
352
+ /** Move one workspace scope to the recoverable archive. */
353
+ async archiveScope(scope) {
354
+ if (scope.key === 'global' || !existsSync(scope.dir))
355
+ return false;
356
+ return this.withScope(scope, async () => {
357
+ if (!existsSync(scope.dir))
358
+ return false;
359
+ const destination = archivedScopeDirectory(this.root, scope.key);
360
+ await mkdir(join(this.root, ARCHIVE_DIR), { recursive: true });
361
+ if (existsSync(destination))
362
+ return false;
363
+ await rename(scope.dir, destination);
364
+ return true;
365
+ });
366
+ }
367
+ /** Permanently remove one archived scope and all of its history/checkpoints. */
368
+ async purgeArchivedScope(scope) {
369
+ if (scope.key === 'global')
370
+ return false;
371
+ const directory = archivedScopeDirectory(this.root, scope.key);
372
+ if (!existsSync(directory))
373
+ return false;
374
+ await rm(directory, { recursive: true, force: true });
375
+ return true;
376
+ }
325
377
  /** Read one scope without forcing initialization or writing any files. */
326
378
  async readSnapshot(scope) {
327
379
  const [summary, entries, state] = await Promise.all([
@@ -331,6 +383,10 @@ export class WorkspaceMemoryStore {
331
383
  ]);
332
384
  return { scope, summary, entries, state };
333
385
  }
386
+ /** Read a scope that currently lives in the recoverable archive. */
387
+ async readArchivedSnapshot(scope) {
388
+ return this.readSnapshot({ ...scope, dir: archivedScopeDirectory(this.root, scope.key) });
389
+ }
334
390
  async writeEntries(scope, entries) {
335
391
  await this.writeAtomic(join(scope.dir, ENTRIES_FILE), JSON.stringify(entries, null, 2) + '\n');
336
392
  }