dsh-layered-memory 0.8.0 → 0.8.2

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/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;
@@ -1567,6 +1924,8 @@ window.__ModuleLoader__.load({
1567
1924
  : settingsData
1568
1925
  ? react.createElement(
1569
1926
  "div", { style: S.switchPanel },
1927
+ // 分组:记忆模式(总闸与三个分项开关)
1928
+ react.createElement("div", { style: S.panelLabel }, "记忆模式"),
1570
1929
  react.createElement(SwitchRow, {
1571
1930
  label: "记忆模式",
1572
1931
  desc: master ? "已开启:捕获对话并蒸馏记忆" : "已关闭:不捕获、不蒸馏、不注入(数据保留)",
@@ -1594,6 +1953,8 @@ window.__ModuleLoader__.load({
1594
1953
  disabled: !master,
1595
1954
  onChange: function (v) { toggle("recall", v); },
1596
1955
  }),
1956
+ // 分组:蒸馏参数(模型路由 / 思考档位 / 输出预算)
1957
+ react.createElement("div", { style: S.panelLabel }, "蒸馏参数"),
1597
1958
  settingsData.effort
1598
1959
  ? react.createElement(
1599
1960
  "div",
@@ -1619,6 +1980,14 @@ window.__ModuleLoader__.load({
1619
1980
  )),
1620
1981
  )
1621
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
+ }),
1622
1991
  ceilingNote
1623
1992
  ? react.createElement("p", { style: S.hint }, ceilingNote)
1624
1993
  : null,
package/dist/config.d.ts CHANGED
@@ -77,6 +77,9 @@ export interface MemoryConfig {
77
77
  allowLocalModels: boolean;
78
78
  /** 模型下载镜像根地址(默认国内可达的 hf-mirror.com,可改回官方)。 */
79
79
  mirror: string;
80
+ /** 模型下载代理三态:''(默认)= 探测代理环境变量(HTTPS_PROXY/ALL_PROXY 等);
81
+ * 'none' = 禁用强制直连;其他值 = 代理 URL(如 http://127.0.0.1:7890)。 */
82
+ proxy: string;
80
83
  };
81
84
  llm: {
82
85
  /** 蒸馏用的 provider 路由;留空用当前默认选择。 */
@@ -92,6 +95,14 @@ export interface MemoryConfig {
92
95
  maxInputChars: number;
93
96
  /** 单次蒸馏调用超时(ms)。 */
94
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
+ }>;
95
106
  };
96
107
  /** 是否注册模型可调用的记忆工具。 */
97
108
  tools: boolean;
@@ -170,6 +181,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
170
181
  timeoutMs: Schema<number, number>;
171
182
  allowLocalModels: Schema<boolean, boolean>;
172
183
  mirror: Schema<string, string>;
184
+ proxy: Schema<string, string>;
173
185
  }>, Schemastery.ObjectT<{
174
186
  enabled: Schema<boolean, boolean>;
175
187
  baseUrl: Schema<string, string>;
@@ -180,6 +192,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
180
192
  timeoutMs: Schema<number, number>;
181
193
  allowLocalModels: Schema<boolean, boolean>;
182
194
  mirror: Schema<string, string>;
195
+ proxy: Schema<string, string>;
183
196
  }>>;
184
197
  llm: Schema<Schemastery.ObjectS<{
185
198
  provider: Schema<string, string>;
@@ -273,6 +286,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
273
286
  timeoutMs: Schema<number, number>;
274
287
  allowLocalModels: Schema<boolean, boolean>;
275
288
  mirror: Schema<string, string>;
289
+ proxy: Schema<string, string>;
276
290
  }>, Schemastery.ObjectT<{
277
291
  enabled: Schema<boolean, boolean>;
278
292
  baseUrl: Schema<string, string>;
@@ -283,6 +297,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
283
297
  timeoutMs: Schema<number, number>;
284
298
  allowLocalModels: Schema<boolean, boolean>;
285
299
  mirror: Schema<string, string>;
300
+ proxy: Schema<string, string>;
286
301
  }>>;
287
302
  llm: Schema<Schemastery.ObjectS<{
288
303
  provider: Schema<string, string>;
package/dist/config.js CHANGED
@@ -53,6 +53,7 @@ export const memorySchema = Schema.object({
53
53
  timeoutMs: Schema.number().min(1000).max(300_000).default(10_000),
54
54
  allowLocalModels: Schema.boolean().default(true),
55
55
  mirror: Schema.string().default('https://hf-mirror.com'),
56
+ proxy: Schema.string().default(''),
56
57
  }),
57
58
  llm: Schema.object({
58
59
  provider: Schema.string().default(''),
package/dist/index.d.ts CHANGED
@@ -82,6 +82,7 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
82
82
  timeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
83
83
  allowLocalModels: import("@deepseek-ai/schemastery").default<boolean, boolean>;
84
84
  mirror: import("@deepseek-ai/schemastery").default<string, string>;
85
+ proxy: import("@deepseek-ai/schemastery").default<string, string>;
85
86
  }>, Schemastery.ObjectT<{
86
87
  enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
87
88
  baseUrl: import("@deepseek-ai/schemastery").default<string, string>;
@@ -92,6 +93,7 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
92
93
  timeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
93
94
  allowLocalModels: import("@deepseek-ai/schemastery").default<boolean, boolean>;
94
95
  mirror: import("@deepseek-ai/schemastery").default<string, string>;
96
+ proxy: import("@deepseek-ai/schemastery").default<string, string>;
95
97
  }>>;
96
98
  llm: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
97
99
  provider: import("@deepseek-ai/schemastery").default<string, string>;
@@ -185,6 +187,7 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
185
187
  timeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
186
188
  allowLocalModels: import("@deepseek-ai/schemastery").default<boolean, boolean>;
187
189
  mirror: import("@deepseek-ai/schemastery").default<string, string>;
190
+ proxy: import("@deepseek-ai/schemastery").default<string, string>;
188
191
  }>, Schemastery.ObjectT<{
189
192
  enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
190
193
  baseUrl: import("@deepseek-ai/schemastery").default<string, string>;
@@ -195,6 +198,7 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
195
198
  timeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
196
199
  allowLocalModels: import("@deepseek-ai/schemastery").default<boolean, boolean>;
197
200
  mirror: import("@deepseek-ai/schemastery").default<string, string>;
201
+ proxy: import("@deepseek-ai/schemastery").default<string, string>;
198
202
  }>>;
199
203
  llm: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
200
204
  provider: import("@deepseek-ai/schemastery").default<string, string>;
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ import { StateStore } from './store/state.js';
39
39
  import { registerMemoryTools } from './tools/index.js';
40
40
  import { errDetail, withFileLog } from './util/filelog.js';
41
41
  import { resolveModelRoute } from './llm.js';
42
+ import { effectiveCfg } from './pipeline/runner.js';
42
43
  export const name = 'dsh-memory-plugin';
43
44
  /** 硬依赖:蒸馏要用 llm,工具注册要用 tools,召回注入要用 systemPrompt。 */
44
45
  export const inject = ['llm', 'tools', 'systemPrompt'];
@@ -86,7 +87,11 @@ export async function apply(ctx, config) {
86
87
  // ── 嵌入源三态(D4:远程/本地/关闭)——状态文件优先于静态配置 ──
87
88
  const sourceStore = new EmbeddingSourceStore(dataDir, logger);
88
89
  const installer = new RuntimeInstaller(dataDir, PINNED_TRANSFORMERS_VERSION, { logger });
89
- const downloader = new ModelDownloadQueue(dataDir, { mirror: config.embedding.mirror, logger });
90
+ const downloader = new ModelDownloadQueue(dataDir, {
91
+ mirror: config.embedding.mirror,
92
+ logger,
93
+ proxy: config.embedding.proxy,
94
+ });
90
95
  const makeLocalService = makeLocalServiceFactory(installer, downloader, logger);
91
96
  let initial = { svc: new NoopEmbeddingService(), dims: 0 };
92
97
  /** 管理器引用:启动重嵌链/backfill 闭包在运行期解引用(声明早于创建避免 TDZ)。 */
@@ -157,9 +162,10 @@ export async function apply(ctx, config) {
157
162
  logger.warn(`[memory] 检索策略 ${config.recall.strategy} 需要向量能力(embedding 未启用/不可用),本次运行按 keyword(FTS5 BM25)检索`);
158
163
  }
159
164
  logger.info(`[memory] 数据目录: ${dataDir}(v${PLUGIN_VERSION},默认档=${config.family},检索: FTS=${caps.ftsSearch} 向量=${caps.vectorSearch}${storageOk ? '' : ',存储不可用已停用捕获/蒸馏'})`);
160
- // 蒸馏模型路由:启动期解析一次并记录(路由错误是最难事后排查的问题之一)
165
+ // 蒸馏模型路由:启动期解析一次并记录(路由错误是最难事后排查的问题之一);
166
+ // 用运行时调参视图解析——用户已用 UI 覆盖蒸馏模型时,日志反映实际路由
161
167
  try {
162
- const route = await resolveModelRoute(ctx, config);
168
+ const route = await resolveModelRoute(ctx, effectiveCfg(config, live));
163
169
  logger.info(`[memory] 蒸馏模型路由: ${route.provider}/${route.model}`);
164
170
  }
165
171
  catch (err) {
@@ -298,6 +304,7 @@ export async function apply(ctx, config) {
298
304
  disposed = true;
299
305
  runner.stop();
300
306
  embedManager?.dispose();
307
+ downloader.dispose();
301
308
  return (async () => {
302
309
  await flushL0?.();
303
310
  db.close();
package/dist/llm.d.ts CHANGED
@@ -21,6 +21,21 @@ export declare const LAYER_MAX_TOKENS_DEDUP = 8000;
21
21
  export declare const LAYER_MAX_TOKENS_L2 = 32000;
22
22
  /** L3 画像(完整 persona 文档)。 */
23
23
  export declare const LAYER_MAX_TOKENS_L3 = 16000;
24
+ /** 分层输出预算键(运行时设置 / UI 与内置默认共用同一套键名)。 */
25
+ export type DistillBudgetLayer = 'extract' | 'dedup' | 'l2' | 'l3';
26
+ /** 各层内置默认预算(设置页"0 = 跟随默认"的默认值来源)。 */
27
+ export declare const LAYER_DEFAULT_BUDGETS: Record<DistillBudgetLayer, number>;
28
+ /**
29
+ * 解析某蒸馏层的生效输出预算:运行时覆盖(cfg.llm.budgets,由 effectiveCfg 从
30
+ * 设置页 distillBudgets 注入,0/缺省 = 跟随)→ 内置默认 → 思考档放大
31
+ * (high/max ×4,reasoning 计入输出预算的历史事故防线)。
32
+ */
33
+ export declare function resolveLayerTokens(cfg: {
34
+ llm: {
35
+ reasoningEffort: string;
36
+ budgets?: Partial<Record<DistillBudgetLayer, number>>;
37
+ };
38
+ }, layer: DistillBudgetLayer): number;
24
39
  /**
25
40
  * 思考档预算放大:reasoning 计入输出预算(v4-flash 事故:high 思考可吃光全部
26
41
  * 预算致正文 0 字符)——effort 为 high/max 时分层预算 ×4。
package/dist/llm.js CHANGED
@@ -10,6 +10,22 @@ export const LAYER_MAX_TOKENS_DEDUP = 8_000;
10
10
  export const LAYER_MAX_TOKENS_L2 = 32_000;
11
11
  /** L3 画像(完整 persona 文档)。 */
12
12
  export const LAYER_MAX_TOKENS_L3 = 16_000;
13
+ /** 各层内置默认预算(设置页"0 = 跟随默认"的默认值来源)。 */
14
+ export const LAYER_DEFAULT_BUDGETS = {
15
+ extract: LAYER_MAX_TOKENS_EXTRACT,
16
+ dedup: LAYER_MAX_TOKENS_DEDUP,
17
+ l2: LAYER_MAX_TOKENS_L2,
18
+ l3: LAYER_MAX_TOKENS_L3,
19
+ };
20
+ /**
21
+ * 解析某蒸馏层的生效输出预算:运行时覆盖(cfg.llm.budgets,由 effectiveCfg 从
22
+ * 设置页 distillBudgets 注入,0/缺省 = 跟随)→ 内置默认 → 思考档放大
23
+ * (high/max ×4,reasoning 计入输出预算的历史事故防线)。
24
+ */
25
+ export function resolveLayerTokens(cfg, layer) {
26
+ const override = cfg.llm.budgets?.[layer];
27
+ return layerMaxTokens(override && override > 0 ? override : LAYER_DEFAULT_BUDGETS[layer], cfg.llm.reasoningEffort);
28
+ }
13
29
  /**
14
30
  * 思考档预算放大:reasoning 计入输出预算(v4-flash 事故:high 思考可吃光全部
15
31
  * 预算致正文 0 字符)——effort 为 high/max 时分层预算 ×4。
@@ -3,7 +3,7 @@
3
3
  * 移植 MemoryCore 的 l1 runner 语义(抽取 prompt → batch 冲突检测 → apply)。
4
4
  */
5
5
  import { randomBytes } from 'node:crypto';
6
- import { callLLM, layerMaxTokens, LAYER_MAX_TOKENS_DEDUP, LAYER_MAX_TOKENS_EXTRACT, parseJsonLogged } from '../llm.js';
6
+ import { callLLM, parseJsonLogged, resolveLayerTokens } from '../llm.js';
7
7
  import { formatExtractionPrompt, getExtractMemoriesSystemPrompt } from '../prompts/l1-extraction.js';
8
8
  import { formatBatchConflictPrompt, getConflictDetectionSystemPrompt } from '../prompts/l1-dedup.js';
9
9
  import { familyForType } from '../types.js';
@@ -63,7 +63,7 @@ export async function runExtraction(ctx, cfg, store, states, pending, background
63
63
  const raw = await callLLM(ctx, cfg, {
64
64
  system: getExtractMemoriesSystemPrompt(mode),
65
65
  user: userPrompt,
66
- maxTokens: layerMaxTokens(LAYER_MAX_TOKENS_EXTRACT, cfg.llm.reasoningEffort),
66
+ maxTokens: resolveLayerTokens(cfg, 'extract'),
67
67
  logger,
68
68
  });
69
69
  const scenes = parseJsonLogged(raw, 'L1 抽取', logger);
@@ -101,7 +101,7 @@ export async function runExtraction(ctx, cfg, store, states, pending, background
101
101
  const dedupRaw = await callLLM(ctx, cfg, {
102
102
  system: getConflictDetectionSystemPrompt(mode),
103
103
  user: dedupPrompt,
104
- maxTokens: layerMaxTokens(LAYER_MAX_TOKENS_DEDUP, cfg.llm.reasoningEffort),
104
+ maxTokens: resolveLayerTokens(cfg, 'dedup'),
105
105
  logger,
106
106
  });
107
107
  const decisions = parseJsonLogged(dedupRaw, 'L1 去重判定', logger);
@@ -1,4 +1,4 @@
1
- import { callLLM, layerMaxTokens, LAYER_MAX_TOKENS_L2, parseJsonLogged } from '../llm.js';
1
+ import { callLLM, parseJsonLogged, resolveLayerTokens } from '../llm.js';
2
2
  import { buildScenePrompt, formatSceneSummaries } from '../prompts/scene.js';
3
3
  import { Bm25Index } from '../store/bm25.js';
4
4
  const REQUEST_RE = /\[PERSONA_UPDATE_REQUEST\]\s*reason:\s*([\s\S]*?)\[\/PERSONA_UPDATE_REQUEST\]/;
@@ -30,7 +30,7 @@ export async function runSceneConsolidation(ctx, cfg, scenes, newMemories, logge
30
30
  const raw = await callLLM(ctx, cfg, {
31
31
  system: systemPrompt,
32
32
  user: userPrompt,
33
- maxTokens: layerMaxTokens(LAYER_MAX_TOKENS_L2, cfg.llm.reasoningEffort),
33
+ maxTokens: resolveLayerTokens(cfg, 'l2'),
34
34
  logger,
35
35
  });
36
36
  // ── 解析:先取 PERSONA_UPDATE_REQUEST 标记,再解析操作数组 ──
@@ -1,4 +1,4 @@
1
- import { callLLM, layerMaxTokens, LAYER_MAX_TOKENS_L3 } from '../llm.js';
1
+ import { callLLM, resolveLayerTokens } from '../llm.js';
2
2
  import { buildPersonaPrompt } from '../prompts/persona.js';
3
3
  export async function runPersona(ctx, cfg, scenes, persona, state, logger, family) {
4
4
  if (!cfg.l3.enabled)
@@ -51,7 +51,7 @@ export async function runPersona(ctx, cfg, scenes, persona, state, logger, famil
51
51
  const raw = await callLLM(ctx, cfg, {
52
52
  system: systemPrompt,
53
53
  user: userPrompt,
54
- maxTokens: layerMaxTokens(LAYER_MAX_TOKENS_L3, cfg.llm.reasoningEffort),
54
+ maxTokens: resolveLayerTokens(cfg, 'l3'),
55
55
  logger,
56
56
  });
57
57
  const body = unwrapFence(raw);
@@ -99,7 +99,7 @@ export class RebuildController {
99
99
  ...this.status,
100
100
  sessionCount: est.sessions,
101
101
  messageCount: est.messages,
102
- estCalls: estimateCalls(est.sessions, est.messages, est.chars, this.cfg.llm.maxInputChars),
102
+ estCalls: estimateCalls(est.sessions, est.messages, est.chars, effectiveCfg(this.cfg, this.live).llm.maxInputChars),
103
103
  };
104
104
  }
105
105
  return { ...this.status };
@@ -123,7 +123,7 @@ export class RebuildController {
123
123
  phase: 'preparing',
124
124
  sessionCount: est.sessions,
125
125
  messageCount: est.messages,
126
- estCalls: estimateCalls(est.sessions, est.messages, est.chars, this.cfg.llm.maxInputChars),
126
+ estCalls: estimateCalls(est.sessions, est.messages, est.chars, effectiveCfg(this.cfg, this.live).llm.maxInputChars),
127
127
  startedAt: Date.now(),
128
128
  };
129
129
  this.runner.enqueueRebuildTask(() => this.prepare());