@max-null/dsh-memory 0.7.3 → 0.9.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 +6 -1
- package/client.js +20 -4
- package/dist/engine.d.ts +26 -9
- package/dist/engine.js +34 -15
- package/dist/index.js +17 -5
- package/dist/injection.d.ts +19 -2
- package/dist/injection.js +19 -2
- package/dist/remote.d.ts +6 -1
- package/dist/remote.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,15 +74,20 @@ md 文件是唯一事实源(`~/.dsh/prompt-library/*.md` 为 global;`<worksp
|
|
|
74
74
|
|
|
75
75
|
```
|
|
76
76
|
模型 memory_save → status: approved,立即生效;命中密钥/凭据规则则隔离(不进检索也不进注入)
|
|
77
|
+
给 injected 则同时钉住常驻(0.8.0),不吃下面两条自动规则
|
|
77
78
|
memory_search → 关键词/语义召回(只搜记忆;模板走 prompt_search 通道,不会混进候选池)
|
|
78
79
|
命中累计 2 次 → injected: true(自动打开常驻:global + 当前会话工作区,摘要化 + 预算截断)
|
|
79
|
-
30 天未再命中 →
|
|
80
|
+
30 天未再命中 → 自动撤下常驻(只撤自动开的;人工或模型显式动过的开关双向豁免)
|
|
80
81
|
人(面板 / 开关) → 钉住 / 删除 / 放行隔离记录 / 回滚
|
|
81
82
|
memory_forget → 随时删除(删除始终是人的动作)
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
两条自动规则撑起淘汰机制:**反复被检索命中**是它值得每轮付费的证据,**长期不再被命中**则自动退出常驻。两者都不删除任何内容——记忆不会无限累积,也不会被系统自行清空。
|
|
85
86
|
|
|
87
|
+
自动规则对**低频但关键**的记忆(长期约定、判据、委托)够不着:它们不会被反复检索,够不到阈值;勉强够到也会被 30 天撤下。这类走显式路径——在 `memory_save` / `memory_update` 里给 `injected` 即钉住,写的是与面板开关同一套语义(`injectedAuto: false`),此后不受两条自动规则影响。未审核与已隔离的记录不接受注入。
|
|
88
|
+
|
|
89
|
+
注入有**字符预算**(默认 1500,config `injectionBudget`):装不下的条目**整条丢弃**(不截内容),按「最近更新优先」取舍——所以 `injected: true` 不等于「每轮真的在场」,它只保证有资格排队。0.9.0 起这种出局不再无声:注入末尾会追加一行 `(另有 N 条常驻因预算未注入:id1, id2, …)`,**清理干净即自行消失**;面板的注入预览里也能展开看明细。诊断行本身不占预算。
|
|
90
|
+
|
|
86
91
|
记忆还可声明**有效性锚点**(环境变量 / 工具清单 / 插件版本):所绑的值变化后,这条记忆被标记 `stale` 并撤下常驻,检索结果里也带出失效提示——环境变了,旧结论就不再被当成仍然正确。
|
|
87
92
|
|
|
88
93
|
模型写入不需要人点头,但**人始终能看见并推翻**:面板按「隔离区 / 常驻 / 冷数据」分组,被隔离的记录必须由人放行才会重新进入检索。
|
package/client.js
CHANGED
|
@@ -196,6 +196,9 @@ var STRINGS = {
|
|
|
196
196
|
confirmAll: "\u5168\u90E8\u653E\u884C",
|
|
197
197
|
injectPreview: "\u6CE8\u5165\u9884\u89C8",
|
|
198
198
|
contextUsage: "\u4E0A\u4E0B\u6587\u5360\u7528",
|
|
199
|
+
injectionOmitted: "{count} \u6761\u5E38\u9A7B\u8BB0\u5FC6\u672A\u6CE8\u5165\uFF08\u5C55\u5F00\u67E5\u770B\uFF09",
|
|
200
|
+
budgetLabel: "\u9884\u7B97",
|
|
201
|
+
contextUsageDetail: "{self}+{injected} \u5B57\u7B26{budget} \u2248 {tokens} token",
|
|
199
202
|
keywordsLabel: "\u5173\u952E\u8BCD",
|
|
200
203
|
suggested: "\u9694\u79BB",
|
|
201
204
|
approved: "\u5DF2\u751F\u6548",
|
|
@@ -249,6 +252,9 @@ var STRINGS = {
|
|
|
249
252
|
confirmAll: "Release all",
|
|
250
253
|
injectPreview: "Injection preview",
|
|
251
254
|
contextUsage: "Context usage",
|
|
255
|
+
injectionOmitted: "{count} resident memories not injected (expand)",
|
|
256
|
+
budgetLabel: "budget",
|
|
257
|
+
contextUsageDetail: "{self}+{injected} chars{budget} \u2248 {tokens} token",
|
|
252
258
|
keywordsLabel: "Keywords",
|
|
253
259
|
suggested: "Quarantined",
|
|
254
260
|
approved: "Active",
|
|
@@ -985,13 +991,23 @@ function MemoryView(props) {
|
|
|
985
991
|
const selfChars = preview.self.length;
|
|
986
992
|
const total = selfChars + preview.chars;
|
|
987
993
|
const tokens = Math.ceil(total / 2);
|
|
988
|
-
const budgetText = preview.budget === null ? "" : ` /
|
|
989
|
-
|
|
990
|
-
return `${selfChars}+${preview.chars} \u5B57\u7B26${budgetText}${omittedText} \u2248 ${tokens} token`;
|
|
994
|
+
const budgetText = preview.budget === null ? "" : ` / ${t("budgetLabel")} ${preview.budget}`;
|
|
995
|
+
return t("contextUsageDetail", { self: selfChars, injected: preview.chars, budget: budgetText, tokens });
|
|
991
996
|
})())
|
|
992
997
|
),
|
|
993
998
|
(0, import_react.createElement)("div", { style: { ...ssid.text, fontSize: 11, whiteSpace: "pre-wrap", wordBreak: "break-all" } }, preview.self),
|
|
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))
|
|
999
|
+
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)),
|
|
1000
|
+
// 预算诊断(2026-09-18):被预算挡在外面的常驻记忆——展开可见 id 与摘要
|
|
1001
|
+
preview.omittedRecords.length > 0 ? (0, import_react.createElement)(
|
|
1002
|
+
"details",
|
|
1003
|
+
{ style: { ...ssid.muted, fontSize: 11 } },
|
|
1004
|
+
(0, import_react.createElement)(
|
|
1005
|
+
"summary",
|
|
1006
|
+
{ style: { cursor: "pointer" } },
|
|
1007
|
+
t("injectionOmitted", { count: preview.omitted })
|
|
1008
|
+
),
|
|
1009
|
+
preview.omittedRecords.map((record, index) => (0, import_react.createElement)("div", { key: index, style: { ...ssid.muted, fontSize: 11, marginTop: 4 } }, record.line))
|
|
1010
|
+
) : null
|
|
995
1011
|
) : null,
|
|
996
1012
|
// 未选择工作区:工作区视图显示占位(0.3.4 工作区路由语义)
|
|
997
1013
|
namespace === "workspace" && (props.cwd === void 0 || props.cwd === "") ? (0, import_react.createElement)("div", { style: ssid.empty }, t("noWorkspace")) : listSection
|
package/dist/engine.d.ts
CHANGED
|
@@ -94,6 +94,12 @@ export interface MemoryWrite {
|
|
|
94
94
|
* 失效。只用于「随环境变化的事实」(某工具版本下的行为、某环境变量决定的配置)。
|
|
95
95
|
*/
|
|
96
96
|
anchor?: MemoryAnchor;
|
|
97
|
+
/**
|
|
98
|
+
* 常驻注入(可选,2026-09-18):显式指定这条记忆是否每轮进上下文。**给值即接管**——
|
|
99
|
+
* 与 {@link MemoryEngine.setInjected} 同一套语义(写 `injectedAuto: false`,此后不受自动
|
|
100
|
+
* 升降影响)。省略则维持缺省行为:不注入,且**保留**「被反复命中即自动升常驻」的资格。
|
|
101
|
+
*/
|
|
102
|
+
injected?: boolean;
|
|
97
103
|
}
|
|
98
104
|
export interface MemoryFilter {
|
|
99
105
|
namespace?: MemoryNamespace;
|
|
@@ -104,6 +110,16 @@ export interface MemoryFilter {
|
|
|
104
110
|
/** 记录类别过滤(0.6.0);缺省不过滤(工具层显式传 kind='fact' 保持旧行为)。 */
|
|
105
111
|
kind?: MemoryKind;
|
|
106
112
|
}
|
|
113
|
+
/** {@link MemoryEngine.update} 的补丁:省略的字段保持原值(`injected` 省略时连 `injectedAuto` 一起保持)。 */
|
|
114
|
+
export interface MemoryPatch {
|
|
115
|
+
content?: string;
|
|
116
|
+
keywords?: string[];
|
|
117
|
+
/**
|
|
118
|
+
* 常驻注入(2026-09-18):给值即接管,与 {@link MemoryEngine.setInjected} 同义(写
|
|
119
|
+
* `injectedAuto: false`,此后不受自动升降影响);省略则保持原值。
|
|
120
|
+
*/
|
|
121
|
+
injected?: boolean;
|
|
122
|
+
}
|
|
107
123
|
export interface MemoryHit {
|
|
108
124
|
record: MemoryRecord;
|
|
109
125
|
score: number;
|
|
@@ -426,21 +442,22 @@ export declare class MemoryEngine extends Service {
|
|
|
426
442
|
/** {@link setStatus} 的实现体;必须经 `writeScope` 进入。 */
|
|
427
443
|
private setStatusInner;
|
|
428
444
|
/**
|
|
429
|
-
* 注入维度开关(0.3.0):只改 injected,不动审核状态。供 UI
|
|
430
|
-
*
|
|
445
|
+
* 注入维度开关(0.3.0):只改 injected,不动审核状态。供 UI 面板「常驻注入」开关调用
|
|
446
|
+
* (remote.setInjected)。2026-09-18 起模型侧有等价路径——`memory_save` / `memory_update`
|
|
447
|
+
* 的 `injected` 参数,经 {@link remember} / {@link update} 写同一套 `injectedAuto: false`
|
|
448
|
+
* 语义。差异只在越界处理:本方法对隔离记录**抛错**(用户明确要开,该被告知),
|
|
449
|
+
* 而写入/更新路径**静默降级为不注入**(那是顺带设置,不该中断整次写入)。
|
|
431
450
|
*/
|
|
432
451
|
setInjected(id: MemoryId, injected: boolean, projectCwd?: string): Promise<MemoryRecord>;
|
|
433
452
|
/** {@link setInjected} 的实现体;必须经 `writeScope` 进入。 */
|
|
434
453
|
private setInjectedInner;
|
|
435
454
|
/**
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
* `injected`
|
|
455
|
+
* 修改一条记忆的内容/关键词/注入位(0.3.1 整理记忆用)。2026-09-15 起:改动**不再**
|
|
456
|
+
* 退回待审核——静默机制下记忆由系统与模型自行维护,更新即保持原有审核状态。2026-09-18
|
|
457
|
+
* 起可改 `injected`:**省略则保留原值**(连 `injectedAuto` 一起不动),给值即接管。若新
|
|
458
|
+
* 内容命中危险规则则转为隔离(与写入路径同一判定),并连带撤下注入。
|
|
439
459
|
*/
|
|
440
|
-
update(id: MemoryId, patch:
|
|
441
|
-
content?: string;
|
|
442
|
-
keywords?: string[];
|
|
443
|
-
}, projectCwd?: string): Promise<MemoryRecord>;
|
|
460
|
+
update(id: MemoryId, patch: MemoryPatch, projectCwd?: string): Promise<MemoryRecord>;
|
|
444
461
|
/** {@link update} 的实现体;必须经 `writeScope` 进入。 */
|
|
445
462
|
private updateInner;
|
|
446
463
|
/**
|
package/dist/engine.js
CHANGED
|
@@ -417,13 +417,16 @@ export class MemoryEngine extends Service {
|
|
|
417
417
|
const block = {
|
|
418
418
|
namespace,
|
|
419
419
|
status: verdict.quarantined ? 'suggested' : 'approved',
|
|
420
|
-
|
|
420
|
+
// 常驻注入(2026-09-18):显式给值即接管;隔离内容一律不注入
|
|
421
|
+
injected: verdict.quarantined ? false : input.injected ?? false,
|
|
421
422
|
content: input.content,
|
|
422
423
|
keywords: (input.keywords ?? []).map(keyword => keyword.toLowerCase()),
|
|
423
424
|
createdAt: now,
|
|
424
425
|
updatedAt: now,
|
|
425
426
|
// ⑧ 来源(2026-09-15):缺省 'agent'——写入这条记忆的就是主模型
|
|
426
427
|
source: input.source ?? 'agent',
|
|
428
|
+
// 显式指定注入即接管:此后既不被自动降级,也不被自动升级(同 setInjected,2026-09-18)
|
|
429
|
+
...input.injected === undefined ? {} : { injectedAuto: false },
|
|
427
430
|
// 有效性锚点(可选):值变了这条记忆会被标 stale,不再被当成仍然正确
|
|
428
431
|
...input.anchor === undefined ? {} : { anchor: input.anchor },
|
|
429
432
|
...verdict.quarantined
|
|
@@ -980,8 +983,11 @@ export class MemoryEngine extends Service {
|
|
|
980
983
|
throw new Error(`cannot set status of unknown memory '${id}'`);
|
|
981
984
|
}
|
|
982
985
|
/**
|
|
983
|
-
* 注入维度开关(0.3.0):只改 injected,不动审核状态。供 UI
|
|
984
|
-
*
|
|
986
|
+
* 注入维度开关(0.3.0):只改 injected,不动审核状态。供 UI 面板「常驻注入」开关调用
|
|
987
|
+
* (remote.setInjected)。2026-09-18 起模型侧有等价路径——`memory_save` / `memory_update`
|
|
988
|
+
* 的 `injected` 参数,经 {@link remember} / {@link update} 写同一套 `injectedAuto: false`
|
|
989
|
+
* 语义。差异只在越界处理:本方法对隔离记录**抛错**(用户明确要开,该被告知),
|
|
990
|
+
* 而写入/更新路径**静默降级为不注入**(那是顺带设置,不该中断整次写入)。
|
|
985
991
|
*/
|
|
986
992
|
async setInjected(id, injected, projectCwd) {
|
|
987
993
|
return this.writeScope(projectCwd, () => this.setInjectedInner(id, injected, projectCwd));
|
|
@@ -1025,9 +1031,10 @@ export class MemoryEngine extends Service {
|
|
|
1025
1031
|
return record;
|
|
1026
1032
|
}
|
|
1027
1033
|
/**
|
|
1028
|
-
*
|
|
1029
|
-
*
|
|
1030
|
-
* `injected`
|
|
1034
|
+
* 修改一条记忆的内容/关键词/注入位(0.3.1 整理记忆用)。2026-09-15 起:改动**不再**
|
|
1035
|
+
* 退回待审核——静默机制下记忆由系统与模型自行维护,更新即保持原有审核状态。2026-09-18
|
|
1036
|
+
* 起可改 `injected`:**省略则保留原值**(连 `injectedAuto` 一起不动),给值即接管。若新
|
|
1037
|
+
* 内容命中危险规则则转为隔离(与写入路径同一判定),并连带撤下注入。
|
|
1031
1038
|
*/
|
|
1032
1039
|
async update(id, patch, projectCwd) {
|
|
1033
1040
|
return this.writeScope(projectCwd, () => this.updateInner(id, patch, projectCwd));
|
|
@@ -1041,11 +1048,16 @@ export class MemoryEngine extends Service {
|
|
|
1041
1048
|
? block.keywords
|
|
1042
1049
|
: patch.keywords.map(keyword => keyword.toLowerCase());
|
|
1043
1050
|
const verdict = detectSensitive(content, ...keywords);
|
|
1051
|
+
// 命中危险规则:退回 suggested(不注入)+ 隔离(不检索)双保险;否则保持原状态
|
|
1052
|
+
const status = verdict.quarantined ? 'suggested' : normalized.status;
|
|
1053
|
+
// 常驻注入(2026-09-18):显式给值即接管;未审核或已隔离一律不注入(与自动升级同一门槛)
|
|
1054
|
+
const injected = status === 'approved' ? patch.injected ?? normalized.injected : false;
|
|
1044
1055
|
return {
|
|
1045
1056
|
...block,
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1057
|
+
status,
|
|
1058
|
+
injected,
|
|
1059
|
+
// 显式改注入即视为有意决定:此后不受自动升降影响(省略时连 injectedAuto 一起保持)
|
|
1060
|
+
...patch.injected === undefined ? {} : { injectedAuto: false },
|
|
1049
1061
|
...verdict.quarantined
|
|
1050
1062
|
? { quarantined: true, ...verdict.reason === undefined ? {} : { quarantineReason: verdict.reason } }
|
|
1051
1063
|
: { quarantined: false, quarantineReason: undefined },
|
|
@@ -1054,13 +1066,22 @@ export class MemoryEngine extends Service {
|
|
|
1054
1066
|
updatedAt: Date.now(),
|
|
1055
1067
|
};
|
|
1056
1068
|
};
|
|
1069
|
+
// 注入位变化单独发一条事件:客户端与面板据此刷新「常驻」列,只发 status 会漏掉它
|
|
1070
|
+
const publish = (previous, updated) => {
|
|
1071
|
+
const record = toRecord(id, updated);
|
|
1072
|
+
this.ctx.emit('memory/changed', { operation: 'status', id, status: normalizeBlock(updated).status });
|
|
1073
|
+
const wasInjected = normalizeBlock(previous).injected;
|
|
1074
|
+
const isInjected = normalizeBlock(updated).injected;
|
|
1075
|
+
if (isInjected !== wasInjected) {
|
|
1076
|
+
this.ctx.emit('memory/changed', { operation: 'injected', id, injected: isInjected });
|
|
1077
|
+
}
|
|
1078
|
+
return record;
|
|
1079
|
+
};
|
|
1057
1080
|
const global = this.requireTable('global').get(id);
|
|
1058
1081
|
if (global !== undefined) {
|
|
1059
1082
|
const updated = applyPatch(global);
|
|
1060
1083
|
await this.requireTable('global').put(id, updated);
|
|
1061
|
-
|
|
1062
|
-
this.ctx.emit('memory/changed', { operation: 'status', id, status: normalizeBlock(updated).status });
|
|
1063
|
-
return record;
|
|
1084
|
+
return publish(global, updated);
|
|
1064
1085
|
}
|
|
1065
1086
|
const project = await this.projectTableFor(projectCwd);
|
|
1066
1087
|
if (project !== undefined) {
|
|
@@ -1068,9 +1089,7 @@ export class MemoryEngine extends Service {
|
|
|
1068
1089
|
if (block !== undefined) {
|
|
1069
1090
|
const updated = applyPatch(block);
|
|
1070
1091
|
await project.put(id, updated);
|
|
1071
|
-
|
|
1072
|
-
this.ctx.emit('memory/changed', { operation: 'status', id, status: normalizeBlock(updated).status });
|
|
1073
|
-
return record;
|
|
1092
|
+
return publish(block, updated);
|
|
1074
1093
|
}
|
|
1075
1094
|
}
|
|
1076
1095
|
throw new Error(`cannot update unknown memory '${id}'`);
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { MemoryEngine } from "./engine.js";
|
|
|
9
9
|
import { MemoryGateway } from "./remote.js";
|
|
10
10
|
import { mountMemoryApi } from "./routes.js";
|
|
11
11
|
import { SELF_DESCRIPTION } from "./self.js";
|
|
12
|
-
import { DEFAULT_INJECTION_BUDGET, DEFAULT_SUMMARY_CHARS, renderInjection } from "./injection.js";
|
|
12
|
+
import { DEFAULT_INJECTION_BUDGET, DEFAULT_SUMMARY_CHARS, omittedNotice, renderInjection } from "./injection.js";
|
|
13
13
|
export { MemoryEngine } from "./engine.js";
|
|
14
14
|
export { MemoryId } from "./engine.js";
|
|
15
15
|
export { bm25FieldScores, bm25Scores, cosineSimilarity, rrfFuse, tokenize } from "./bm25.js";
|
|
@@ -112,11 +112,19 @@ const GUIDANCE = 'Use memory tools for cross-session preferences, habits, and pr
|
|
|
112
112
|
// (0.3.4 起工作区记忆就按会话 cwd 路由;provider 是同步回调,只读已
|
|
113
113
|
// 打开/缓存的表,未打开时本轮工作区部分为空,由 ensureProjectOpen 预热)。
|
|
114
114
|
// 0.5.2:注入经摘要化 + 预算截断(renderInjection,纯函数)防上下文膨胀。
|
|
115
|
+
// 0.9.0:装不下的条目不再是无声丢弃——末尾追加一行预算诊断(omittedNotice),
|
|
116
|
+
// 报出被挡在外面的 id;它只在有出局时出现,清理干净即消失。
|
|
115
117
|
function recallText(memory, projectCwd, budget, summaryChars) {
|
|
116
118
|
const rendered = renderInjection(memory.recallRecords(projectCwd), budget, summaryChars);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
const parts = [];
|
|
120
|
+
if (rendered.lines.length > 0) {
|
|
121
|
+
parts.push(`Remembered preferences and conventions — apply these:\n${rendered.lines.join('\n')}`);
|
|
122
|
+
}
|
|
123
|
+
// 全部出局(lines 为空)时诊断照出——那正是最该报的情形
|
|
124
|
+
const notice = omittedNotice(rendered);
|
|
125
|
+
if (notice !== '')
|
|
126
|
+
parts.push(notice);
|
|
127
|
+
return parts.join('\n');
|
|
120
128
|
}
|
|
121
129
|
/** 从组装上下文取当前 agent 会话的工作区 cwd(AssembleContext.agent 由 dsh-agent 声明合并提供)。 */
|
|
122
130
|
function assemblyProjectCwd(assembly) {
|
|
@@ -213,6 +221,7 @@ export async function apply(ctx, config) {
|
|
|
213
221
|
content: { type: 'string', required: true, description: 'Plaintext memory content.' },
|
|
214
222
|
namespace: { type: 'string', enum: ['global', 'project'], description: 'Where it applies; defaults to global.' },
|
|
215
223
|
keywords: { type: 'array', items: { type: 'string' }, description: 'Explicit searchable anchors for memory_search. Give several angles (synonyms, abbreviations, zh/en) or it will not be found later. 多角度关键词(同义词、缩写、中英),否则将来检索不到它。' },
|
|
224
|
+
injected: { type: 'boolean', description: 'Set true to make this memory resident in every turn\'s context, as a deliberate decision that no longer auto-expires. Reserve it for rules, standing agreements and judgement criteria — the kind that must apply even when nobody thinks to search for them. Facts, references and case notes stay search-only (omit it). 设为 true 让这条记忆每轮常驻上下文,且视为有意决定、不再被自动淘汰。只用于规则、长期约定与判据——那些「没人想起来搜也必须生效」的记忆;事实、参考与案例留作按需检索(省略即可)。' },
|
|
216
225
|
anchor: {
|
|
217
226
|
type: 'object',
|
|
218
227
|
additionalProperties: false,
|
|
@@ -233,6 +242,7 @@ export async function apply(ctx, config) {
|
|
|
233
242
|
content: args.content,
|
|
234
243
|
...args.namespace === undefined ? {} : { namespace: args.namespace },
|
|
235
244
|
...args.keywords === undefined ? {} : { keywords: args.keywords },
|
|
245
|
+
...args.injected === undefined ? {} : { injected: args.injected },
|
|
236
246
|
// 参数已由 tool schema 校验,这里的窄化只为过类型
|
|
237
247
|
...args.anchor === undefined
|
|
238
248
|
? {}
|
|
@@ -431,11 +441,12 @@ export async function apply(ctx, config) {
|
|
|
431
441
|
}));
|
|
432
442
|
ctx.tools.register(defineTool({
|
|
433
443
|
name: 'memory_update',
|
|
434
|
-
description: 'Update the content or
|
|
444
|
+
description: 'Update the content, keywords or persistent-injection switch of one stored memory (e.g. correcting stale facts). The record keeps its review status; content that looks like a credential quarantines it instead. 修改一条记忆的内容、关键词或常驻注入开关(如修正过时信息)。改动后记忆保持原有审核状态;若新内容命中危险规则则转为隔离。',
|
|
435
445
|
parameters: {
|
|
436
446
|
id: { type: 'string', required: true, description: 'Exact memory id from memory_list. 记忆 id(来自 memory_list).' },
|
|
437
447
|
content: { type: 'string', description: 'New content; omit to keep current. 新内容;省略则保留现有内容.' },
|
|
438
448
|
keywords: { type: 'array', items: { type: 'string' }, description: 'New keywords; omit to keep current. 新关键词;省略则保留现有.' },
|
|
449
|
+
injected: { type: 'boolean', description: 'Set or clear the persistent-injection switch; omit to keep the current value. Setting it records a deliberate decision — the memory stops being auto-promoted or auto-demoted. See memory_save for what deserves residency. 设置或清除常驻注入开关;省略则保持原值。给值即视为有意决定——此后不受自动升降影响。何时该常驻见 memory_save。' },
|
|
439
450
|
},
|
|
440
451
|
output: {
|
|
441
452
|
schema: RECORD_SCHEMA,
|
|
@@ -445,6 +456,7 @@ export async function apply(ctx, config) {
|
|
|
445
456
|
return memory.update(args.id, {
|
|
446
457
|
...args.content === undefined ? {} : { content: args.content },
|
|
447
458
|
...args.keywords === undefined ? {} : { keywords: args.keywords },
|
|
459
|
+
...args.injected === undefined ? {} : { injected: args.injected },
|
|
448
460
|
}, execProjectCwd(exec)).then(recordValue);
|
|
449
461
|
},
|
|
450
462
|
presentCall: args => present('Update memory', 'other', args.id),
|
package/dist/injection.d.ts
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
* 注入渲染(0.5.2):`memory:recall` 与面板「注入预览」共用的确定性
|
|
3
3
|
* 渲染——摘要化 + 预算截断。纯函数、无模型调用:注入行 = 每条记忆的
|
|
4
4
|
* 单行摘要(内容过长取首行截断),超出预算(字符数)时按「最近更新
|
|
5
|
-
*
|
|
5
|
+
* 优先」贪心保留;省略明细返回给面板与预算诊断(2026-09-18)。
|
|
6
6
|
*/
|
|
7
7
|
import type { MemoryRecord } from './engine.ts';
|
|
8
8
|
/** 摘要截断上限(字符):中英混合场景下 ~40-60 token。 */
|
|
9
9
|
export declare const DEFAULT_SUMMARY_CHARS = 80;
|
|
10
10
|
/** 注入预算(字符):0.5.2 起默认开启,防常驻注入随记忆增长膨胀上下文。 */
|
|
11
11
|
export declare const DEFAULT_INJECTION_BUDGET = 1500;
|
|
12
|
+
/** 诊断行列出的 id 上限(2026-09-18):超出只列前 N 个,防这行本身失控。 */
|
|
13
|
+
export declare const OMITTED_ID_LIMIT = 12;
|
|
12
14
|
/**
|
|
13
15
|
* 确定性摘要:取首行,超过 maxChars 截断加省略号。
|
|
14
16
|
* 空内容/空首行回退为去掉换行后的前 maxChars(保证 any 内容有摘要)。
|
|
@@ -23,7 +25,16 @@ export interface InjectionRender {
|
|
|
23
25
|
budget: number | null;
|
|
24
26
|
/** 因预算被省略的候选条数(0 = 全部注入)。 */
|
|
25
27
|
omitted: number;
|
|
26
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* 被省略条目的明细:`id` 供预算诊断行与后续清理定位,`line` 与 {@link lines}
|
|
30
|
+
* 同格式,供面板明细直接渲染。诊断行**不占预算**——它由 `recallText` 追加在
|
|
31
|
+
* 注入行之后,不参与这里的截断。
|
|
32
|
+
*/
|
|
33
|
+
omittedRecords: Array<{
|
|
34
|
+
id: string;
|
|
35
|
+
line: string;
|
|
36
|
+
}>;
|
|
37
|
+
/** 实际注入的字符数(摘要 + 标记开销之和;不含 {@link omittedRecords})。 */
|
|
27
38
|
chars: number;
|
|
28
39
|
}
|
|
29
40
|
/**
|
|
@@ -32,3 +43,9 @@ export interface InjectionRender {
|
|
|
32
43
|
* 预算为 null 或非正数时全量注入(不过滤)。
|
|
33
44
|
*/
|
|
34
45
|
export declare function renderInjection(records: readonly MemoryRecord[], budget: number | null, summaryChars: number): InjectionRender;
|
|
46
|
+
/**
|
|
47
|
+
* 预算诊断行(2026-09-18):列 id 而非摘要——列全文要约 4000 字符,id 列表约
|
|
48
|
+
* 100 字符。**不占注入预算**(调用方把它追加在注入行之后),清理干净后自行消失
|
|
49
|
+
* (`omitted === 0` 返回空串);超出 {@link OMITTED_ID_LIMIT} 只列前 N 个。
|
|
50
|
+
*/
|
|
51
|
+
export declare function omittedNotice(rendered: InjectionRender): string;
|
package/dist/injection.js
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* 注入渲染(0.5.2):`memory:recall` 与面板「注入预览」共用的确定性
|
|
3
3
|
* 渲染——摘要化 + 预算截断。纯函数、无模型调用:注入行 = 每条记忆的
|
|
4
4
|
* 单行摘要(内容过长取首行截断),超出预算(字符数)时按「最近更新
|
|
5
|
-
*
|
|
5
|
+
* 优先」贪心保留;省略明细返回给面板与预算诊断(2026-09-18)。
|
|
6
6
|
*/
|
|
7
7
|
/** 摘要截断上限(字符):中英混合场景下 ~40-60 token。 */
|
|
8
8
|
export const DEFAULT_SUMMARY_CHARS = 80;
|
|
9
9
|
/** 注入预算(字符):0.5.2 起默认开启,防常驻注入随记忆增长膨胀上下文。 */
|
|
10
10
|
export const DEFAULT_INJECTION_BUDGET = 1500;
|
|
11
|
+
/** 诊断行列出的 id 上限(2026-09-18):超出只列前 N 个,防这行本身失控。 */
|
|
12
|
+
export const OMITTED_ID_LIMIT = 12;
|
|
11
13
|
/** 单条注入行的固定标记开销(不含摘要本体)。 */
|
|
12
14
|
const LINE_OVERHEAD = 27;
|
|
13
15
|
/**
|
|
@@ -36,6 +38,7 @@ export function injectionLine(record, summaryChars) {
|
|
|
36
38
|
export function renderInjection(records, budget, summaryChars) {
|
|
37
39
|
const ordered = [...records].sort((left, right) => (right.lastUsedAt ?? right.updatedAt) - (left.lastUsedAt ?? left.updatedAt));
|
|
38
40
|
const lines = [];
|
|
41
|
+
const omittedRecords = [];
|
|
39
42
|
let chars = 0;
|
|
40
43
|
let omitted = 0;
|
|
41
44
|
for (const record of ordered) {
|
|
@@ -43,10 +46,24 @@ export function renderInjection(records, budget, summaryChars) {
|
|
|
43
46
|
const cost = text.length + LINE_OVERHEAD;
|
|
44
47
|
if (budget !== null && budget > 0 && chars + cost > budget) {
|
|
45
48
|
omitted++;
|
|
49
|
+
omittedRecords.push({ id: String(record.id), line: text });
|
|
46
50
|
continue;
|
|
47
51
|
}
|
|
48
52
|
lines.push(text);
|
|
49
53
|
chars += cost;
|
|
50
54
|
}
|
|
51
|
-
return { lines, budget: budget ?? null, omitted, chars };
|
|
55
|
+
return { lines, budget: budget ?? null, omitted, omittedRecords, chars };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 预算诊断行(2026-09-18):列 id 而非摘要——列全文要约 4000 字符,id 列表约
|
|
59
|
+
* 100 字符。**不占注入预算**(调用方把它追加在注入行之后),清理干净后自行消失
|
|
60
|
+
* (`omitted === 0` 返回空串);超出 {@link OMITTED_ID_LIMIT} 只列前 N 个。
|
|
61
|
+
*/
|
|
62
|
+
export function omittedNotice(rendered) {
|
|
63
|
+
if (rendered.omitted === 0)
|
|
64
|
+
return '';
|
|
65
|
+
const ids = rendered.omittedRecords.map(record => String(record.id).slice(0, 8));
|
|
66
|
+
const shown = ids.slice(0, OMITTED_ID_LIMIT);
|
|
67
|
+
const truncated = ids.length > shown.length ? '…' : '';
|
|
68
|
+
return `(另有 ${rendered.omitted} 条常驻因预算未注入:${shown.join(', ')}${truncated})`;
|
|
52
69
|
}
|
package/dist/remote.d.ts
CHANGED
|
@@ -31,13 +31,18 @@ export declare class MemoryGateway extends TypertRemoteService {
|
|
|
31
31
|
/**
|
|
32
32
|
* 注入预览(0.3.5 + 0.5.2):开发者查看当前注入到 system prompt 的记忆
|
|
33
33
|
* 内容——self 自述 + 当前会话工作区(cwd)的 approved+injected 记忆,
|
|
34
|
-
* 经摘要化 + 预算截断(与 memory:recall
|
|
34
|
+
* 经摘要化 + 预算截断(与 memory:recall 同源渲染)。`omittedRecords`
|
|
35
|
+
* (2026-09-18)供面板展开「哪些常驻没进上下文」。
|
|
35
36
|
*/
|
|
36
37
|
injectionPreview(cwd?: string, budget?: number | null, summaryChars?: number): Promise<{
|
|
37
38
|
self: string;
|
|
38
39
|
lines: string[];
|
|
39
40
|
budget: number | null;
|
|
40
41
|
omitted: number;
|
|
42
|
+
omittedRecords: Array<{
|
|
43
|
+
id: string;
|
|
44
|
+
line: string;
|
|
45
|
+
}>;
|
|
41
46
|
chars: number;
|
|
42
47
|
}>;
|
|
43
48
|
}
|
package/dist/remote.js
CHANGED
|
@@ -112,7 +112,8 @@ let MemoryGateway = (() => {
|
|
|
112
112
|
/**
|
|
113
113
|
* 注入预览(0.3.5 + 0.5.2):开发者查看当前注入到 system prompt 的记忆
|
|
114
114
|
* 内容——self 自述 + 当前会话工作区(cwd)的 approved+injected 记忆,
|
|
115
|
-
* 经摘要化 + 预算截断(与 memory:recall
|
|
115
|
+
* 经摘要化 + 预算截断(与 memory:recall 同源渲染)。`omittedRecords`
|
|
116
|
+
* (2026-09-18)供面板展开「哪些常驻没进上下文」。
|
|
116
117
|
*/
|
|
117
118
|
async injectionPreview(cwd, budget, summaryChars) {
|
|
118
119
|
if (cwd !== undefined)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@max-null/dsh-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Cross-session plaintext memory plugin for the DeepSeek Harness — deterministic BM25 recall, writes take effect immediately, signal-driven injection, no embeddings",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|