@max-null/dsh-memory 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/client.js +182 -145
  3. package/package.json +13 -13
package/README.md CHANGED
@@ -58,7 +58,7 @@ memory_forget → 随时删除
58
58
 
59
59
  ## SSID 系列
60
60
 
61
- 本插件属于 **`@max-null/*` 插件系列**——这一系列共同构成 **[SSID(思灵 · Seek Soul in Darkness)](https://github.com/Max-Null/seek-soul-in-darkness)** 桌面体验。SSID 是整合它们的盒:`dsh-plugin-center` · `dsh-memory` · `dsh-chinese-thinking` · `dsh-guardian` · `dsh-habit`。
61
+ 本插件属于 **`@max-null/*` 插件系列**——这一系列共同构成 **[SSID(思灵 · Seek Soul in Darkness)](https://github.com/Max-Null/seek-soul-in-darkness)** 桌面体验。SSID 是整合它们的盒:`dsh-chat-rail` · `dsh-chinese-thinking` · `dsh-draft-polish` · `dsh-guardian` · `dsh-habit` · `dsh-header-unify` · `dsh-memory` · `dsh-node-appearance` · `dsh-plugin-center` · `dsh-skill-mcp-center` · `dsh-ssid-panels` · `dsh-ssid-zh-ui`。
62
62
 
63
63
  This plugin belongs to the **`@max-null/*` family** — a set of plugins that together form the **[SSID (思灵 · Seek Soul in Darkness)](https://github.com/Max-Null/seek-soul-in-darkness)** desktop experience.
64
64
 
package/client.js CHANGED
@@ -226,7 +226,26 @@ function useT() {
226
226
  }
227
227
  var ssid = {
228
228
  accent: "#4FC3F7",
229
- wrap: { display: "flex", flexDirection: "column", gap: 8, padding: "10px 12px", overflowY: "auto", height: "100%", boxSizing: "border-box" },
229
+ // Root column: toolbar + namespace tabs stay pinned at the top (flex:none);
230
+ // only the content area below scrolls (2026-08-22 用户: tab 与功能不应随滚动)。
231
+ wrap: { display: "flex", flexDirection: "column", gap: 8, padding: "10px 12px", height: "100%", boxSizing: "border-box" },
232
+ header: { display: "flex", flexDirection: "column", gap: 8, flex: "none" },
233
+ content: { flex: 1, minHeight: 0, overflowY: "auto", display: "flex", flexDirection: "column", gap: 8, marginRight: -4, paddingRight: 4 },
234
+ // 计数徽标(醒目:accent 底 + 白色数字)。
235
+ count: {
236
+ display: "inline-flex",
237
+ alignItems: "center",
238
+ justifyContent: "center",
239
+ minWidth: 18,
240
+ height: 18,
241
+ padding: "0 6px",
242
+ borderRadius: 9,
243
+ background: "var(--dsw-alias-state-business-primary, #4FC3F7)",
244
+ color: "#fff",
245
+ fontSize: 11,
246
+ fontWeight: 600,
247
+ lineHeight: 1
248
+ },
230
249
  card: {
231
250
  background: "var(--dsw-alias-bg-layer-1, #131a26)",
232
251
  border: "1px solid var(--dsw-alias-border-l2, #1e2836)",
@@ -369,6 +388,11 @@ function MemoryView(props) {
369
388
  const q = query.trim().toLowerCase();
370
389
  const byNs = namespace === null ? records : namespace === "workspace" ? records.filter((record) => record.namespace === "project") : records.filter((record) => record.namespace === "global");
371
390
  const filtered = byNs.filter((record) => q === "" || record.content.toLowerCase().includes(q));
391
+ const nsCounts = {
392
+ all: records.length,
393
+ global: records.filter((record) => record.namespace === "global").length,
394
+ workspace: records.filter((record) => record.namespace === "project").length
395
+ };
372
396
  const groups = [
373
397
  { key: "pending", label: t("groupPending"), items: filtered.filter((record) => record.status === "suggested") },
374
398
  { key: "ondemand", label: t("groupOnDemand"), items: filtered.filter((record) => record.status === "approved" && !record.injected) },
@@ -379,164 +403,177 @@ function MemoryView(props) {
379
403
  { style: ssid.wrap },
380
404
  (0, import_react.createElement)(
381
405
  "div",
382
- { style: { display: "flex", gap: 6 } },
383
- (0, import_react.createElement)("button", {
384
- type: "button",
385
- title: t("organizeMemory"),
386
- onClick: () => {
387
- void organize();
388
- },
389
- disabled: organizing,
390
- style: { ...ssid.btn, color: ssid.accent, borderColor: ssid.accent }
391
- }, organizing ? "\u2026" : t("organizeMemory")),
392
- (0, import_react.createElement)("button", {
393
- type: "button",
394
- title: t("injectPreview"),
395
- onClick: () => {
396
- void togglePreview();
397
- },
398
- style: { ...ssid.btn, ...previewOpen ? { color: ssid.accent, borderColor: ssid.accent } : {} }
399
- }, t("injectPreview")),
400
- (0, import_react.createElement)("input", {
401
- value: query,
402
- onChange: (event) => {
403
- setQuery(event.target.value);
404
- },
405
- placeholder: t("memorySearch"),
406
- style: {
407
- flex: 1,
408
- padding: "6px 10px",
409
- fontSize: 12.5,
410
- boxSizing: "border-box",
411
- background: "var(--dsw-alias-bg-layer-1, #0f141d)",
412
- border: "1px solid var(--dsw-alias-border-l2, #1e2836)",
413
- borderRadius: 8,
414
- color: "var(--dsw-alias-label-primary, #d8e0ea)",
415
- outline: "none"
416
- }
417
- }),
418
- (0, import_react.createElement)("button", {
419
- type: "button",
420
- title: t("refresh"),
421
- onClick: () => {
422
- void refreshFromDisk();
423
- },
424
- disabled: refreshing,
425
- style: ssid.btn
426
- }, refreshing ? "\u2026" : "\u21BB")
427
- ),
428
- (0, import_react.createElement)(
429
- "div",
430
- { style: { display: "flex", gap: 4 } },
431
- [null, "global", "workspace"].map((ns) => (0, import_react.createElement)("button", {
432
- key: ns ?? "all",
433
- onClick: () => {
434
- setNamespace(ns);
435
- },
436
- style: { flex: 1, ...ssid.btn, ...namespace === ns ? { color: ssid.accent, borderColor: ssid.accent } : {} }
437
- }, ns === null ? t("allNamespaces") : ns === "global" ? t("nsGlobal") : t("nsWorkspace")))
438
- ),
439
- // 注入预览(开发者):self 自述 + 当前注入的记忆 + 上下文占用统计
440
- previewOpen && preview !== null ? (0, import_react.createElement)(
441
- "div",
442
- { style: { ...ssid.card, display: "flex", flexDirection: "column", gap: 6 } },
406
+ { style: ssid.header },
443
407
  (0, import_react.createElement)(
444
408
  "div",
445
- { style: { ...ssid.muted, fontSize: 10.5, display: "flex", justifyContent: "space-between", alignItems: "center" } },
446
- (0, import_react.createElement)("span", null, t("contextUsage")),
447
- (0, import_react.createElement)("span", null, (() => {
448
- const selfChars = preview.self.length;
449
- const injectedChars = preview.injected.reduce((sum, record) => sum + record.content.length + record.keywords.join("").length, 0);
450
- const total = selfChars + injectedChars;
451
- const tokens = Math.ceil(total / 2);
452
- return `${selfChars}+${injectedChars} \u5B57\u7B26 \u2248 ${tokens} token`;
453
- })())
409
+ { style: { display: "flex", gap: 6 } },
410
+ (0, import_react.createElement)("button", {
411
+ type: "button",
412
+ title: t("organizeMemory"),
413
+ onClick: () => {
414
+ void organize();
415
+ },
416
+ disabled: organizing,
417
+ style: { ...ssid.btn, color: ssid.accent, borderColor: ssid.accent }
418
+ }, organizing ? "\u2026" : t("organizeMemory")),
419
+ (0, import_react.createElement)("button", {
420
+ type: "button",
421
+ title: t("injectPreview"),
422
+ onClick: () => {
423
+ void togglePreview();
424
+ },
425
+ style: { ...ssid.btn, ...previewOpen ? { color: ssid.accent, borderColor: ssid.accent } : {} }
426
+ }, t("injectPreview")),
427
+ (0, import_react.createElement)("input", {
428
+ value: query,
429
+ onChange: (event) => {
430
+ setQuery(event.target.value);
431
+ },
432
+ placeholder: t("memorySearch"),
433
+ style: {
434
+ flex: 1,
435
+ padding: "6px 10px",
436
+ fontSize: 12.5,
437
+ boxSizing: "border-box",
438
+ background: "var(--dsw-alias-bg-layer-1, #0f141d)",
439
+ border: "1px solid var(--dsw-alias-border-l2, #1e2836)",
440
+ borderRadius: 8,
441
+ color: "var(--dsw-alias-label-primary, #d8e0ea)",
442
+ outline: "none"
443
+ }
444
+ }),
445
+ (0, import_react.createElement)("button", {
446
+ type: "button",
447
+ title: t("refresh"),
448
+ onClick: () => {
449
+ void refreshFromDisk();
450
+ },
451
+ disabled: refreshing,
452
+ style: ssid.btn
453
+ }, refreshing ? "\u2026" : "\u21BB")
454
454
  ),
455
- (0, import_react.createElement)("div", { style: { ...ssid.text, fontSize: 11, whiteSpace: "pre-wrap", wordBreak: "break-all" } }, preview.self),
456
- preview.injected.length === 0 ? (0, import_react.createElement)("div", { style: ssid.muted }, t("empty")) : preview.injected.map((record) => (0, import_react.createElement)(
457
- "div",
458
- { key: record.id, style: { ...ssid.muted, fontSize: 11 } },
459
- `- [memory:${record.id.slice(0, 8)}] ${record.content}`
460
- ))
461
- ) : null,
462
- // 未选择工作区:工作区视图显示占位(0.3.4 工作区路由语义)
463
- namespace === "workspace" && (props.cwd === void 0 || props.cwd === "") ? (0, import_react.createElement)("div", { style: ssid.empty }, t("noWorkspace")) : groups.length === 0 ? (0, import_react.createElement)("div", { style: ssid.empty }, t("empty")) : groups.map((group) => (0, import_react.createElement)(
464
- "div",
465
- { key: group.key, style: { display: "flex", flexDirection: "column", gap: 6 } },
466
455
  (0, import_react.createElement)(
467
456
  "div",
468
- { style: ssid.title },
469
- (0, import_react.createElement)("span", null, group.label),
470
- (0, import_react.createElement)(
471
- "div",
472
- { style: { display: "flex", alignItems: "center", gap: 6 } },
473
- group.key === "pending" && group.items.length > 0 ? (0, import_react.createElement)("button", {
474
- type: "button",
475
- title: t("confirmAll"),
457
+ { style: { display: "flex", gap: 4 } },
458
+ [null, "global", "workspace"].map((ns) => {
459
+ const label = ns === null ? t("allNamespaces") : ns === "global" ? t("nsGlobal") : t("nsWorkspace");
460
+ const count = ns === null ? nsCounts.all : ns === "global" ? nsCounts.global : nsCounts.workspace;
461
+ return (0, import_react.createElement)("button", {
462
+ key: ns ?? "all",
476
463
  onClick: () => {
477
- void confirmAll();
464
+ setNamespace(ns);
478
465
  },
479
- style: { ...ssid.btn, padding: "1px 8px", fontSize: 10.5 }
480
- }, t("confirmAll")) : null,
481
- (0, import_react.createElement)("span", null, `${group.items.length}`)
482
- )
483
- ),
484
- group.items.map((record) => (0, import_react.createElement)(
466
+ style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", gap: 6, ...ssid.btn, ...namespace === ns ? { color: ssid.accent, borderColor: ssid.accent } : {} }
467
+ }, (0, import_react.createElement)("span", null, label), (0, import_react.createElement)("span", { style: ssid.count }, String(count)));
468
+ })
469
+ )
470
+ ),
471
+ // 内容区独立滚动:预览 + 分组列表。
472
+ (0, import_react.createElement)(
473
+ "div",
474
+ { style: ssid.content },
475
+ // 注入预览(开发者):self 自述 + 当前注入的记忆 + 上下文占用统计
476
+ previewOpen && preview !== null ? (0, import_react.createElement)(
485
477
  "div",
486
- { key: record.id, style: ssid.card },
487
- (0, import_react.createElement)("div", { style: ssid.text }, ContentWithRefs({ text: record.content })),
488
- // keywords 展示(0.3.5:设计约定 UI 显示 keywords)
489
- record.keywords !== void 0 && record.keywords.length > 0 ? (0, import_react.createElement)(
490
- "div",
491
- { style: { display: "flex", flexWrap: "wrap", gap: 4, marginTop: 6 } },
492
- record.keywords.map((keyword) => (0, import_react.createElement)("span", {
493
- key: keyword,
494
- style: {
495
- fontSize: 10,
496
- padding: "1px 7px",
497
- borderRadius: 8,
498
- background: "var(--dsw-alias-bg-module-platform, rgba(128,148,168,.14))",
499
- color: "var(--dsw-alias-label-secondary, #67748a)"
500
- }
501
- }, keyword))
502
- ) : null,
478
+ { style: { ...ssid.card, display: "flex", flexDirection: "column", gap: 6 } },
503
479
  (0, import_react.createElement)(
504
480
  "div",
505
- { style: { ...ssid.muted, marginTop: 6 } },
506
- `${record.namespace} \xB7 ${record.status === "approved" ? t("approved") : t("suggested")}${record.injected ? ` \xB7 ${t("groupInjected")}` : ""}`
481
+ { style: { ...ssid.muted, fontSize: 10.5, display: "flex", justifyContent: "space-between", alignItems: "center" } },
482
+ (0, import_react.createElement)("span", null, t("contextUsage")),
483
+ (0, import_react.createElement)("span", null, (() => {
484
+ const selfChars = preview.self.length;
485
+ const injectedChars = preview.injected.reduce((sum, record) => sum + record.content.length + record.keywords.join("").length, 0);
486
+ const total = selfChars + injectedChars;
487
+ const tokens = Math.ceil(total / 2);
488
+ return `${selfChars}+${injectedChars} \u5B57\u7B26 \u2248 ${tokens} token`;
489
+ })())
507
490
  ),
491
+ (0, import_react.createElement)("div", { style: { ...ssid.text, fontSize: 11, whiteSpace: "pre-wrap", wordBreak: "break-all" } }, preview.self),
492
+ preview.injected.length === 0 ? (0, import_react.createElement)("div", { style: ssid.muted }, t("empty")) : preview.injected.map((record) => (0, import_react.createElement)(
493
+ "div",
494
+ { key: record.id, style: { ...ssid.muted, fontSize: 11 } },
495
+ `- [memory:${record.id.slice(0, 8)}] ${record.content}`
496
+ ))
497
+ ) : null,
498
+ // 未选择工作区:工作区视图显示占位(0.3.4 工作区路由语义)
499
+ namespace === "workspace" && (props.cwd === void 0 || props.cwd === "") ? (0, import_react.createElement)("div", { style: ssid.empty }, t("noWorkspace")) : groups.length === 0 ? (0, import_react.createElement)("div", { style: ssid.empty }, t("empty")) : groups.map((group) => (0, import_react.createElement)(
500
+ "div",
501
+ { key: group.key, style: { display: "flex", flexDirection: "column", gap: 6 } },
508
502
  (0, import_react.createElement)(
509
503
  "div",
510
- { style: { display: "flex", gap: 6, marginTop: 8, alignItems: "center" } },
511
- (0, import_react.createElement)("button", {
512
- type: "button",
513
- title: record.status === "approved" ? t("injectSwitch") : t("approveFirst"),
514
- disabled: record.status !== "approved",
515
- onClick: () => {
516
- void toggleInjected(record);
517
- },
518
- style: {
519
- ...ssid.btn,
520
- ...record.injected ? { color: ssid.accent, borderColor: ssid.accent } : {},
521
- opacity: record.status !== "approved" ? 0.4 : 1,
522
- cursor: record.status !== "approved" ? "not-allowed" : "pointer"
523
- }
524
- }, record.injected ? `\u2713 ${t("injectSwitch")}` : t("injectSwitch")),
525
- record.status === "suggested" ? (0, import_react.createElement)("button", {
526
- style: ssid.btn,
527
- onClick: () => {
528
- void memoryApi.confirm(record.id, props.cwd).then(() => reload());
529
- }
530
- }, t("confirm")) : null,
531
- (0, import_react.createElement)("button", {
532
- style: ssid.btn,
533
- onClick: () => {
534
- void memoryApi.forget(record.id, props.cwd).then(() => reload());
535
- }
536
- }, t("forget"))
537
- )
504
+ { style: ssid.title },
505
+ (0, import_react.createElement)("span", null, group.label),
506
+ (0, import_react.createElement)(
507
+ "div",
508
+ { style: { display: "flex", alignItems: "center", gap: 6 } },
509
+ group.key === "pending" && group.items.length > 0 ? (0, import_react.createElement)("button", {
510
+ type: "button",
511
+ title: t("confirmAll"),
512
+ onClick: () => {
513
+ void confirmAll();
514
+ },
515
+ style: { ...ssid.btn, padding: "1px 8px", fontSize: 10.5 }
516
+ }, t("confirmAll")) : null,
517
+ (0, import_react.createElement)("span", { style: ssid.count }, String(group.items.length))
518
+ )
519
+ ),
520
+ group.items.map((record) => (0, import_react.createElement)(
521
+ "div",
522
+ { key: record.id, style: ssid.card },
523
+ (0, import_react.createElement)("div", { style: ssid.text }, ContentWithRefs({ text: record.content })),
524
+ // keywords 展示(0.3.5:设计约定 UI 显示 keywords)
525
+ record.keywords !== void 0 && record.keywords.length > 0 ? (0, import_react.createElement)(
526
+ "div",
527
+ { style: { display: "flex", flexWrap: "wrap", gap: 4, marginTop: 6 } },
528
+ record.keywords.map((keyword) => (0, import_react.createElement)("span", {
529
+ key: keyword,
530
+ style: {
531
+ fontSize: 10,
532
+ padding: "1px 7px",
533
+ borderRadius: 8,
534
+ background: "var(--dsw-alias-bg-module-platform, rgba(128,148,168,.14))",
535
+ color: "var(--dsw-alias-label-secondary, #67748a)"
536
+ }
537
+ }, keyword))
538
+ ) : null,
539
+ (0, import_react.createElement)(
540
+ "div",
541
+ { style: { ...ssid.muted, marginTop: 6 } },
542
+ `${record.namespace} \xB7 ${record.status === "approved" ? t("approved") : t("suggested")}${record.injected ? ` \xB7 ${t("groupInjected")}` : ""}`
543
+ ),
544
+ (0, import_react.createElement)(
545
+ "div",
546
+ { style: { display: "flex", gap: 6, marginTop: 8, alignItems: "center" } },
547
+ (0, import_react.createElement)("button", {
548
+ type: "button",
549
+ title: record.status === "approved" ? t("injectSwitch") : t("approveFirst"),
550
+ disabled: record.status !== "approved",
551
+ onClick: () => {
552
+ void toggleInjected(record);
553
+ },
554
+ style: {
555
+ ...ssid.btn,
556
+ ...record.injected ? { color: ssid.accent, borderColor: ssid.accent } : {},
557
+ opacity: record.status !== "approved" ? 0.4 : 1,
558
+ cursor: record.status !== "approved" ? "not-allowed" : "pointer"
559
+ }
560
+ }, record.injected ? `\u2713 ${t("injectSwitch")}` : t("injectSwitch")),
561
+ record.status === "suggested" ? (0, import_react.createElement)("button", {
562
+ style: ssid.btn,
563
+ onClick: () => {
564
+ void memoryApi.confirm(record.id, props.cwd).then(() => reload());
565
+ }
566
+ }, t("confirm")) : null,
567
+ (0, import_react.createElement)("button", {
568
+ style: ssid.btn,
569
+ onClick: () => {
570
+ void memoryApi.forget(record.id, props.cwd).then(() => reload());
571
+ }
572
+ }, t("forget"))
573
+ )
574
+ ))
538
575
  ))
539
- ))
576
+ )
540
577
  );
541
578
  }
542
579
  function currentSessionCwd(ctx) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@max-null/dsh-memory",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Cross-session plaintext memory plugin for the DeepSeek Harness — deterministic BM25 recall, human-owned, no embeddings",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -61,22 +61,22 @@
61
61
  },
62
62
  "peerDependencies": {
63
63
  "@deepseek-ai/cordis": "^4.0.1",
64
- "@deepseek-ai/dsh-storage": ">=0.0.1-rc.1",
65
- "@deepseek-ai/dsh-storage-domain": ">=0.0.1-rc.1",
66
- "@deepseek-ai/dsh-storage-json": ">=0.0.1-rc.1",
67
- "@deepseek-ai/dsh-system-prompt": ">=0.0.1-rc.1",
68
- "@deepseek-ai/dsh-tools": ">=0.0.1-rc.1",
69
- "@deepseek-ai/dsh-typert-protocol": ">=0.0.1-rc.5"
64
+ "@deepseek-ai/dsh-storage": "^0.1.1-rc.1",
65
+ "@deepseek-ai/dsh-storage-domain": "^0.1.1-rc.1",
66
+ "@deepseek-ai/dsh-storage-json": "^0.1.1-rc.1",
67
+ "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.1",
68
+ "@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
69
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.1"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@deepseek-ai/cordis": "^4.0.1",
73
73
  "@deepseek-ai/cordis-plugin-loader": "^1.0.2",
74
- "@deepseek-ai/dsh-storage": ">=0.0.1-rc.1",
75
- "@deepseek-ai/dsh-storage-domain": ">=0.0.1-rc.1",
76
- "@deepseek-ai/dsh-storage-json": ">=0.0.1-rc.1",
77
- "@deepseek-ai/dsh-system-prompt": ">=0.0.1-rc.1",
78
- "@deepseek-ai/dsh-tools": ">=0.0.1-rc.1",
79
- "@deepseek-ai/dsh-typert-protocol": ">=0.0.1-rc.5",
74
+ "@deepseek-ai/dsh-storage": "^0.1.1-rc.1",
75
+ "@deepseek-ai/dsh-storage-domain": "^0.1.1-rc.1",
76
+ "@deepseek-ai/dsh-storage-json": "^0.1.1-rc.1",
77
+ "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.1",
78
+ "@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
79
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.1",
80
80
  "@types/node": "^26.2.0",
81
81
  "tsx": "^4.0.0",
82
82
  "typescript": "^5.5.0",