@cmnyhh/dsh-mnemo 0.9.6 → 0.9.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.
Files changed (3) hide show
  1. package/README.md +8 -1
  2. package/index.js +29 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -137,6 +137,13 @@ An exchange whose text fits in `segmentChars` is stored whole. A longer one is s
137
137
 
138
138
  Turn-start turns, plugin-sourced messages, tool payloads, and the plugin's own injected block are excluded — recall must never feed on its own output, and a tool result quoted as memory is noise.
139
139
 
140
+ **Since 0.9.7 every exchange is recorded, not only the ones that mention a change.** That is `writeMode: 'log'`, the default, and it separates two products that used to share one switch:
141
+
142
+ - a **conversation log entry** — the turn's own words, composed by rule, no model call. It answers "what did we talk about", so its value comes from coverage and nothing filters it but an envelope check (a turn that is nothing but host scaffolding is not a conversation).
143
+ - **extracted facts** — only when the gate reports a signal (`explicit` / `change` / `repair`). This is where a model call is spent, and where admission applies.
144
+
145
+ Before this, a turn that stated something plainly ("我们用的是 PG18") matched no keyword, so it was never sent, never logged, and unanswerable later. `writeMode: 'signal'` restores that behaviour for hosts that want strict filtering; `writeGate: false` turns the judgement off entirely.
146
+
140
147
  ### When a block is injected
141
148
 
142
149
  Only at step 1 of a turn, which is the one step that opens a turn's request. Later steps of the same turn already carry the block, and re-preparing step 1 after a failure injects again because that retry is the attempt that needs it.
@@ -150,7 +157,7 @@ With `serviceUrl` set the block is rendered by the service, so its wording and i
150
157
 
151
158
  The `writeGate` is textual on purpose and it only recognises a "记住", a change, or a repair. A decision reached in ordinary discussion says none of those words, so the plugin also exposes **`memory_write`** (name configurable via `writeToolName`): one self-contained statement per call.
152
159
 
153
- It is not a privileged path. The statement is pushed through the same ingest queue as a captured turn with `trigger: 'explicit'`, so the service's write review still runs on it — an instruction smuggled into a "fact" is downgraded there rather than trusted because a model wrote it. In thin-client mode the call returns `queued: true`, since extraction happens service-side; the record becomes searchable a moment later.
160
+ It is not a privileged path. The statement is written as a **direct item insert** (`sourceKind: 'tool'`, with the turn attached only so the service sees `trigger: 'explicit'`), and the service's write review still runs on it — an instruction smuggled into a "fact" is downgraded there rather than trusted because a model wrote it. Since 0.9.6 it no longer goes through extraction: re-running a model on a statement the model itself just decided to keep cost 5–8 seconds and could rewrite or drop it. In thin-client mode the call returns `queued: true`, since the write is delivered by the queue; the record becomes searchable a moment later.
154
161
 
155
162
 
156
163
  ### Corrections and the fact layer
package/index.js CHANGED
@@ -1841,26 +1841,37 @@ var FIXED = /(根因|原因是|问题在于|解决的?办法|修好|修复|已
1841
1841
  function join2(input) {
1842
1842
  return [input.prompt, ...input.replies].join("\n").trim();
1843
1843
  }
1844
+ function signalOf(input) {
1845
+ if (EXPLICIT.test(input.prompt)) return "explicit";
1846
+ const replies = input.replies.join("\n");
1847
+ if (PROBLEM.test(input.prompt) && FIXED.test(replies)) return "repair";
1848
+ if (CHANGE2.test(replies) || CHANGE2.test(input.prompt)) return "change";
1849
+ return "none";
1850
+ }
1844
1851
  function decideCapture(input, options = {}) {
1845
1852
  if (options.enabled === false) {
1846
- return { capture: true, trigger: "none", reason: "\u95F8\u95E8\u5DF2\u5173\u95ED\uFF0C\u5168\u90E8\u5199\u5165" };
1853
+ return { capture: true, trigger: "change", reason: "\u95F8\u95E8\u5DF2\u5173\u95ED\uFF0C\u5168\u90E8\u5199\u5165" };
1847
1854
  }
1848
1855
  const minChars = options.minChars ?? 24;
1849
- if (EXPLICIT.test(input.prompt)) {
1850
- return { capture: true, trigger: "explicit", reason: "\u7528\u6237\u663E\u5F0F\u8981\u6C42\u8BB0\u4F4F" };
1851
- }
1856
+ const mode = options.mode ?? "log";
1852
1857
  const text = join2(input);
1853
- const replies = input.replies.join("\n");
1854
- if (text.length < minChars && !CHANGE2.test(text)) {
1855
- return { capture: false, trigger: "none", reason: `\u5185\u5BB9\u8FC7\u77ED\uFF08${String(text.length)} \u5B57\uFF09\u4E14\u65E0\u53D8\u66F4\u4FE1\u53F7\uFF0C\u4E0D\u62BD\u53D6` };
1858
+ if (text.length === 0) {
1859
+ return { capture: false, trigger: "none", reason: "\u7A7A\u8F6E\uFF0C\u65E0\u53EF\u5199\u5165\u5185\u5BB9" };
1860
+ }
1861
+ const trigger = signalOf(input);
1862
+ if (trigger === "explicit") {
1863
+ return { capture: true, trigger, reason: "\u7528\u6237\u663E\u5F0F\u8981\u6C42\u8BB0\u4F4F" };
1864
+ }
1865
+ if (text.length < minChars && trigger === "none") {
1866
+ return { capture: false, trigger, reason: `\u5185\u5BB9\u8FC7\u77ED\uFF08${String(text.length)} \u5B57\uFF09\u4E14\u65E0\u53D8\u66F4\u4FE1\u53F7\uFF0C\u4E0D\u8BB0` };
1856
1867
  }
1857
- if (PROBLEM.test(input.prompt) && FIXED.test(replies)) {
1858
- return { capture: true, trigger: "repair", reason: "\u5148\u5931\u8D25\u540E\u89E3\u51B3\uFF0C\u503C\u5F97\u8BB0\u6210 episode" };
1868
+ if (mode === "signal" && trigger === "none") {
1869
+ return { capture: false, trigger, reason: "\u65E2\u975E\u663E\u5F0F\u3001\u4E5F\u975E\u53D8\u66F4\u6216\u4FEE\u590D\uFF0C\u4E0D\u8BB0" };
1859
1870
  }
1860
- if (CHANGE2.test(replies) || CHANGE2.test(input.prompt)) {
1861
- return { capture: true, trigger: "change", reason: "\u672C\u8F6E\u53D1\u751F\u4E86\u53D8\u66F4" };
1871
+ if (trigger === "none") {
1872
+ return { capture: true, trigger, reason: "\u8BB0\u5BF9\u8BDD\u65E5\u5FD7\uFF08\u4E0D\u62BD\u4E8B\u5B9E\uFF09" };
1862
1873
  }
1863
- return { capture: false, trigger: "none", reason: "\u65E2\u975E\u663E\u5F0F\u3001\u4E5F\u975E\u53D8\u66F4\u6216\u4FEE\u590D\uFF0C\u4E0D\u62BD\u53D6" };
1874
+ return { capture: true, trigger, reason: "\u672C\u8F6E\u6709\u4FE1\u53F7\uFF0C\u8BB0\u65E5\u5FD7\u5E76\u62BD\u4E8B\u5B9E" };
1864
1875
  }
1865
1876
 
1866
1877
  // src/token.ts
@@ -1934,7 +1945,10 @@ var Config = Schema.object({
1934
1945
  agentsMaxChars: Schema.number().default(2048),
1935
1946
  agentsSyncMinutes: Schema.number().default(30),
1936
1947
  writeGate: Schema.boolean().default(true),
1937
- writeGateMinChars: Schema.number().default(24)
1948
+ writeGateMinChars: Schema.number().default(24),
1949
+ // 取值在下面按 `=== 'signal'` 判定,非法值自动落回 log——不必用 union/literal,
1950
+ // 这个宿主带的 schemastery 版本没有 Schema.literal。
1951
+ writeMode: Schema.string().default("log")
1938
1952
  });
1939
1953
  var SEARCH_PARAMETERS = {
1940
1954
  query: {
@@ -2061,6 +2075,7 @@ function apply(ctx, config) {
2061
2075
  if (writeToolName === toolName) throw new Error("memory-core: writeToolName must differ from toolName");
2062
2076
  const writeGate = config.writeGate !== false;
2063
2077
  const writeGateMinChars = requireInteger("writeGateMinChars", config.writeGateMinChars, 1);
2078
+ const writeMode = config.writeMode === "signal" ? "signal" : "log";
2064
2079
  const t0WriteEnabled = config.t0WriteEnabled === true;
2065
2080
  const agentsMaxChars = requireInteger("agentsMaxChars", config.agentsMaxChars, 256);
2066
2081
  const agentsSyncMs = requireInteger("agentsSyncMinutes", config.agentsSyncMinutes, 1) * 6e4;
@@ -2155,7 +2170,7 @@ function apply(ctx, config) {
2155
2170
  offset,
2156
2171
  gate: decideCapture(
2157
2172
  { prompt: exchange.prompt, replies: exchange.replies },
2158
- { enabled: writeGate, minChars: writeGateMinChars }
2173
+ { enabled: writeGate, minChars: writeGateMinChars, mode: writeMode }
2159
2174
  )
2160
2175
  }));
2161
2176
  const kept = gated.filter((entry) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmnyhh/dsh-mnemo",
3
- "version": "0.9.6",
3
+ "version": "0.9.7",
4
4
  "description": "Long-term memory for dsh: capture finished turns, recall them into the first step of a later turn, and expose recall and write as tools. Ships a bundle layer and a settings panel for the service address and token.",
5
5
  "license": "MIT",
6
6
  "type": "module",