dsh-layered-memory 0.7.1 → 0.8.1

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 (53) hide show
  1. package/README.en.md +165 -75
  2. package/README.md +174 -104
  3. package/assets/img/EmbeddingSource.png +0 -0
  4. package/assets/img/MemoryTools.png +0 -0
  5. package/assets/img/ToolTrajectory.png +0 -0
  6. package/assets/readme/bench-dialog.svg +87 -0
  7. package/assets/readme/bench-workflow.svg +79 -0
  8. package/assets/readme/flow.svg +74 -74
  9. package/assets/readme/storage.svg +56 -56
  10. package/dist/client.js +380 -2
  11. package/dist/config.d.ts +41 -1
  12. package/dist/config.js +10 -3
  13. package/dist/hooks/recall.d.ts +11 -8
  14. package/dist/hooks/recall.js +70 -51
  15. package/dist/index.d.ts +20 -0
  16. package/dist/index.js +15 -4
  17. package/dist/llm.d.ts +28 -0
  18. package/dist/llm.js +33 -0
  19. package/dist/pipeline/l1.js +5 -4
  20. package/dist/pipeline/l2.js +7 -2
  21. package/dist/pipeline/l3.js +7 -2
  22. package/dist/pipeline/rebuild.js +2 -2
  23. package/dist/pipeline/runner.d.ts +45 -8
  24. package/dist/pipeline/runner.js +185 -39
  25. package/dist/pipeline/trigger.d.ts +38 -0
  26. package/dist/pipeline/trigger.js +64 -0
  27. package/dist/settings.d.ts +14 -0
  28. package/dist/settings.js +41 -2
  29. package/dist/stats.js +113 -1
  30. package/dist/store/bm25.js +2 -1
  31. package/dist/store/download-queue.d.ts +35 -2
  32. package/dist/store/download-queue.js +102 -5
  33. package/dist/store/embedding.d.ts +12 -6
  34. package/dist/store/embedding.js +10 -7
  35. package/dist/store/l0.d.ts +2 -0
  36. package/dist/store/l0.js +4 -0
  37. package/dist/store/l1.d.ts +2 -0
  38. package/dist/store/l1.js +2 -2
  39. package/dist/store/model-catalog.js +1 -1
  40. package/dist/store/pending.d.ts +27 -6
  41. package/dist/store/pending.js +49 -9
  42. package/dist/store/search-utils.js +3 -2
  43. package/dist/store/session-modes.d.ts +4 -0
  44. package/dist/store/session-modes.js +16 -0
  45. package/dist/store/sqlite.d.ts +12 -0
  46. package/dist/store/sqlite.js +103 -5
  47. package/dist/util/recall-budget.d.ts +32 -0
  48. package/dist/util/recall-budget.js +85 -0
  49. package/dist/util/text.d.ts +8 -3
  50. package/dist/util/text.js +41 -15
  51. package/dist/util/tokenizer.d.ts +14 -0
  52. package/dist/util/tokenizer.js +53 -0
  53. package/package.json +6 -3
package/dist/client.js CHANGED
@@ -1463,6 +1463,363 @@ window.__ModuleLoader__.load({
1463
1463
  );
1464
1464
  }
1465
1465
 
1466
+ // ── 蒸馏模型选择器(供应商/模型两级下拉)──
1467
+ // 数据源:dsh-memory/llm-providers(供应商目录 + 当前覆盖 + 实际生效路由)与
1468
+ // dsh-memory/llm-models(所选供应商的模型列表)。写入走 settings-set 的
1469
+ // distillProvider/distillModel 两键(成对提交)。部署静态 pin(pinned)时禁用。
1470
+ function LlmModelRow(props) {
1471
+ var rpc = props.rpc;
1472
+ var disabled = !!props.disabled;
1473
+
1474
+ // 注意:全部 hook 必须在任何提前返回之前声明(Rules of Hooks)——
1475
+ // info 未加载的首渲染会 return null,晚于它的 useState 会让 hook 数随渲染漂移而崩溃
1476
+ var infoState = react.useState(null);
1477
+ var setInfo = infoState[1];
1478
+ var modelsState = react.useState(null);
1479
+ var setModels = modelsState[1];
1480
+ var draftState = react.useState("");
1481
+ var setDraft = draftState[1];
1482
+ var manualState = react.useState("");
1483
+ var manualModel = manualState[0];
1484
+ var setManualModel = manualState[1];
1485
+
1486
+ react.useEffect(function () {
1487
+ var load = function () {
1488
+ rpc("dsh-memory/llm-providers", {})
1489
+ .then(function (r) { if (r && r.ok) setInfo(r.value); })
1490
+ .catch(function () {});
1491
+ };
1492
+ load();
1493
+ var timer = setInterval(load, 5000);
1494
+ return function () { clearInterval(timer); };
1495
+ }, [rpc]);
1496
+
1497
+ var info = infoState[0];
1498
+ // 草稿供应商(已选待挑模型)优先于已存选择
1499
+ var draft = draftState[0];
1500
+ var curProvider = draft || (info && info.current ? info.current.provider : "");
1501
+ var curModel = info && info.current ? info.current.model : "";
1502
+
1503
+ react.useEffect(function () {
1504
+ if (!curProvider) { setModels(null); return undefined; }
1505
+ var alive = true;
1506
+ setModels(null);
1507
+ rpc("dsh-memory/llm-models", { provider: curProvider })
1508
+ .then(function (r) { if (alive && r && r.ok) setModels(r.value.models || []); })
1509
+ .catch(function () { if (alive) setModels([]); });
1510
+ return function () { alive = false; };
1511
+ }, [rpc, curProvider]);
1512
+
1513
+ var writeLlm = function (patch) {
1514
+ if (!info) return;
1515
+ // 乐观更新:立即写视图字段,失败回滚
1516
+ var prev = info;
1517
+ setInfo(Object.assign({}, prev, {
1518
+ current: Object.assign({}, prev.current, patch),
1519
+ }));
1520
+ setDraft("");
1521
+ rpc("dsh-memory/settings-set", patch)
1522
+ .then(function (r) {
1523
+ if (!r || !r.ok) {
1524
+ setInfo(prev);
1525
+ setDraft(draft);
1526
+ }
1527
+ })
1528
+ .catch(function () {
1529
+ setInfo(prev);
1530
+ setDraft(draft);
1531
+ });
1532
+ };
1533
+
1534
+ if (!info) return null;
1535
+ if (info.pinned) {
1536
+ // 部署静态 pin:显示固定路由,选择器不出场
1537
+ return react.createElement(
1538
+ "div", { style: S.switchRow },
1539
+ react.createElement("div", null,
1540
+ react.createElement("div", { style: S.switchLabel }, "蒸馏模型"),
1541
+ react.createElement(
1542
+ "div", { style: S.switchDesc },
1543
+ "已由部署配置固定:" + info.effective.provider + " / " + info.effective.model,
1544
+ )),
1545
+ );
1546
+ }
1547
+
1548
+ var providers = info.providers || [];
1549
+ var models = modelsState[0];
1550
+ var providerKnown = curProvider === "" || providers.some(function (p) { return p.id === curProvider; });
1551
+ var modelKnown = models && models.some(function (m) { return m.id === curModel; });
1552
+ // 供应商列表为空数组 = 适配器不提供模型目录:降级为手动输入模型 id
1553
+ var manualInput = models !== null && models.length === 0;
1554
+
1555
+ var defaultLabel = info.default
1556
+ ? "跟随默认(" + info.default.provider + " / " + info.default.model + ")"
1557
+ : "跟随默认";
1558
+
1559
+ var providerOptions = [{ id: "", label: defaultLabel }].concat(
1560
+ providers.map(function (p) { return { id: p.id, label: p.name + "(" + p.id + ")" }; }),
1561
+ );
1562
+ if (curProvider && !providerKnown) {
1563
+ providerOptions.push({ id: curProvider, label: curProvider + "(已不在列表)" });
1564
+ }
1565
+
1566
+ var modelOptions = [{ id: "", label: "跟随默认" }].concat(
1567
+ (models || []).map(function (m) { return { id: m.id, label: m.name !== m.id ? m.name + "(" + m.id + ")" : m.id }; }),
1568
+ );
1569
+ if (curModel && !modelKnown) {
1570
+ modelOptions.push({ id: curModel, label: curModel + "(已不在列表)" });
1571
+ }
1572
+
1573
+ return react.createElement(
1574
+ "div", { style: Object.assign({}, S.switchRow, { flexWrap: "wrap" }) },
1575
+ react.createElement(
1576
+ "select",
1577
+ {
1578
+ className: "dsh-mem-select",
1579
+ style: { flexShrink: 0, maxWidth: 220 },
1580
+ value: curProvider,
1581
+ disabled: disabled,
1582
+ onChange: function (e) {
1583
+ var v = e.target.value;
1584
+ if (v === "") {
1585
+ writeLlm({ distillProvider: "", distillModel: "" });
1586
+ } else {
1587
+ setDraft(v);
1588
+ }
1589
+ },
1590
+ },
1591
+ providerOptions.map(function (o) {
1592
+ return react.createElement("option", { key: o.id, value: o.id }, o.label);
1593
+ }),
1594
+ ),
1595
+ !curProvider
1596
+ ? null
1597
+ : manualInput
1598
+ ? react.createElement(NInput, {
1599
+ style: { flex: 1, minWidth: 160 },
1600
+ placeholder: "模型 id(该供应商未提供列表,手动输入后回车)…",
1601
+ value: manualModel,
1602
+ onChange: function (e) { setManualModel(e.target.value); },
1603
+ onKeyDown: function (e) {
1604
+ if (e.key === "Enter" && manualModel.trim()) {
1605
+ writeLlm({ distillProvider: curProvider, distillModel: manualModel.trim() });
1606
+ setManualModel("");
1607
+ }
1608
+ },
1609
+ })
1610
+ : react.createElement(
1611
+ "select",
1612
+ {
1613
+ className: "dsh-mem-select",
1614
+ style: { flex: 1, minWidth: 160 },
1615
+ value: curModel,
1616
+ disabled: disabled || models === null,
1617
+ onChange: function (e) {
1618
+ writeLlm({ distillProvider: curProvider, distillModel: e.target.value });
1619
+ },
1620
+ },
1621
+ modelOptions.map(function (o) {
1622
+ return react.createElement("option", { key: o.id, value: o.id }, o.label);
1623
+ }),
1624
+ ),
1625
+ react.createElement("div", null,
1626
+ react.createElement("div", { style: S.switchLabel }, "蒸馏模型"),
1627
+ react.createElement(
1628
+ "div", { style: S.switchDesc },
1629
+ info.effective
1630
+ ? "当前生效 " + info.effective.provider + " / " + info.effective.model +
1631
+ (curProvider ? "" : "(跟随默认选择)")
1632
+ : "暂无可用路由(未选择且无默认)",
1633
+ ),
1634
+ !providerKnown || (curModel && models !== null && !modelKnown)
1635
+ ? react.createElement(
1636
+ "div", { style: { fontSize: 12, color: "var(--dsh-mem-danger)" } },
1637
+ "所选供应商或模型已不在已配置列表中,蒸馏调用可能失败,建议重新选择。",
1638
+ )
1639
+ : null),
1640
+ );
1641
+ }
1642
+
1643
+ // ── 蒸馏预算(分层输出 token 上限 + 输入字符上限)──
1644
+ // 数据源:settings-get 的 budgets(current 运行时覆盖 0=跟随默认 / defaults
1645
+ // 内置默认 / effective 实际生效)与 inputBudget(current 0=跟随配置 /
1646
+ // fallback 静态配置值 / effective)。输出四键经 settings-set 的
1647
+ // distillBudgets 成对提交;输入走 distillMaxInputChars 单键提交。
1648
+ // 思考档 high/max 的 ×4 放大只作用于输出预算(提示文案注明)。
1649
+ function BudgetInputs(props) {
1650
+ var rpc = props.rpc;
1651
+ var disabled = !!props.disabled;
1652
+ var data = props.data;
1653
+ var setData = props.setData;
1654
+ var onError = props.onError;
1655
+ var layers = [
1656
+ ["extract", "抽取"],
1657
+ ["dedup", "去重"],
1658
+ ["l2", "L2 场景"],
1659
+ ["l3", "L3 画像"],
1660
+ ];
1661
+ // 输入草稿(string|null):击键只改本地,blur/回车才提交(input 键 = 输入预算)
1662
+ var draftState = react.useState(null);
1663
+ var draft = draftState[0];
1664
+ var setDraft = draftState[1];
1665
+
1666
+ if (!data || !data.budgets) return null;
1667
+ var cur = data.budgets.current || {};
1668
+ var def = data.budgets.defaults || {};
1669
+ var eff = data.budgets.effective || {};
1670
+ var ib = data.inputBudget || {};
1671
+ var curIn = ib.current || 0;
1672
+ var effIn = ib.effective || ib.fallback || 0;
1673
+
1674
+ var shown = function (key) {
1675
+ if (draft && draft[key] !== undefined) return draft[key];
1676
+ var c = key === "input" ? curIn : cur[key];
1677
+ return c > 0 ? String(c) : "";
1678
+ };
1679
+
1680
+ /** 通用提交:patchKey 为 settings-set 载荷键名;pick 从草稿取值并校验。 */
1681
+ var commitPart = function (keys, buildPayload, applyView) {
1682
+ if (!draft) return;
1683
+ var values = {};
1684
+ for (var i = 0; i < keys.length; i++) {
1685
+ var key = keys[i];
1686
+ var raw = (draft[key] !== undefined ? draft[key] : shown(key)).trim();
1687
+ var n = raw === "" ? 0 : Number(raw);
1688
+ var max = key === "input" ? 1000000 : 1000000;
1689
+ var min = key === "input" ? (raw === "" ? 0 : 1000) : 0;
1690
+ if (!Number.isInteger(n) || n < min || n > max) {
1691
+ onError(key === "input"
1692
+ ? "输入预算须为 0 或 1000~1000000 的整数(留空或 0 = 跟随配置)"
1693
+ : "输出预算须为 0~1000000 的整数(留空或 0 = 跟随默认)");
1694
+ setDraft(null);
1695
+ return;
1696
+ }
1697
+ values[key] = n;
1698
+ }
1699
+ setDraft(null);
1700
+ var prev = data;
1701
+ setData(applyView(prev, values));
1702
+ rpc("dsh-memory/settings-set", buildPayload(values))
1703
+ .then(function (r) {
1704
+ if (!r || !r.ok) {
1705
+ setData(prev);
1706
+ onError(r && r.error ? "预算写入失败:" + r.error.message : "预算写入失败");
1707
+ } else {
1708
+ onError(null);
1709
+ }
1710
+ })
1711
+ .catch(function (e) {
1712
+ setData(prev);
1713
+ onError("预算写入失败:" + String((e && e.message) || e));
1714
+ });
1715
+ };
1716
+
1717
+ /** 输出四键成对提交(distillBudgets)。 */
1718
+ var commitOutputs = function () {
1719
+ commitPart(
1720
+ ["extract", "dedup", "l2", "l3"],
1721
+ function (v) { return { distillBudgets: v }; },
1722
+ function (prev, v) {
1723
+ var effNext = {};
1724
+ for (var j = 0; j < layers.length; j++) {
1725
+ var k = layers[j][0];
1726
+ effNext[k] = v[k] > 0 ? v[k] : (def[k] || 0);
1727
+ }
1728
+ return Object.assign({}, prev, {
1729
+ settings: Object.assign({}, prev.settings, { distillBudgets: v }),
1730
+ budgets: Object.assign({}, prev.budgets, { current: v, effective: effNext }),
1731
+ });
1732
+ },
1733
+ );
1734
+ };
1735
+
1736
+ /** 输入预算单键提交(distillMaxInputChars)。 */
1737
+ var commitInput = function () {
1738
+ commitPart(
1739
+ ["input"],
1740
+ function (v) { return { distillMaxInputChars: v.input }; },
1741
+ function (prev, v) {
1742
+ return Object.assign({}, prev, {
1743
+ settings: Object.assign({}, prev.settings, { distillMaxInputChars: v.input }),
1744
+ inputBudget: Object.assign({}, prev.inputBudget || {}, {
1745
+ current: v.input,
1746
+ effective: v.input > 0 ? v.input : (ib.fallback || 0),
1747
+ }),
1748
+ });
1749
+ },
1750
+ );
1751
+ };
1752
+
1753
+ /** 某组键是否有未提交改动(触发 blur/Enter 提交)。 */
1754
+ var dirty = function (keys) {
1755
+ if (!draft) return false;
1756
+ for (var i = 0; i < keys.length; i++) {
1757
+ var key = keys[i];
1758
+ var want = (draft[key] !== undefined ? draft[key] : "").trim();
1759
+ var was = key === "input"
1760
+ ? (curIn > 0 ? String(curIn) : "")
1761
+ : (cur[key] > 0 ? String(cur[key]) : "");
1762
+ if (want !== was) return true;
1763
+ }
1764
+ return false;
1765
+ };
1766
+ var outKeys = ["extract", "dedup", "l2", "l3"];
1767
+
1768
+ var inputBox = function (key, label, title, width, placeholder, onCommit) {
1769
+ return react.createElement(NInput, {
1770
+ key: key,
1771
+ type: "number",
1772
+ min: 0,
1773
+ max: 1000000,
1774
+ style: { width: width },
1775
+ title: title,
1776
+ placeholder: String(placeholder || ""),
1777
+ value: shown(key),
1778
+ disabled: disabled,
1779
+ onChange: function (e) {
1780
+ var v = e.target.value;
1781
+ var d = Object.assign({}, draft || {});
1782
+ d[key] = v;
1783
+ setDraft(d);
1784
+ },
1785
+ onBlur: function () { if (dirty([key])) onCommit(); },
1786
+ onKeyDown: function (e) { if (e.key === "Enter" && dirty([key])) onCommit(); },
1787
+ });
1788
+ };
1789
+
1790
+ return react.createElement(
1791
+ "div", null,
1792
+ react.createElement(
1793
+ "div", { style: S.switchRow },
1794
+ react.createElement(
1795
+ "div", { style: Object.assign({}, S.flexRow, { gap: 6 }) },
1796
+ layers.map(function (l) {
1797
+ return inputBox(l[0], l[1], l[1] + " 输出预算(token,留空 = 默认 " + (def[l[0]] || "?") + ")", 92, def[l[0]], commitOutputs);
1798
+ }),
1799
+ ),
1800
+ react.createElement("div", null,
1801
+ react.createElement("div", { style: S.switchLabel }, "输出预算"),
1802
+ react.createElement(
1803
+ "div", { style: S.switchDesc },
1804
+ "各蒸馏层单次输出的 token 上限(抽取/去重/L2/L3);留空或 0 = 跟随默认(当前生效 " +
1805
+ layers.map(function (l) { return eff[l[0]] || "?"; }).join(" / ") +
1806
+ ");思考档 high/max 时实际限额自动 ×4",
1807
+ )),
1808
+ ),
1809
+ react.createElement(
1810
+ "div", { style: S.switchRow },
1811
+ inputBox("input", "输入", "单次蒸馏输入字符上限(≈token,中文 1 字≈1 token;留空 = 跟随配置 " + (ib.fallback || "?") + ")", 120, ib.fallback, commitInput),
1812
+ react.createElement("div", null,
1813
+ react.createElement("div", { style: S.switchLabel }, "输入预算"),
1814
+ react.createElement(
1815
+ "div", { style: S.switchDesc },
1816
+ "单次蒸馏调用的输入字符上限(≈token):L1 抽取按此分块、L2/L3 超限截断;" +
1817
+ "留空或 0 = 跟随配置(当前生效 " + (effIn || "?") + ",来自 llm.maxInputChars)",
1818
+ )),
1819
+ ),
1820
+ );
1821
+ }
1822
+
1466
1823
  // ── Tab:概览(开关面板 + 计数) ──
1467
1824
  function OverviewTab(props) {
1468
1825
  var rpc = props.rpc;
@@ -1500,11 +1857,20 @@ window.__ModuleLoader__.load({
1500
1857
  if (!settingsData) return;
1501
1858
  // 乐观更新:立即翻 UI,失败回滚
1502
1859
  var prev = settingsData;
1860
+ var patch = (function () { var o = {}; o[key] = value; return o; })();
1503
1861
  var next = Object.assign({}, prev, {
1504
- settings: Object.assign({}, prev.settings, (function () { var o = {}; o[key] = value; return o; })()),
1862
+ settings: Object.assign({}, prev.settings, patch),
1505
1863
  });
1864
+ // 蒸馏思考选择器读 effort.current(非 settings 键):乐观更新须同步该视图字段,
1865
+ // 否则点击后选择器在下一个 5s 轮询前"弹回"旧值,看起来没生效
1866
+ if (key === "reasoningEffort" && next.effort) {
1867
+ next.effort = Object.assign({}, prev.effort, {
1868
+ current: value,
1869
+ effective: value || prev.effort.fallback,
1870
+ });
1871
+ }
1506
1872
  setSettingsData(next);
1507
- rpc("dsh-memory/settings-set", (function () { var o = {}; o[key] = value; return o; })())
1873
+ rpc("dsh-memory/settings-set", patch)
1508
1874
  .then(function (r) {
1509
1875
  if (!r || !r.ok) {
1510
1876
  setSettingsData(prev);
@@ -1558,6 +1924,8 @@ window.__ModuleLoader__.load({
1558
1924
  : settingsData
1559
1925
  ? react.createElement(
1560
1926
  "div", { style: S.switchPanel },
1927
+ // 分组:记忆模式(总闸与三个分项开关)
1928
+ react.createElement("div", { style: S.panelLabel }, "记忆模式"),
1561
1929
  react.createElement(SwitchRow, {
1562
1930
  label: "记忆模式",
1563
1931
  desc: master ? "已开启:捕获对话并蒸馏记忆" : "已关闭:不捕获、不蒸馏、不注入(数据保留)",
@@ -1585,6 +1953,8 @@ window.__ModuleLoader__.load({
1585
1953
  disabled: !master,
1586
1954
  onChange: function (v) { toggle("recall", v); },
1587
1955
  }),
1956
+ // 分组:蒸馏参数(模型路由 / 思考档位 / 输出预算)
1957
+ react.createElement("div", { style: S.panelLabel }, "蒸馏参数"),
1588
1958
  settingsData.effort
1589
1959
  ? react.createElement(
1590
1960
  "div",
@@ -1610,6 +1980,14 @@ window.__ModuleLoader__.load({
1610
1980
  )),
1611
1981
  )
1612
1982
  : null,
1983
+ react.createElement(LlmModelRow, { rpc: rpc, disabled: !master }),
1984
+ react.createElement(BudgetInputs, {
1985
+ rpc: rpc,
1986
+ disabled: !master,
1987
+ data: settingsData,
1988
+ setData: setSettingsData,
1989
+ onError: setError,
1990
+ }),
1613
1991
  ceilingNote
1614
1992
  ? react.createElement("p", { style: S.hint }, ceilingNote)
1615
1993
  : null,
package/dist/config.d.ts CHANGED
@@ -19,8 +19,11 @@ export interface MemoryConfig {
19
19
  };
20
20
  extract: {
21
21
  enabled: boolean;
22
- /** 至少攒够多少条新消息才跑一次 L1 抽取(省 token)。 */
22
+ /** 稳态触发阈值:单会话攒够多少条新消息才跑一次 L1 抽取(省 token)。
23
+ * 起步阶段生效阈值从 1 翻倍爬坡到此值(渐进阈值,ADR-0003)。 */
23
24
  minMessages: number;
25
+ /** 闲置兜底:会话静默多少秒后把未蒸馏切片落袋(接住"没攒够阈值用户就离开");0 = 关闭。 */
26
+ idleSeconds: number;
24
27
  /** L1 抽取时的背景消息条数(供上下文推断,不参与提取)。 */
25
28
  backgroundMessages: number;
26
29
  /** 去重候选池大小(每条新记忆的相似候选数)。 */
@@ -44,6 +47,12 @@ export interface MemoryConfig {
44
47
  enabled: boolean;
45
48
  /** 每步自动召回注入的 L1 条数。 */
46
49
  maxResults: number;
50
+ /** 单条注入记忆的字符上限(超限截断并提示用工具查全文);0 = 不限。 */
51
+ maxCharsPerMemory: number;
52
+ /** 整轮注入总字符上限(超限按相关性丢尾部);0 = 不限。 */
53
+ maxTotalRecallChars: number;
54
+ /** 召回总预算(ms):超时跳过本轮注入、不阻塞对话;0 = 不限时。 */
55
+ timeoutMs: number;
47
56
  includePersona: boolean;
48
57
  includeSceneNav: boolean;
49
58
  /** 检索策略:keyword(FTS5 BM25)| embedding(向量)| hybrid(双路 + RRF 融合)。 */
@@ -68,6 +77,9 @@ export interface MemoryConfig {
68
77
  allowLocalModels: boolean;
69
78
  /** 模型下载镜像根地址(默认国内可达的 hf-mirror.com,可改回官方)。 */
70
79
  mirror: string;
80
+ /** 模型下载代理三态:''(默认)= 探测代理环境变量(HTTPS_PROXY/ALL_PROXY 等);
81
+ * 'none' = 禁用强制直连;其他值 = 代理 URL(如 http://127.0.0.1:7890)。 */
82
+ proxy: string;
71
83
  };
72
84
  llm: {
73
85
  /** 蒸馏用的 provider 路由;留空用当前默认选择。 */
@@ -83,6 +95,14 @@ export interface MemoryConfig {
83
95
  maxInputChars: number;
84
96
  /** 单次蒸馏调用超时(ms)。 */
85
97
  timeoutMs: number;
98
+ /** 分层输出预算运行时覆盖(设置页 distillBudgets 经 effectiveCfg 注入;
99
+ * 0/缺省 = 用内置默认。不属于静态 schema——预算无部署上限语义,只有运行时偏好)。 */
100
+ budgets?: Partial<{
101
+ extract: number;
102
+ dedup: number;
103
+ l2: number;
104
+ l3: number;
105
+ }>;
86
106
  };
87
107
  /** 是否注册模型可调用的记忆工具。 */
88
108
  tools: boolean;
@@ -102,11 +122,13 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
102
122
  extract: Schema<Schemastery.ObjectS<{
103
123
  enabled: Schema<boolean, boolean>;
104
124
  minMessages: Schema<number, number>;
125
+ idleSeconds: Schema<number, number>;
105
126
  backgroundMessages: Schema<number, number>;
106
127
  candidatePool: Schema<number, number>;
107
128
  }>, Schemastery.ObjectT<{
108
129
  enabled: Schema<boolean, boolean>;
109
130
  minMessages: Schema<number, number>;
131
+ idleSeconds: Schema<number, number>;
110
132
  backgroundMessages: Schema<number, number>;
111
133
  candidatePool: Schema<number, number>;
112
134
  }>>;
@@ -131,6 +153,9 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
131
153
  recall: Schema<Schemastery.ObjectS<{
132
154
  enabled: Schema<boolean, boolean>;
133
155
  maxResults: Schema<number, number>;
156
+ maxCharsPerMemory: Schema<number, number>;
157
+ maxTotalRecallChars: Schema<number, number>;
158
+ timeoutMs: Schema<number, number>;
134
159
  includePersona: Schema<boolean, boolean>;
135
160
  includeSceneNav: Schema<boolean, boolean>;
136
161
  strategy: Schema<"keyword" | "embedding" | "hybrid", "keyword" | "embedding" | "hybrid">;
@@ -138,6 +163,9 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
138
163
  }>, Schemastery.ObjectT<{
139
164
  enabled: Schema<boolean, boolean>;
140
165
  maxResults: Schema<number, number>;
166
+ maxCharsPerMemory: Schema<number, number>;
167
+ maxTotalRecallChars: Schema<number, number>;
168
+ timeoutMs: Schema<number, number>;
141
169
  includePersona: Schema<boolean, boolean>;
142
170
  includeSceneNav: Schema<boolean, boolean>;
143
171
  strategy: Schema<"keyword" | "embedding" | "hybrid", "keyword" | "embedding" | "hybrid">;
@@ -153,6 +181,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
153
181
  timeoutMs: Schema<number, number>;
154
182
  allowLocalModels: Schema<boolean, boolean>;
155
183
  mirror: Schema<string, string>;
184
+ proxy: Schema<string, string>;
156
185
  }>, Schemastery.ObjectT<{
157
186
  enabled: Schema<boolean, boolean>;
158
187
  baseUrl: Schema<string, string>;
@@ -163,6 +192,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
163
192
  timeoutMs: Schema<number, number>;
164
193
  allowLocalModels: Schema<boolean, boolean>;
165
194
  mirror: Schema<string, string>;
195
+ proxy: Schema<string, string>;
166
196
  }>>;
167
197
  llm: Schema<Schemastery.ObjectS<{
168
198
  provider: Schema<string, string>;
@@ -197,11 +227,13 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
197
227
  extract: Schema<Schemastery.ObjectS<{
198
228
  enabled: Schema<boolean, boolean>;
199
229
  minMessages: Schema<number, number>;
230
+ idleSeconds: Schema<number, number>;
200
231
  backgroundMessages: Schema<number, number>;
201
232
  candidatePool: Schema<number, number>;
202
233
  }>, Schemastery.ObjectT<{
203
234
  enabled: Schema<boolean, boolean>;
204
235
  minMessages: Schema<number, number>;
236
+ idleSeconds: Schema<number, number>;
205
237
  backgroundMessages: Schema<number, number>;
206
238
  candidatePool: Schema<number, number>;
207
239
  }>>;
@@ -226,6 +258,9 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
226
258
  recall: Schema<Schemastery.ObjectS<{
227
259
  enabled: Schema<boolean, boolean>;
228
260
  maxResults: Schema<number, number>;
261
+ maxCharsPerMemory: Schema<number, number>;
262
+ maxTotalRecallChars: Schema<number, number>;
263
+ timeoutMs: Schema<number, number>;
229
264
  includePersona: Schema<boolean, boolean>;
230
265
  includeSceneNav: Schema<boolean, boolean>;
231
266
  strategy: Schema<"keyword" | "embedding" | "hybrid", "keyword" | "embedding" | "hybrid">;
@@ -233,6 +268,9 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
233
268
  }>, Schemastery.ObjectT<{
234
269
  enabled: Schema<boolean, boolean>;
235
270
  maxResults: Schema<number, number>;
271
+ maxCharsPerMemory: Schema<number, number>;
272
+ maxTotalRecallChars: Schema<number, number>;
273
+ timeoutMs: Schema<number, number>;
236
274
  includePersona: Schema<boolean, boolean>;
237
275
  includeSceneNav: Schema<boolean, boolean>;
238
276
  strategy: Schema<"keyword" | "embedding" | "hybrid", "keyword" | "embedding" | "hybrid">;
@@ -248,6 +286,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
248
286
  timeoutMs: Schema<number, number>;
249
287
  allowLocalModels: Schema<boolean, boolean>;
250
288
  mirror: Schema<string, string>;
289
+ proxy: Schema<string, string>;
251
290
  }>, Schemastery.ObjectT<{
252
291
  enabled: Schema<boolean, boolean>;
253
292
  baseUrl: Schema<string, string>;
@@ -258,6 +297,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
258
297
  timeoutMs: Schema<number, number>;
259
298
  allowLocalModels: Schema<boolean, boolean>;
260
299
  mirror: Schema<string, string>;
300
+ proxy: Schema<string, string>;
261
301
  }>>;
262
302
  llm: Schema<Schemastery.ObjectS<{
263
303
  provider: Schema<string, string>;
package/dist/config.js CHANGED
@@ -15,7 +15,8 @@ export const memorySchema = Schema.object({
15
15
  }),
16
16
  extract: Schema.object({
17
17
  enabled: Schema.boolean().default(true),
18
- minMessages: Schema.number().min(1).max(100).default(1),
18
+ minMessages: Schema.number().min(1).max(100).default(6),
19
+ idleSeconds: Schema.number().min(0).max(86_400).default(300),
19
20
  backgroundMessages: Schema.number().min(0).max(50).default(10),
20
21
  candidatePool: Schema.number().min(1).max(20).default(5),
21
22
  }),
@@ -32,6 +33,10 @@ export const memorySchema = Schema.object({
32
33
  recall: Schema.object({
33
34
  enabled: Schema.boolean().default(true),
34
35
  maxResults: Schema.number().min(1).max(20).default(5),
36
+ // 预算与超时(ADR-0001 / 规格 A 节):截断是引流——工具路径返回全文
37
+ maxCharsPerMemory: Schema.number().min(0).max(100_000).default(500),
38
+ maxTotalRecallChars: Schema.number().min(0).max(100_000).default(2000),
39
+ timeoutMs: Schema.number().min(0).max(60_000).default(5000),
35
40
  includePersona: Schema.boolean().default(true),
36
41
  includeSceneNav: Schema.boolean().default(true),
37
42
  strategy: Schema.union(['keyword', 'embedding', 'hybrid']).default('hybrid'),
@@ -48,12 +53,14 @@ export const memorySchema = Schema.object({
48
53
  timeoutMs: Schema.number().min(1000).max(300_000).default(10_000),
49
54
  allowLocalModels: Schema.boolean().default(true),
50
55
  mirror: Schema.string().default('https://hf-mirror.com'),
56
+ proxy: Schema.string().default(''),
51
57
  }),
52
58
  llm: Schema.object({
53
59
  provider: Schema.string().default(''),
54
60
  model: Schema.string().default(''),
55
- // 推理模型(如 v4-flash)的 reasoning 计入输出预算:预算不足会被思考吃光导致正文 0 字符
56
- maxTokens: Schema.number().min(1024).max(1_000_000).default(256_000),
61
+ // 推理模型(如 v4-flash)的 reasoning 计入输出预算:预算不足会被思考吃光导致正文 0 字符。
62
+ // 0.8.0 起各蒸馏层显式传分层预算(见 llm.ts LAYER_MAX_TOKENS_*),本值为未分层调用的兜底总闸
63
+ maxTokens: Schema.number().min(1024).max(1_000_000).default(65_536),
57
64
  // 蒸馏是结构化抽取任务,默认关思考(off):v4-flash 默认 high 档的思考可把任意 maxTokens
58
65
  // 预算全部吃光导致正文 0 字符;非推理模型不认识 effort 时会报 UNSUPPORTED_REASONING_EFFORT,设空串跳过
59
66
  reasoningEffort: Schema.union(['', 'off', 'high', 'max']).default('off'),
@@ -1,13 +1,16 @@
1
1
  /**
2
- * 召回 Hook:
3
- * 1. agent/pre-step(waterfall):用进入步骤的用户消息做 L1 BM25 检索,缓存到该 agent 的槽位;
4
- * 必须调用并返回 next()(只观察,不改写步骤)。
5
- * 2. agent/created:在 agent 作用域注册动态上下文 provider(L1 召回 + L3 画像 + L2 场景导航 + 工具指南)。
2
+ * 召回 Hook(消息侧注入版,ADR-0001):
3
+ * 1. agent/pre-step(waterfall,prepend 注册):有新的用户来源消息到达的步骤
4
+ * (轮首 claim 或 steering 插话)→ 检索 L1 → 以合成消息形式注入到用户消息之前。
5
+ * 注入消息携带插件来源(form: 'recall'),对用户可见、随会话历史持久累积——
6
+ * 这就是"记忆生效"的显式提示(dsh-time-context 同款官方范式)。
7
+ * 纯工具步透传;召回超时(recall.timeoutMs 总预算)跳过本轮,绝不阻塞对话。
8
+ * 2. agent/created:在 agent 作用域注册动态上下文 provider(L3 画像 + L2 场景导航 +
9
+ * 工具指南——系统提示只保留稳定内容;指南三条件门控:工具开启 && 有内容)。
6
10
  *
7
- * 注入内容使用 <relevant-memories> / <user-persona> / <scene-navigation> / <memory-tools-guide>
8
- * 标签包裹——capture 侧 sanitize 会剥离这些标签,防止反馈循环。
9
- * auto 档两族合并时在 <user-persona>/<scene-navigation> 内部用 <domain family="..."> 子块
10
- * 分域(子块只出现在外层标签内部,随外层一起被 sanitize 剥离,无泄漏风险)。
11
+ * 注入内容使用 <relevant-memories> 标签包裹(模型侧语义边界 + 助手回显剥离锚点),
12
+ * 召回预算(单条/整轮)超限截断并引导模型用记忆工具查全文。
13
+ * L0 防污染零成本:capture 侧只收 source.kind === 'user' 的消息,注入消息天然被排除。
11
14
  *
12
15
  * 注意:PromptContext.text 是**同步**函数,画像/场景导航这类异步文件读取必须走内存缓存,
13
16
  * 由定时刷新 + 管线更新后的主动失效来更新。