@max-null/dsh-memory 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -101
- package/client.js +224 -105
- package/dist/anchors.d.ts +45 -0
- package/dist/anchors.js +45 -0
- package/dist/engine.d.ts +189 -8
- package/dist/engine.js +445 -17
- package/dist/index.js +32 -8
- package/dist/quarantine.d.ts +24 -0
- package/dist/quarantine.js +43 -0
- package/dist/query-log.d.ts +155 -0
- package/dist/query-log.js +220 -0
- package/dist/routes.js +2 -0
- package/dist/self.d.ts +1 -0
- package/dist/self.js +14 -10
- package/docs/shots/memory-settings-1.png +0 -0
- package/package.json +3 -4
package/client.js
CHANGED
|
@@ -94,11 +94,11 @@ var BRAIN_PATHS = [
|
|
|
94
94
|
];
|
|
95
95
|
function brainIcon() {
|
|
96
96
|
return (0, import_react.createElement)("svg", {
|
|
97
|
-
width:
|
|
98
|
-
height:
|
|
97
|
+
width: 22,
|
|
98
|
+
height: 22,
|
|
99
99
|
viewBox: "0 0 24 24",
|
|
100
100
|
fill: "none",
|
|
101
|
-
stroke: "
|
|
101
|
+
stroke: "#8b5cf6",
|
|
102
102
|
strokeWidth: 2,
|
|
103
103
|
strokeLinecap: "round",
|
|
104
104
|
strokeLinejoin: "round"
|
|
@@ -175,26 +175,34 @@ var STRINGS = {
|
|
|
175
175
|
promptFilterAll: "\u5168\u90E8",
|
|
176
176
|
memorySearch: "\u641C\u7D22\u8BB0\u5FC6\u2026",
|
|
177
177
|
empty: "\u9ED1\u6697\u4E2D\u672A\u89C1\u7075\u5149",
|
|
178
|
-
confirm: "\
|
|
178
|
+
confirm: "\u653E\u884C",
|
|
179
179
|
forget: "\u5220\u9664",
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
groupQuarantine: "\u9694\u79BB\u533A",
|
|
181
|
+
groupQuarantineHint: "\u547D\u4E2D\u5371\u9669\u5185\u5BB9\u89C4\u5219\uFF08\u5BC6\u94A5/\u51ED\u636E\u7C7B\uFF09\u2192 \u4E0D\u8FDB\u6CE8\u5165\u3001\u4E0D\u8FDB\u68C0\u7D22\u3002\u653E\u884C\u540E\u6309\u666E\u901A\u8BB0\u5FC6\u5904\u7406\u3002",
|
|
182
|
+
quarantineReason: "\u89C4\u5219\uFF1A{reason}",
|
|
183
|
+
release: "\u653E\u884C",
|
|
184
|
+
blockedWhileQuarantined: "\u9694\u79BB\u4E2D \xB7 \u653E\u884C\u540E\u53EF\u5E38\u9A7B\u6CE8\u5165",
|
|
185
|
+
groupPending: "\u5F85\u653E\u884C",
|
|
186
|
+
groupOnDemand: "\u6309\u9700\u68C0\u7D22",
|
|
182
187
|
groupInjected: "\u5E38\u9A7B\u6CE8\u5165",
|
|
188
|
+
groupCold: "\u51B7\u6570\u636E\xB7\u6298\u53E0\uFF08{days} \u5929\u672A\u68C0\u7D22\uFF09",
|
|
183
189
|
injectSwitch: "\u5E38\u9A7B\u6CE8\u5165",
|
|
184
|
-
approveFirst: "\
|
|
190
|
+
approveFirst: "\u5148\u653E\u884C\u9694\u79BB\u624D\u80FD\u5E38\u9A7B\u6CE8\u5165",
|
|
185
191
|
allNamespaces: "\u5168\u90E8",
|
|
186
192
|
nsGlobal: "\u5168\u5C40",
|
|
187
193
|
nsWorkspace: "\u5DE5\u4F5C\u533A",
|
|
188
194
|
noWorkspace: "\u672A\u9009\u62E9\u5DE5\u4F5C\u533A",
|
|
189
195
|
organizeMemory: "\u6574\u7406\u8BB0\u5FC6",
|
|
190
|
-
confirmAll: "\u5168\u90E8\
|
|
196
|
+
confirmAll: "\u5168\u90E8\u653E\u884C",
|
|
191
197
|
injectPreview: "\u6CE8\u5165\u9884\u89C8",
|
|
192
198
|
contextUsage: "\u4E0A\u4E0B\u6587\u5360\u7528",
|
|
193
199
|
keywordsLabel: "\u5173\u952E\u8BCD",
|
|
194
|
-
suggested: "\
|
|
195
|
-
approved: "\u5DF2\
|
|
200
|
+
suggested: "\u9694\u79BB",
|
|
201
|
+
approved: "\u5DF2\u751F\u6548",
|
|
196
202
|
refresh: "\u5237\u65B0",
|
|
197
|
-
coldHint: "\u51B7\u6570\u636E
|
|
203
|
+
coldHint: "\u51B7\u6570\u636E",
|
|
204
|
+
lastUsedAt: "\u4E0A\u641C",
|
|
205
|
+
hitCount: "\u547D\u4E2D {count} \u6B21"
|
|
198
206
|
},
|
|
199
207
|
en: {
|
|
200
208
|
tabMemory: "Memory",
|
|
@@ -220,26 +228,34 @@ var STRINGS = {
|
|
|
220
228
|
promptFilterAll: "All",
|
|
221
229
|
memorySearch: "Search memory\u2026",
|
|
222
230
|
empty: "No spark in the dark",
|
|
223
|
-
confirm: "
|
|
231
|
+
confirm: "Release",
|
|
224
232
|
forget: "Forget",
|
|
225
|
-
|
|
226
|
-
|
|
233
|
+
groupQuarantine: "Quarantine",
|
|
234
|
+
groupQuarantineHint: "Flagged by a dangerous-content rule (secrets/credentials) \u2192 kept out of injection and recall. Releasing it makes it an ordinary memory.",
|
|
235
|
+
quarantineReason: "Rule: {reason}",
|
|
236
|
+
release: "Release",
|
|
237
|
+
blockedWhileQuarantined: "Quarantined \xB7 release to allow injection",
|
|
238
|
+
groupPending: "Pending release",
|
|
239
|
+
groupOnDemand: "On demand",
|
|
227
240
|
groupInjected: "Always injected",
|
|
241
|
+
groupCold: "Cold \xB7 collapsed ({days} days untouched)",
|
|
228
242
|
injectSwitch: "Inject every turn",
|
|
229
|
-
approveFirst: "
|
|
243
|
+
approveFirst: "Release the quarantine first",
|
|
230
244
|
allNamespaces: "All",
|
|
231
245
|
nsGlobal: "Global",
|
|
232
246
|
nsWorkspace: "Workspace",
|
|
233
247
|
noWorkspace: "No workspace selected",
|
|
234
248
|
organizeMemory: "Organize memory",
|
|
235
|
-
confirmAll: "
|
|
249
|
+
confirmAll: "Release all",
|
|
236
250
|
injectPreview: "Injection preview",
|
|
237
251
|
contextUsage: "Context usage",
|
|
238
252
|
keywordsLabel: "Keywords",
|
|
239
|
-
suggested: "
|
|
240
|
-
approved: "
|
|
253
|
+
suggested: "Quarantined",
|
|
254
|
+
approved: "Active",
|
|
241
255
|
refresh: "Refresh",
|
|
242
|
-
coldHint: "Cold
|
|
256
|
+
coldHint: "Cold",
|
|
257
|
+
lastUsedAt: "Recalled",
|
|
258
|
+
hitCount: "{count} hits"
|
|
243
259
|
}
|
|
244
260
|
};
|
|
245
261
|
var localeId = "zh";
|
|
@@ -356,7 +372,7 @@ var memoryApi = {
|
|
|
356
372
|
return api("prompt.refresh", { ...cwd === void 0 ? {} : { cwd } });
|
|
357
373
|
}
|
|
358
374
|
};
|
|
359
|
-
var ORGANIZE_PROMPT = "\u8BF7\u6574\u7406\u6211\u7684\u8BB0\u5FC6\u5E93\uFF1A\u7528 memory_list \u67E5\u770B\u5168\u90E8\u8BB0\u5FC6\u3002\u6574\u7406\u89C4\u5219\uFF08\u5FC5\u987B\u6267\u884C\uFF09\uFF1A\u2460\u540C\u7C7B\u6761\u76EE\u5408\u5E76\u2014\u2014\u5DE5\u4F5C\u4E60\u60EF/\u7EA6\u5B9A\u7C7B\u591A\u6761\u5408\u5E76\u4E3A\u4E00\u6761\uFF08\u5185\u5BB9\u7528 \u2460\u2461\u2462 \u5E76\u5217\uFF0C\u907F\u514D\u788E\u7247\u5316\uFF09\uFF1B\u2461\u4E0E\u300C[\u8BB0\u5FC6\u7CFB\u7EDF\u81EA\u8FF0]\u300D\uFF08\u4F60\u4E0A\u4E0B\u6587\u4E2D\u7684\u8BB0\u5FC6\u673A\u5236\u8BF4\u660E\uFF09\u5185\u5BB9\u91CD\u590D\u7684\u63D2\u4EF6\u4ECB\u7ECD\u6761\u76EE\uFF08\u5982 dsh-memory \u63D2\u4EF6\u53D1\u5E03\u4FE1\u606F\uFF09\u5E94\u5220\u9664\u2014\u2014\u673A\u5236\u8BF4\u660E\u5DF2\u7531\u7CFB\u7EDF\u5E38\u9A7B\u63D0\u4F9B\uFF0C\u65E0\u9700\u7528\u6237\u5B58\u50A8\uFF1B\u2462\u5BF9\u8FC7\u65F6\u3001\u9519\u8BEF\u6216\u5DF2\u53D8\u5316\u7684\u5185\u5BB9\u7528 memory_update \u4FEE\u6B63\uFF08\
|
|
375
|
+
var ORGANIZE_PROMPT = "\u8BF7\u6574\u7406\u6211\u7684\u8BB0\u5FC6\u5E93\uFF1A\u7528 memory_list \u67E5\u770B\u5168\u90E8\u8BB0\u5FC6\u3002\u6574\u7406\u89C4\u5219\uFF08\u5FC5\u987B\u6267\u884C\uFF09\uFF1A\u2460\u540C\u7C7B\u6761\u76EE\u5408\u5E76\u2014\u2014\u5DE5\u4F5C\u4E60\u60EF/\u7EA6\u5B9A\u7C7B\u591A\u6761\u5408\u5E76\u4E3A\u4E00\u6761\uFF08\u5185\u5BB9\u7528 \u2460\u2461\u2462 \u5E76\u5217\uFF0C\u907F\u514D\u788E\u7247\u5316\uFF09\uFF1B\u2461\u4E0E\u300C[\u8BB0\u5FC6\u7CFB\u7EDF\u81EA\u8FF0]\u300D\uFF08\u4F60\u4E0A\u4E0B\u6587\u4E2D\u7684\u8BB0\u5FC6\u673A\u5236\u8BF4\u660E\uFF09\u5185\u5BB9\u91CD\u590D\u7684\u63D2\u4EF6\u4ECB\u7ECD\u6761\u76EE\uFF08\u5982 dsh-memory \u63D2\u4EF6\u53D1\u5E03\u4FE1\u606F\uFF09\u5E94\u5220\u9664\u2014\u2014\u673A\u5236\u8BF4\u660E\u5DF2\u7531\u7CFB\u7EDF\u5E38\u9A7B\u63D0\u4F9B\uFF0C\u65E0\u9700\u7528\u6237\u5B58\u50A8\uFF1B\u2462\u5BF9\u8FC7\u65F6\u3001\u9519\u8BEF\u6216\u5DF2\u53D8\u5316\u7684\u5185\u5BB9\u7528 memory_update \u4FEE\u6B63\uFF08\u6539\u52A8\u751F\u6548\u3001\u6CE8\u5165\u5F00\u5173\u4FDD\u7559\uFF09\uFF1B\u2463\u7CBE\u7B80\u5197\u957F\u5185\u5BB9\uFF0C\u4E3A\u6BCF\u6761\u8865\u5145\u6216\u4FEE\u6B63 keywords\uFF1B\u2464\u9700\u8981\u5220\u9664\u7684\u7528 memory_forget\uFF0C\u9700\u8981\u65B0\u589E\u7684\u7528 memory_save\u3002\u5224\u65AD\u5185\u5BB9\u662F\u5426\u8FC7\u65F6\u7684\u65B9\u6CD5\uFF1A\u628A\u8BB0\u5FC6\u91CC\u63D0\u5230\u7684\u5DE5\u5177\u540D/\u6570\u91CF\u4E0E\u4F60\u5F53\u524D\u5B9E\u9645\u53EF\u7528\u7684\u8BB0\u5FC6\u5DE5\u5177\u5BF9\u7167\u2014\u2014\u4F60\u5F53\u524D\u53EF\u7528\uFF1Amemory_save / memory_list / memory_search / memory_confirm / memory_forget / memory_update\uFF08\u5171 6 \u4E2A\uFF09\uFF1B\u82E5\u8BB0\u5FC6\u4E2D\u7684\u5DE5\u5177\u5217\u8868\u3001\u6570\u91CF\u3001\u6D41\u7A0B\u4E0E\u6B64\u4E0D\u7B26\u5373\u4E3A\u8FC7\u65F6\uFF0C\u7528 memory_update \u4FEE\u6B63\u3002\u6539\u52A8\u76F4\u63A5\u751F\u6548\uFF08\u4E0D\u8981\u8C03\u7528 memory_confirm\uFF09\uFF0C\u5B8C\u6210\u540E\u7528\u4E00\u53E5\u8BDD\u6C47\u62A5\u6574\u7406\u7ED3\u679C\u3002";
|
|
360
376
|
function insertIntoInput(ctx, text) {
|
|
361
377
|
const sessions = ctx.get?.("sessions");
|
|
362
378
|
const conversation = ctx.get?.("conversation");
|
|
@@ -585,6 +601,9 @@ function MemoryView(props) {
|
|
|
585
601
|
const t = useT();
|
|
586
602
|
const [mode, setMode] = (0, import_react.useState)("mem");
|
|
587
603
|
const [records, setRecords] = (0, import_react.useState)([]);
|
|
604
|
+
const [quarantined, setQuarantined] = (0, import_react.useState)([]);
|
|
605
|
+
const [coldOpen, setColdOpen] = (0, import_react.useState)(false);
|
|
606
|
+
const [quarantineOpen, setQuarantineOpen] = (0, import_react.useState)(true);
|
|
588
607
|
const [query, setQuery] = (0, import_react.useState)("");
|
|
589
608
|
const [namespace, setNamespace] = (0, import_react.useState)(null);
|
|
590
609
|
const [refreshing, setRefreshing] = (0, import_react.useState)(false);
|
|
@@ -608,11 +627,16 @@ function MemoryView(props) {
|
|
|
608
627
|
setRecords(await memoryApi.list({}, props.cwd));
|
|
609
628
|
} catch {
|
|
610
629
|
}
|
|
630
|
+
try {
|
|
631
|
+
setQuarantined(await memoryApi.list({ quarantined: true }, props.cwd));
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
611
634
|
};
|
|
612
635
|
const refreshFromDisk = async () => {
|
|
613
636
|
setRefreshing(true);
|
|
614
637
|
try {
|
|
615
638
|
setRecords(await memoryApi.reload(props.cwd));
|
|
639
|
+
setQuarantined(await memoryApi.list({ quarantined: true }, props.cwd));
|
|
616
640
|
} catch {
|
|
617
641
|
await reload();
|
|
618
642
|
} finally {
|
|
@@ -623,15 +647,22 @@ function MemoryView(props) {
|
|
|
623
647
|
if (props.visible) void reload();
|
|
624
648
|
}, [props.visible]);
|
|
625
649
|
const toggleInjected = async (record) => {
|
|
626
|
-
if (record.status !== "approved") return;
|
|
650
|
+
if (record.status !== "approved" || record.quarantined) return;
|
|
627
651
|
try {
|
|
628
652
|
await memoryApi.setInjected(record.id, !record.injected, props.cwd);
|
|
629
653
|
} catch {
|
|
630
654
|
}
|
|
631
655
|
await reload();
|
|
632
656
|
};
|
|
657
|
+
const release = async (record) => {
|
|
658
|
+
try {
|
|
659
|
+
await memoryApi.confirm(record.id, props.cwd);
|
|
660
|
+
} catch {
|
|
661
|
+
}
|
|
662
|
+
await reload();
|
|
663
|
+
};
|
|
633
664
|
const confirmAll = async () => {
|
|
634
|
-
const pending = records.filter((record) => record.status === "suggested");
|
|
665
|
+
const pending = records.filter((record) => record.status === "suggested" && !record.quarantined);
|
|
635
666
|
if (pending.length === 0) return;
|
|
636
667
|
await Promise.all(pending.map((record) => memoryApi.confirm(record.id, props.cwd).catch(() => null)));
|
|
637
668
|
await reload();
|
|
@@ -671,16 +702,180 @@ function MemoryView(props) {
|
|
|
671
702
|
const q = query.trim().toLowerCase();
|
|
672
703
|
const byNs = namespace === null ? records : namespace === "workspace" ? records.filter((record) => record.namespace === "project") : records.filter((record) => record.namespace === "global");
|
|
673
704
|
const filtered = byNs.filter((record) => q === "" || record.content.toLowerCase().includes(q));
|
|
705
|
+
const quarantinedScoped = (namespace === null ? quarantined : namespace === "workspace" ? quarantined.filter((record) => record.namespace === "project") : quarantined.filter((record) => record.namespace === "global")).filter((record) => q === "" || record.content.toLowerCase().includes(q));
|
|
674
706
|
const nsCounts = {
|
|
675
|
-
all: records.length,
|
|
676
|
-
global: records.filter((record) => record.namespace === "global").length,
|
|
677
|
-
workspace: records.filter((record) => record.namespace === "project").length
|
|
707
|
+
all: records.length + quarantined.length,
|
|
708
|
+
global: records.filter((record) => record.namespace === "global").length + quarantined.filter((record) => record.namespace === "global").length,
|
|
709
|
+
workspace: records.filter((record) => record.namespace === "project").length + quarantined.filter((record) => record.namespace === "project").length
|
|
678
710
|
};
|
|
711
|
+
const coldNow = Date.now();
|
|
712
|
+
const settled = (excludeCold) => excludeCold ? filtered.filter((record) => !isCold(record.lastUsedAt, coldNow)) : filtered;
|
|
679
713
|
const groups = [
|
|
680
|
-
{ key: "pending", label: t("groupPending"), items:
|
|
681
|
-
{ key: "
|
|
682
|
-
{ key: "
|
|
714
|
+
{ key: "pending", label: t("groupPending"), items: settled(false).filter((record) => record.status === "suggested") },
|
|
715
|
+
{ key: "injected", label: t("groupInjected"), items: settled(true).filter((record) => record.status === "approved" && record.injected) },
|
|
716
|
+
{ key: "ondemand", label: t("groupOnDemand"), items: settled(true).filter((record) => record.status === "approved" && !record.injected) }
|
|
683
717
|
].filter((group) => group.items.length > 0);
|
|
718
|
+
const coldItems = settled(false).filter((record) => record.status === "approved" && isCold(record.lastUsedAt, coldNow));
|
|
719
|
+
const memoryCard = (record, options = {}) => {
|
|
720
|
+
const isQuarantined = options.quarantined === true || record.quarantined;
|
|
721
|
+
const injectable = record.status === "approved" && !isQuarantined;
|
|
722
|
+
return (0, import_react.createElement)(
|
|
723
|
+
"div",
|
|
724
|
+
{
|
|
725
|
+
key: record.id,
|
|
726
|
+
style: {
|
|
727
|
+
...ssid.card,
|
|
728
|
+
// 隔离区显眼但不吓人:左边一条警示色,不换底色(安全网,不是错误)
|
|
729
|
+
...isQuarantined ? { borderLeft: "3px solid var(--dsw-alias-state-warning-primary, #f7c94f)" } : {}
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
(0, import_react.createElement)("div", { style: ssid.text }, ContentWithRefs({ text: record.content })),
|
|
733
|
+
// 隔离原因:命中的规则名(安全网,只陈述事实,不做道德评判)
|
|
734
|
+
isQuarantined ? (0, import_react.createElement)(
|
|
735
|
+
"div",
|
|
736
|
+
{ style: { ...ssid.muted, marginTop: 6, display: "flex", flexWrap: "wrap", gap: 4, alignItems: "center" } },
|
|
737
|
+
(0, import_react.createElement)("span", {
|
|
738
|
+
style: {
|
|
739
|
+
fontSize: 10,
|
|
740
|
+
padding: "1px 7px",
|
|
741
|
+
borderRadius: 8,
|
|
742
|
+
fontWeight: 600,
|
|
743
|
+
background: "var(--dsw-alias-state-warning-tertiary, rgba(247,201,79,.16))",
|
|
744
|
+
color: "var(--dsw-alias-state-warning-primary, #f7c94f)"
|
|
745
|
+
}
|
|
746
|
+
}, t("groupQuarantine")),
|
|
747
|
+
(0, import_react.createElement)("span", null, record.quarantineReason === void 0 || record.quarantineReason === "" ? t("quarantineReason", { reason: "\u2014" }) : t("quarantineReason", { reason: record.quarantineReason }))
|
|
748
|
+
) : null,
|
|
749
|
+
// keywords 展示(0.3.5:设计约定 UI 显示 keywords)
|
|
750
|
+
record.keywords !== void 0 && record.keywords.length > 0 ? (0, import_react.createElement)(
|
|
751
|
+
"div",
|
|
752
|
+
{ style: { display: "flex", flexWrap: "wrap", gap: 4, marginTop: 6 } },
|
|
753
|
+
record.keywords.map((keyword) => (0, import_react.createElement)("span", {
|
|
754
|
+
key: keyword,
|
|
755
|
+
style: {
|
|
756
|
+
fontSize: 10,
|
|
757
|
+
padding: "1px 7px",
|
|
758
|
+
borderRadius: 8,
|
|
759
|
+
background: "var(--dsw-alias-bg-module-platform, rgba(128,148,168,.14))",
|
|
760
|
+
color: "var(--dsw-alias-label-secondary, #67748a)"
|
|
761
|
+
}
|
|
762
|
+
}, keyword))
|
|
763
|
+
) : null,
|
|
764
|
+
(0, import_react.createElement)(
|
|
765
|
+
"div",
|
|
766
|
+
{ style: { ...ssid.muted, marginTop: 6 } },
|
|
767
|
+
`${record.namespace} \xB7 ${record.status === "approved" ? t("approved") : t("suggested")}${record.injected ? ` \xB7 ${t("groupInjected")}` : ""}${record.lastUsedAt !== void 0 ? ` \xB7 ${t("lastUsedAt")} ${fmtDate(record.lastUsedAt)}` : ""}${isCold(record.lastUsedAt, coldNow) ? ` \xB7 ${t("coldHint")}` : ""}${record.hitCount > 0 ? ` \xB7 ${t("hitCount", { count: record.hitCount })}` : ""}`
|
|
768
|
+
),
|
|
769
|
+
(0, import_react.createElement)(
|
|
770
|
+
"div",
|
|
771
|
+
{ style: { display: "flex", gap: 6, marginTop: 8, alignItems: "center" } },
|
|
772
|
+
(0, import_react.createElement)("button", {
|
|
773
|
+
type: "button",
|
|
774
|
+
title: isQuarantined ? t("blockedWhileQuarantined") : record.status === "approved" ? t("injectSwitch") : t("approveFirst"),
|
|
775
|
+
disabled: !injectable,
|
|
776
|
+
onClick: () => {
|
|
777
|
+
void toggleInjected(record);
|
|
778
|
+
},
|
|
779
|
+
style: {
|
|
780
|
+
...ssid.btn,
|
|
781
|
+
...record.injected ? { color: ssid.accent, borderColor: ssid.accent } : {},
|
|
782
|
+
opacity: injectable ? 1 : 0.4,
|
|
783
|
+
cursor: injectable ? "pointer" : "not-allowed"
|
|
784
|
+
}
|
|
785
|
+
}, record.injected ? `\u2713 ${t("injectSwitch")}` : t("injectSwitch")),
|
|
786
|
+
isQuarantined ? (0, import_react.createElement)("button", {
|
|
787
|
+
style: { ...ssid.btn, color: ssid.accent, borderColor: ssid.accent },
|
|
788
|
+
onClick: () => {
|
|
789
|
+
void release(record);
|
|
790
|
+
}
|
|
791
|
+
}, t("release")) : null,
|
|
792
|
+
record.status === "suggested" && !isQuarantined ? (0, import_react.createElement)("button", {
|
|
793
|
+
style: ssid.btn,
|
|
794
|
+
onClick: () => {
|
|
795
|
+
void memoryApi.confirm(record.id, props.cwd).then(() => reload());
|
|
796
|
+
}
|
|
797
|
+
}, t("confirm")) : null,
|
|
798
|
+
(0, import_react.createElement)("button", {
|
|
799
|
+
style: ssid.btn,
|
|
800
|
+
onClick: () => {
|
|
801
|
+
void memoryApi.forget(record.id, props.cwd).then(() => reload());
|
|
802
|
+
}
|
|
803
|
+
}, t("forget"))
|
|
804
|
+
)
|
|
805
|
+
);
|
|
806
|
+
};
|
|
807
|
+
const foldHeader = (label, count, open, toggle) => (0, import_react.createElement)(
|
|
808
|
+
"div",
|
|
809
|
+
{
|
|
810
|
+
style: { ...ssid.title, cursor: "pointer", marginBottom: 0 },
|
|
811
|
+
onClick: toggle,
|
|
812
|
+
onKeyDown: (event) => {
|
|
813
|
+
if (event.key === "Enter" || event.key === " ") toggle();
|
|
814
|
+
},
|
|
815
|
+
role: "button",
|
|
816
|
+
tabIndex: 0
|
|
817
|
+
},
|
|
818
|
+
(0, import_react.createElement)("span", null, `${open ? "\u25BE" : "\u25B8"} ${label}`),
|
|
819
|
+
(0, import_react.createElement)("span", { style: ssid.count }, String(count))
|
|
820
|
+
);
|
|
821
|
+
const quarantineSection = quarantinedScoped.length === 0 ? null : (0, import_react.createElement)(
|
|
822
|
+
"div",
|
|
823
|
+
{ key: "quarantine", style: { display: "flex", flexDirection: "column", gap: 6 } },
|
|
824
|
+
foldHeader(t("groupQuarantine"), quarantinedScoped.length, quarantineOpen, () => {
|
|
825
|
+
setQuarantineOpen((open) => !open);
|
|
826
|
+
}),
|
|
827
|
+
(0, import_react.createElement)("div", { style: { ...ssid.muted, fontSize: 10.5 } }, t("groupQuarantineHint")),
|
|
828
|
+
quarantineOpen ? (0, import_react.createElement)(
|
|
829
|
+
"div",
|
|
830
|
+
{ style: { display: "flex", flexDirection: "column", gap: 6 } },
|
|
831
|
+
quarantinedScoped.map((record) => memoryCard(record, { quarantined: true }))
|
|
832
|
+
) : null
|
|
833
|
+
);
|
|
834
|
+
const coldSection = coldItems.length === 0 ? null : (0, import_react.createElement)(
|
|
835
|
+
"div",
|
|
836
|
+
{ key: "cold", style: { display: "flex", flexDirection: "column", gap: 6 } },
|
|
837
|
+
foldHeader(t("groupCold", { days: COLD_DAYS }), coldItems.length, coldOpen, () => {
|
|
838
|
+
setColdOpen((open) => !open);
|
|
839
|
+
}),
|
|
840
|
+
coldOpen ? (0, import_react.createElement)(
|
|
841
|
+
"div",
|
|
842
|
+
{ style: { display: "flex", flexDirection: "column", gap: 6 } },
|
|
843
|
+
coldItems.map((record) => memoryCard(record))
|
|
844
|
+
) : null
|
|
845
|
+
);
|
|
846
|
+
const groupSections = groups.map((group) => (0, import_react.createElement)(
|
|
847
|
+
"div",
|
|
848
|
+
{
|
|
849
|
+
key: group.key,
|
|
850
|
+
style: { display: "flex", flexDirection: "column", gap: 6 }
|
|
851
|
+
},
|
|
852
|
+
(0, import_react.createElement)(
|
|
853
|
+
"div",
|
|
854
|
+
{ style: ssid.title },
|
|
855
|
+
(0, import_react.createElement)("span", null, group.label),
|
|
856
|
+
(0, import_react.createElement)(
|
|
857
|
+
"div",
|
|
858
|
+
{ style: { display: "flex", alignItems: "center", gap: 6 } },
|
|
859
|
+
group.key === "pending" && group.items.length > 0 ? (0, import_react.createElement)("button", {
|
|
860
|
+
type: "button",
|
|
861
|
+
title: t("confirmAll"),
|
|
862
|
+
onClick: () => {
|
|
863
|
+
void confirmAll();
|
|
864
|
+
},
|
|
865
|
+
style: { ...ssid.btn, padding: "1px 8px", fontSize: 10.5 }
|
|
866
|
+
}, t("confirmAll")) : null,
|
|
867
|
+
(0, import_react.createElement)("span", { style: ssid.count }, String(group.items.length))
|
|
868
|
+
)
|
|
869
|
+
),
|
|
870
|
+
group.items.map((record) => memoryCard(record))
|
|
871
|
+
));
|
|
872
|
+
const listSection = quarantineSection === null && groupSections.length === 0 && coldSection === null ? (0, import_react.createElement)("div", { key: "empty", style: ssid.empty }, t("empty")) : (0, import_react.createElement)(
|
|
873
|
+
"div",
|
|
874
|
+
{ key: "groups", style: { display: "flex", flexDirection: "column", gap: 12 } },
|
|
875
|
+
quarantineSection,
|
|
876
|
+
groupSections,
|
|
877
|
+
coldSection
|
|
878
|
+
);
|
|
684
879
|
const modeTabs = (0, import_react.createElement)(
|
|
685
880
|
"div",
|
|
686
881
|
{ style: { display: "flex", gap: 4 } },
|
|
@@ -799,83 +994,7 @@ function MemoryView(props) {
|
|
|
799
994
|
preview.lines.length === 0 ? (0, import_react.createElement)("div", { style: ssid.muted }, t("empty")) : preview.lines.map((line, index) => (0, import_react.createElement)("div", { key: index, style: { ...ssid.muted, fontSize: 11 } }, line))
|
|
800
995
|
) : null,
|
|
801
996
|
// 未选择工作区:工作区视图显示占位(0.3.4 工作区路由语义)
|
|
802
|
-
namespace === "workspace" && (props.cwd === void 0 || props.cwd === "") ? (0, import_react.createElement)("div", { style: ssid.empty }, t("noWorkspace")) :
|
|
803
|
-
"div",
|
|
804
|
-
{ key: group.key, style: { display: "flex", flexDirection: "column", gap: 6 } },
|
|
805
|
-
(0, import_react.createElement)(
|
|
806
|
-
"div",
|
|
807
|
-
{ style: ssid.title },
|
|
808
|
-
(0, import_react.createElement)("span", null, group.label),
|
|
809
|
-
(0, import_react.createElement)(
|
|
810
|
-
"div",
|
|
811
|
-
{ style: { display: "flex", alignItems: "center", gap: 6 } },
|
|
812
|
-
group.key === "pending" && group.items.length > 0 ? (0, import_react.createElement)("button", {
|
|
813
|
-
type: "button",
|
|
814
|
-
title: t("confirmAll"),
|
|
815
|
-
onClick: () => {
|
|
816
|
-
void confirmAll();
|
|
817
|
-
},
|
|
818
|
-
style: { ...ssid.btn, padding: "1px 8px", fontSize: 10.5 }
|
|
819
|
-
}, t("confirmAll")) : null,
|
|
820
|
-
(0, import_react.createElement)("span", { style: ssid.count }, String(group.items.length))
|
|
821
|
-
)
|
|
822
|
-
),
|
|
823
|
-
group.items.map((record) => (0, import_react.createElement)(
|
|
824
|
-
"div",
|
|
825
|
-
{ key: record.id, style: ssid.card },
|
|
826
|
-
(0, import_react.createElement)("div", { style: ssid.text }, ContentWithRefs({ text: record.content })),
|
|
827
|
-
// keywords 展示(0.3.5:设计约定 UI 显示 keywords)
|
|
828
|
-
record.keywords !== void 0 && record.keywords.length > 0 ? (0, import_react.createElement)(
|
|
829
|
-
"div",
|
|
830
|
-
{ style: { display: "flex", flexWrap: "wrap", gap: 4, marginTop: 6 } },
|
|
831
|
-
record.keywords.map((keyword) => (0, import_react.createElement)("span", {
|
|
832
|
-
key: keyword,
|
|
833
|
-
style: {
|
|
834
|
-
fontSize: 10,
|
|
835
|
-
padding: "1px 7px",
|
|
836
|
-
borderRadius: 8,
|
|
837
|
-
background: "var(--dsw-alias-bg-module-platform, rgba(128,148,168,.14))",
|
|
838
|
-
color: "var(--dsw-alias-label-secondary, #67748a)"
|
|
839
|
-
}
|
|
840
|
-
}, keyword))
|
|
841
|
-
) : null,
|
|
842
|
-
(0, import_react.createElement)(
|
|
843
|
-
"div",
|
|
844
|
-
{ style: { ...ssid.muted, marginTop: 6 } },
|
|
845
|
-
`${record.namespace} \xB7 ${record.status === "approved" ? t("approved") : t("suggested")}${record.injected ? ` \xB7 ${t("groupInjected")}` : ""}${record.lastUsedAt !== void 0 ? ` \xB7 \u4E0A\u641C ${fmtDate(record.lastUsedAt)}` : ""}${isCold(record.lastUsedAt) ? ` \xB7 ${t("coldHint")}` : ""}`
|
|
846
|
-
),
|
|
847
|
-
(0, import_react.createElement)(
|
|
848
|
-
"div",
|
|
849
|
-
{ style: { display: "flex", gap: 6, marginTop: 8, alignItems: "center" } },
|
|
850
|
-
(0, import_react.createElement)("button", {
|
|
851
|
-
type: "button",
|
|
852
|
-
title: record.status === "approved" ? t("injectSwitch") : t("approveFirst"),
|
|
853
|
-
disabled: record.status !== "approved",
|
|
854
|
-
onClick: () => {
|
|
855
|
-
void toggleInjected(record);
|
|
856
|
-
},
|
|
857
|
-
style: {
|
|
858
|
-
...ssid.btn,
|
|
859
|
-
...record.injected ? { color: ssid.accent, borderColor: ssid.accent } : {},
|
|
860
|
-
opacity: record.status !== "approved" ? 0.4 : 1,
|
|
861
|
-
cursor: record.status !== "approved" ? "not-allowed" : "pointer"
|
|
862
|
-
}
|
|
863
|
-
}, record.injected ? `\u2713 ${t("injectSwitch")}` : t("injectSwitch")),
|
|
864
|
-
record.status === "suggested" ? (0, import_react.createElement)("button", {
|
|
865
|
-
style: ssid.btn,
|
|
866
|
-
onClick: () => {
|
|
867
|
-
void memoryApi.confirm(record.id, props.cwd).then(() => reload());
|
|
868
|
-
}
|
|
869
|
-
}, t("confirm")) : null,
|
|
870
|
-
(0, import_react.createElement)("button", {
|
|
871
|
-
style: ssid.btn,
|
|
872
|
-
onClick: () => {
|
|
873
|
-
void memoryApi.forget(record.id, props.cwd).then(() => reload());
|
|
874
|
-
}
|
|
875
|
-
}, t("forget"))
|
|
876
|
-
)
|
|
877
|
-
))
|
|
878
|
-
))
|
|
997
|
+
namespace === "workspace" && (props.cwd === void 0 || props.cwd === "") ? (0, import_react.createElement)("div", { style: ssid.empty }, t("noWorkspace")) : listSection
|
|
879
998
|
)
|
|
880
999
|
);
|
|
881
1000
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 有效性锚点(2026-09-15 静默记忆机制):把一条记忆绑定到某个**可探测的环境值**上,
|
|
3
|
+
* 值变了就自动失效——解决「版本特性类记忆随环境升级静默变错」的问题。
|
|
4
|
+
*
|
|
5
|
+
* 第一版只做**进程内可探测**的类型:校验跑在会话启动的路径上,探测必须零 IO。
|
|
6
|
+
* 需要跑子进程才能拿到的(如 PowerShell 版本)留待后续——那要先解决「插件直接
|
|
7
|
+
* spawn 会绕过 DSH 的 shell 沙箱」的合规问题,不能顺手开这个口子。
|
|
8
|
+
*/
|
|
9
|
+
/** 受控词汇表:写入时只能从这三种里选,不能自创(自创的锚点无法机械校验)。 */
|
|
10
|
+
export type AnchorKind = 'env' | 'tool-list' | 'self-version';
|
|
11
|
+
/** 一条记忆声明的锚点。 */
|
|
12
|
+
export interface MemoryAnchor {
|
|
13
|
+
kind: AnchorKind;
|
|
14
|
+
/** `env` 类型时的变量名;其余类型省略。 */
|
|
15
|
+
name?: string;
|
|
16
|
+
/** 写入时探测到的值。 */
|
|
17
|
+
value: string;
|
|
18
|
+
}
|
|
19
|
+
/** 探测上下文:由调用方提供,便于测试注入与缓存。 */
|
|
20
|
+
export interface AnchorProbes {
|
|
21
|
+
/**
|
|
22
|
+
* 当前可用工具的清单;**拿不到时必须是 `undefined` 而不是空数组**——空数组会被判成
|
|
23
|
+
* 「工具全没了」,让所有 `tool-list` 锚点误失效。
|
|
24
|
+
*/
|
|
25
|
+
toolNames?: readonly string[];
|
|
26
|
+
/** dsh-memory 自身版本。 */
|
|
27
|
+
selfVersion: string;
|
|
28
|
+
/** 环境变量读取器;缺省 `process.env`。 */
|
|
29
|
+
env?: (name: string) => string | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 探测某个锚点当前的取值。
|
|
33
|
+
*
|
|
34
|
+
* @param anchor - 记忆声明的锚点。
|
|
35
|
+
* @param probes - 当前环境的探测上下文。
|
|
36
|
+
* @returns 当前值;**无法探测时返回 `undefined`**(调用方按「未校验」处理,不能判失效)。
|
|
37
|
+
*/
|
|
38
|
+
export declare function probeAnchor(anchor: MemoryAnchor, probes: AnchorProbes): string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* 锚点是否仍然成立。
|
|
41
|
+
*
|
|
42
|
+
* @returns `true` 成立;`false` 失效;`undefined` **未校验**(探测不到,不判失效——探测
|
|
43
|
+
* 失败与「值变了」是两件事,混起来会误伤)。
|
|
44
|
+
*/
|
|
45
|
+
export declare function anchorHolds(anchor: MemoryAnchor, probes: AnchorProbes): boolean | undefined;
|
package/dist/anchors.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 有效性锚点(2026-09-15 静默记忆机制):把一条记忆绑定到某个**可探测的环境值**上,
|
|
3
|
+
* 值变了就自动失效——解决「版本特性类记忆随环境升级静默变错」的问题。
|
|
4
|
+
*
|
|
5
|
+
* 第一版只做**进程内可探测**的类型:校验跑在会话启动的路径上,探测必须零 IO。
|
|
6
|
+
* 需要跑子进程才能拿到的(如 PowerShell 版本)留待后续——那要先解决「插件直接
|
|
7
|
+
* spawn 会绕过 DSH 的 shell 沙箱」的合规问题,不能顺手开这个口子。
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* 探测某个锚点当前的取值。
|
|
11
|
+
*
|
|
12
|
+
* @param anchor - 记忆声明的锚点。
|
|
13
|
+
* @param probes - 当前环境的探测上下文。
|
|
14
|
+
* @returns 当前值;**无法探测时返回 `undefined`**(调用方按「未校验」处理,不能判失效)。
|
|
15
|
+
*/
|
|
16
|
+
export function probeAnchor(anchor, probes) {
|
|
17
|
+
switch (anchor.kind) {
|
|
18
|
+
case 'env': {
|
|
19
|
+
if (anchor.name === undefined || anchor.name === '')
|
|
20
|
+
return undefined;
|
|
21
|
+
const read = probes.env ?? ((name) => process.env[name]);
|
|
22
|
+
return read(anchor.name);
|
|
23
|
+
}
|
|
24
|
+
case 'tool-list':
|
|
25
|
+
if (probes.toolNames === undefined)
|
|
26
|
+
return undefined;
|
|
27
|
+
return [...probes.toolNames].sort().join(',');
|
|
28
|
+
case 'self-version':
|
|
29
|
+
return probes.selfVersion;
|
|
30
|
+
default:
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 锚点是否仍然成立。
|
|
36
|
+
*
|
|
37
|
+
* @returns `true` 成立;`false` 失效;`undefined` **未校验**(探测不到,不判失效——探测
|
|
38
|
+
* 失败与「值变了」是两件事,混起来会误伤)。
|
|
39
|
+
*/
|
|
40
|
+
export function anchorHolds(anchor, probes) {
|
|
41
|
+
const current = probeAnchor(anchor, probes);
|
|
42
|
+
if (current === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
return current === anchor.value;
|
|
45
|
+
}
|