dsh-lite-memory 0.18.19 → 0.18.26

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/TRIGGERS.md CHANGED
@@ -16,7 +16,7 @@
16
16
  - 写:用户明确要求;用户自己的话已经是长期规则(「以后都这样 / 别再 X」)时直接存,不先问;或用户对某条建议点了「保留」。
17
17
  - 建议:一条经验能跨会话复用、又只存在于用户嘴里(偏好、惯例、踩过的坑),且同一项目 48 小时内没建议过。只出按钮,不写「要不要记」这种问句——问句要用户打字,按钮不要。
18
18
  - 不写:`1+1=2` 这种常识、一次性任务参数、能从仓库直接读到的正文;凭据只记位置与调用方式。
19
- - 自动命中:用户这句话与某条记忆的标题、旧标题、标签、触发词或摘要真有重合、且分数过线时,这一步的运行时上下文里多一段「已有记忆可能相关」——最多三条标题加 id,普通卡片与实验规则共用 3 条 / 1200 字节预算,正文仍要 `memory_read`。只有当前任务条件判为 `ok` 的条目能进;条件没确认或明确不符的既不进自动注入,也不进检索结果,检索只在全都被挡时说明「有相关的项目记忆,但适用条件还没确认 / 当前任务明确不适用」。没重合为空字符串,整条索引不进上下文,也不改 hits / lastUsed,更不落盘。同一句话只算一次;换句话重新算。
19
+ - 自动命中:用户这句话与某条记忆的标题、旧标题、标签、触发词或摘要真有重合、且分数过线时,这一步的运行时上下文里多一段「已有记忆可能相关」——最多三条标题加 id,普通卡片与实验规则共用 3 条 / 1200 字节预算,正文仍要 `memory_read`。只有当前任务条件判为 `ok` 的条目能进;条件没确认或明确不符的既不进自动注入,也不进检索结果,检索只在全都被挡时说明「有相关的项目记忆,但适用条件还没确认 / 当前任务明确不适用」。没重合为空字符串,整条索引不进上下文,也不改 hits / lastUsed,更不落盘。用户这句话没提到动作、一张卡都没命中时,再用上一轮助手的话和工具调用里的实词补一次有界查询(预算不变);补不到就保留第一次的「为什么没提醒」。同一句话只算一次;换句话重新算。
20
20
 
21
21
  ## 触发后长什么样
22
22
 
package/client.js CHANGED
@@ -94,7 +94,7 @@ window.__ModuleLoader__.load({
94
94
  scope: meta.scope === 'global' ? 'global' : 'project',
95
95
  revision: Number.isInteger(meta.revision) ? meta.revision : 1,
96
96
  status: typeof meta.status === 'string' ? meta.status : 'ok',
97
- origin: meta.origin === 'project_evolution' ? 'project_evolution' : 'user_explicit',
97
+ origin: meta.origin === 'project_evolution' || meta.origin === 'model_drafted' ? meta.origin : 'user_explicit',
98
98
  kind,
99
99
  exposure: kind === 'memory-save' || kind === 'memory-forget' ? 'update' : 'reference',
100
100
  /** 条目类型:读正文看 recordKind,检索看 ref 自己的 kind;工具名不是类型。 */
@@ -568,12 +568,32 @@ window.__ModuleLoader__.load({
568
568
  };
569
569
 
570
570
  const versionLine = (shown) => {
571
- const scope = shown.scopeLabel || (shown.scope === 'global' ? '全局' : '项目');
571
+ /** 项目名只当分层后缀:光写目录名会被当成记忆标题。 */
572
+ const scope = shown.scope === 'global' ? '全局'
573
+ : shown.scopeLabel ? `项目 · ${shown.scopeLabel}` : '项目';
572
574
  const archived = shown.status === 'archived' ? ' · 已归档' : '';
573
575
  const processing = processingStatusOf(shown.processingStatus ? { processingStatus: shown.processingStatus } : shown);
574
576
  return `${scope}${archived}${processing ? ` · ${PROCESSING_LABEL[processing]}` : ''}`;
575
577
  };
576
578
 
579
+ /** 展开的正文紧跟被点的那条;顺序由列表决定,不挂在一段末尾。 */
580
+ const refSectionRowsOf = (items, shown, renderRow, renderDetail) => {
581
+ const rows = [];
582
+ for (const item of items) {
583
+ rows.push(renderRow(item));
584
+ if (shown && shown.ref && shown.key === item.key && shown.ref.revision === item.revision) rows.push(renderDetail(item));
585
+ }
586
+ return rows;
587
+ };
588
+
589
+ /** 正文抬头永远是记忆标题;项目/全局只做下面的来源行。 */
590
+ const refDetailTitleOf = (shown) => {
591
+ const title = shown && shown.ref && typeof shown.ref.title === 'string' ? shown.ref.title.trim() : '';
592
+ if (title) return title;
593
+ const fallback = shown && typeof shown.title === 'string' ? shown.title.trim() : '';
594
+ return fallback || '记忆';
595
+ };
596
+
577
597
  /**
578
598
  * 引用三层的色调:中性 / 强调 / 成功。
579
599
  * 三档色阶:中性 / 强调 / 成功;正文用主文本色。
@@ -599,7 +619,8 @@ window.__ModuleLoader__.load({
599
619
  };
600
620
  const refMetaOf = (item) => {
601
621
  /** 会话里只标层与来源:类型留给面板和工具,不占引用行的宽度。 */
602
- const layer = `${item.scope === 'global' ? '[全局]' : '[项目]'}${item.origin === 'project_evolution' ? '[自进化]' : ''}`;
622
+ /** 来源只标注信任差别:自进化规则与模型整理稿都不会因此升级层级。 */
623
+ const layer = `${item.scope === 'global' ? '[全局]' : '[项目]'}${item.origin === 'project_evolution' ? '[自进化]' : item.origin === 'model_drafted' ? '[模型]' : ''}`;
603
624
  const parts = [];
604
625
  const ago = agoLabel(item.updated);
605
626
  if (ago) parts.push(ago);
@@ -624,8 +645,9 @@ window.__ModuleLoader__.load({
624
645
  onMouseEnter: () => setHover(true),
625
646
  onMouseLeave: () => setHover(false),
626
647
  onClick: props.onShow, // ui-copy-ignore
627
- 'aria-label': `查看记忆 ${item.title || '记忆'}${reason ? `,${reason}` : ''}`,
628
- title: reason ? `${item.title || '记忆'},${reason}` : (item.title || '记忆'),
648
+ 'aria-expanded': Boolean(props.active),
649
+ 'aria-label': `${props.active ? '收起' : '查看'}记忆 ${item.title || '记忆'}${reason ? `,${reason}` : ''}`,
650
+ title: props.active ? `收起 ${item.title || '记忆'}` : (reason ? `${item.title || '记忆'},${reason}` : (item.title || '记忆')),
629
651
  style: { // ui-copy-ignore
630
652
  display: 'flex', alignItems: 'flex-start', gap: '7px', width: '100%', minWidth: 0, textAlign: 'left',
631
653
  border: 'none', borderRadius: '8px', padding: '3px 7px', cursor: 'pointer', color: 'inherit',
@@ -674,8 +696,11 @@ window.__ModuleLoader__.load({
674
696
  setOpen(false);
675
697
  };
676
698
  const show = (item) => {
677
- if (shown && shown.key === item.key) {
678
- close();
699
+ /** 再点同一条只收起正文,列表留着,方便接着看别的。 */
700
+ if (shown && shown.key === item.key && shown.ref && shown.ref.revision === item.revision) {
701
+ generation.current += 1;
702
+ setShown(null);
703
+ setNote('');
679
704
  return;
680
705
  }
681
706
  const request = generation.current + 1;
@@ -710,20 +735,13 @@ window.__ModuleLoader__.load({
710
735
  });
711
736
  });
712
737
  };
713
- const rows = open ? items.map((item) => h(RefRow, {
714
- key: `${item.key}|${item.revision}`, // ui-copy-ignore
715
- item, tone, active: Boolean(shown && shown.key === item.key),
716
- onShow: () => show(item),
717
- })) : [];
718
- const detail = shown && shown.ref && items.some((item) => item.key === shown.key && item.revision === shown.ref.revision)
719
- ? h('div', { style: { ...REF_BODY, borderLeft: `2px solid ${tone}` } },
738
+ const detail = shown && shown.ref
739
+ ? h('div', { style: { ...REF_BODY, margin: '2px 0 4px 7px', padding: '2px 2px 4px 10px', borderLeft: `2px solid ${tone}` } },
720
740
  shown.loading ? h('span', { style: REF_META }, '读取中…') : null,
721
- shown.gone ? h('span', { style: REF_META }, '这条记忆已删除') : null,
741
+ shown.gone ? h('span', { style: REF_META }, '这条记忆已经不在这里了') : null,
722
742
  shown.invalid ? h('span', { style: REF_META }, '读不到正文,点名字重试') : null,
723
743
  !shown.loading && !shown.gone && !shown.invalid ? h(React.Fragment, null,
724
- shown.title && shown.title !== shown.ref.title
725
- ? h('div', { style: { fontSize: '12px', lineHeight: '1.55', fontWeight: 600 } }, shown.title)
726
- : null,
744
+ h('div', { style: { fontSize: '13px', lineHeight: '1.5', fontWeight: 600, wordBreak: 'break-word' } }, refDetailTitleOf(shown)),
727
745
  h('div', { style: REF_META }, versionLine(shown)),
728
746
  shown.when ? h('div', { style: REF_META }, shown.when) : null,
729
747
  shown.body
@@ -734,6 +752,13 @@ window.__ModuleLoader__.load({
734
752
  note ? h('span', { style: REF_META }, note) : null,
735
753
  )
736
754
  : null;
755
+ const rows = open
756
+ ? refSectionRowsOf(items, shown, (item) => h(RefRow, {
757
+ key: `${item.key}|${item.revision}`, // ui-copy-ignore
758
+ item, tone, active: Boolean(shown && shown.ref && shown.key === item.key && shown.ref.revision === item.revision),
759
+ onShow: () => show(item),
760
+ }), () => detail)
761
+ : [];
737
762
  return h('div', { style: DOCK, 'data-memory-section': props.label },
738
763
  h('button', {
739
764
  type: 'button',
@@ -752,7 +777,6 @@ window.__ModuleLoader__.load({
752
777
  h(RefChevron, { open }),
753
778
  ),
754
779
  ...rows,
755
- detail,
756
780
  );
757
781
  };
758
782
 
@@ -870,10 +894,22 @@ window.__ModuleLoader__.load({
870
894
  explainBus.add(listener);
871
895
  return () => explainBus.delete(listener);
872
896
  };
897
+ /** 召回回执只说“多久以前”,不跟列表的“更新”混用。 */
898
+ const shortAgo = (value) => {
899
+ const at = Date.parse(value);
900
+ if (!Number.isFinite(at)) return '';
901
+ const minutes = Math.floor((Date.now() - at) / 60_000);
902
+ if (minutes < 1) return '刚刚';
903
+ if (minutes < 60) return `${minutes} 分钟前`;
904
+ const hours = Math.floor(minutes / 60);
905
+ if (hours < 24) return `${hours} 小时前`;
906
+ const days = Math.floor(hours / 24);
907
+ return days < 30 ? `${days} 天前` : '';
908
+ };
873
909
  /**
874
910
  * 面板只说判断结果,不说内部原因码;条数来自上一次自动召回的回执。
875
911
  */
876
- const recallLineOf = (receipt) => {
912
+ const recallTextOf = (receipt) => {
877
913
  if (!receipt || typeof receipt !== 'object') return '';
878
914
  const cards = Number.isInteger(receipt.cards) ? receipt.cards : 0;
879
915
  const matched = Number.isInteger(receipt.matched) ? receipt.matched : 0;
@@ -882,12 +918,20 @@ window.__ModuleLoader__.load({
882
918
  case 'hit': return cards > 0 ? `上次自动提醒 ${cards} 条` : '';
883
919
  case 'budget': return `上次没提醒,${related}本轮自动提醒已用满`;
884
920
  case 'needs_context': return `上次没提醒,${related}适用条件未确认`;
921
+ case 'conflict': return `上次没提醒,${related}存在冲突的结论`;
885
922
  case 'no_overlap': return '上次没提醒,没有匹配的记忆';
923
+ case 'expired': return '上次没提醒,相关内容已经过期';
886
924
  case 'memory_off': return '上次没提醒,记忆已关闭';
887
925
  case 'tools_only': return '上次没提醒,只在你问时检索';
888
926
  default: return '';
889
927
  }
890
928
  };
929
+ /** 回执可能是上一轮甚至更早的:带上时间,别让旧提醒看起来像刚发生。 */
930
+ const recallLineOf = (receipt) => {
931
+ const text = recallTextOf(receipt);
932
+ const when = receipt && typeof receipt === 'object' ? shortAgo(receipt.at) : '';
933
+ return text && when ? `${text} · ${when}` : text;
934
+ };
891
935
  /** 功能关闭时视图统一回列表,不保留一个画不出来的页签。 */
892
936
  const graphViewOf = (enabled, view) => (enabled === true && view === 'graph' ? 'graph' : 'list');
893
937
  const viewModeOf = (display, view) => {
@@ -2194,7 +2238,7 @@ window.__ModuleLoader__.load({
2194
2238
  post('/lite-memory/body', { sessionId, id: selectedMemory, scopeKey: data.selected.key }).then((result) => {
2195
2239
  if (!live) return;
2196
2240
  if (result.ok && result.body && result.body.ok) {
2197
- if (result.body.status === 'gone') setDetailNote('这条记忆已删除');
2241
+ if (result.body.status === 'gone') setDetailNote('这条记忆已经不在这里了');
2198
2242
  else setDetail(result.body);
2199
2243
  } else setDetailNote(failure(result, '读不到正文'));
2200
2244
  });
@@ -2210,6 +2254,8 @@ window.__ModuleLoader__.load({
2210
2254
  ? selected.control
2211
2255
  : (current && selected && selected.key === current.key && current.control ? current.control : { enabled: true });
2212
2256
  const memoryOn = control.enabled !== false;
2257
+ const memoryMode = !memoryOn ? 'off'
2258
+ : (control.effectiveMode === 'tool_only' || control.mode === 'tool_only' ? 'tool_only' : 'adaptive');
2213
2259
  const evolution = selected && selected.evolution
2214
2260
  ? selected.evolution
2215
2261
  : (current && selected && selected.key === current.key ? current.evolution : null);
@@ -2374,6 +2420,7 @@ window.__ModuleLoader__.load({
2374
2420
  || !fresh.ok || !fresh.body || !fresh.body.ok || fresh.body.status === 'gone') return;
2375
2421
  const revision = Number.isInteger(fresh.body.revision) ? fresh.body.revision : 0;
2376
2422
  setDetail(fresh.body);
2423
+ setEditNote('已载入最新内容,再保存会覆盖它');
2377
2424
  setData((prev) => {
2378
2425
  if (!prev || !prev.selected || !Array.isArray(prev.selected.records)) return prev;
2379
2426
  const records = prev.selected.records.map((row) => (row.id === record.id ? { ...row, revision: revision || row.revision } : row));
@@ -2385,14 +2432,45 @@ window.__ModuleLoader__.load({
2385
2432
  });
2386
2433
  };
2387
2434
 
2388
- const toggleControl = () => {
2389
- if (busy || !canToggle) return;
2435
+ /** 适用范围待确认是模型留下的不确定;人点一次就把它变成明确结论,别再让模型猜。 */
2436
+ const confirmMemoryScope = (record) => {
2437
+ if (busy || !canManage || !record || !record.id) return;
2438
+ const text = currentBodyOf(detail) || '';
2439
+ if (!text) return;
2390
2440
  setBusy(true);
2391
- post('/lite-memory/control', { sessionId, enabled: !memoryOn, operationId: newOperationId() }).then((result) => {
2441
+ post('/lite-memory/entry', {
2442
+ sessionId, scopeKey: selectedKey, id: record.id,
2443
+ title: record.title || detail.title || '', body: text,
2444
+ expectedRevision: record.revision, applicability: 'all_in_scope', conditions: [],
2445
+ }).then((result) => {
2446
+ if (!alive.current || (selectedKey && scopeRef.current !== selectedKey)) return;
2447
+ setBusy(false);
2448
+ if (!succeeded(result)) {
2449
+ setNote(failure(result, '适用范围没有改动'));
2450
+ return;
2451
+ }
2452
+ const revision = result.body && Number.isInteger(result.body.revision) ? result.body.revision : record.revision;
2453
+ setDetail((prev) => (prev ? { ...prev, applicability: 'all_in_scope', conditions: [], revision } : prev));
2454
+ setData((prev) => {
2455
+ if (!prev || !prev.selected || !Array.isArray(prev.selected.records)) return prev;
2456
+ const records = prev.selected.records.map((row) => (row.id === record.id
2457
+ ? { ...row, applicability: 'all_in_scope', conditions: [], revision } : row));
2458
+ return { ...prev, selected: { ...prev.selected, records } };
2459
+ });
2460
+ });
2461
+ };
2462
+
2463
+ const setControlMode = (mode) => {
2464
+ if (busy || !canToggle || mode === memoryMode) return;
2465
+ setBusy(true);
2466
+ const body = mode === 'off'
2467
+ ? { sessionId, enabled: false, operationId: newOperationId() }
2468
+ : { sessionId, enabled: true, mode, operationId: newOperationId() };
2469
+ post('/lite-memory/control', body).then((result) => {
2392
2470
  if (!alive.current || (selectedKey && scopeRef.current !== selectedKey)) return;
2393
2471
  setBusy(false);
2394
2472
  if (succeeded(result)) load(selectedKey);
2395
- else setNote(failure(result, '开关没有改动'));
2473
+ else setNote(failure(result, '触发方式没有改动'));
2396
2474
  });
2397
2475
  };
2398
2476
 
@@ -2460,6 +2538,25 @@ window.__ModuleLoader__.load({
2460
2538
  } : undefined,
2461
2539
  );
2462
2540
 
2541
+ const modeChoices = () => h('div', {
2542
+ style: { display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0 },
2543
+ },
2544
+ h('span', { style: { fontSize: '12px', lineHeight: '20px', fontWeight: 600, whiteSpace: 'nowrap' } }, '记忆'),
2545
+ h('div', {
2546
+ role: 'radiogroup', 'aria-label': '记忆触发方式',
2547
+ style: { display: 'flex', gap: '2px', padding: '2px', borderRadius: '8px', background: 'var(--dsw-alias-bg-layer-2)', border: '1px solid var(--dsw-alias-border-l1)' },
2548
+ },
2549
+ [['adaptive', '自动提醒'], ['tool_only', '仅搜索'], ['off', '关闭']].map(([key, text]) => h('button', {
2550
+ key, type: 'button', role: 'radio', 'aria-checked': memoryMode === key, disabled: busy || !canToggle,
2551
+ onClick: canToggle && !busy ? () => setControlMode(key) : undefined,
2552
+ style: {
2553
+ padding: '2px 7px', minHeight: '22px', border: 'none', borderRadius: '6px',
2554
+ background: memoryMode === key ? 'var(--dsw-alias-bg-layer-1)' : 'transparent',
2555
+ color: 'inherit', fontSize: '11px', lineHeight: '18px', fontWeight: memoryMode === key ? 600 : 400,
2556
+ cursor: canToggle && !busy ? 'pointer' : 'default', whiteSpace: 'nowrap',
2557
+ opacity: memoryMode === key || canToggle ? 1 : 0.55,
2558
+ },
2559
+ }, text))));
2463
2560
  const switchItem = (label, hint, on, disabled, onClick) => h('div', {
2464
2561
  style: { display: 'flex', flexDirection: 'column', gap: '1px', minWidth: 0, opacity: disabled && !on ? 0.55 : 1 },
2465
2562
  },
@@ -2476,7 +2573,7 @@ window.__ModuleLoader__.load({
2476
2573
  globalView
2477
2574
  ? h('div', { style: { ...PANEL_MUTED, lineHeight: '20px' } }, '全局记忆只读')
2478
2575
  : h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(190px, 1fr))', gap: '4px 18px' } },
2479
- switchItem('启用记忆', memoryOn ? '读取项目和全局记忆' : '记忆已关闭,内容只读', memoryOn, busy || !canToggle, toggleControl),
2576
+ modeChoices(),
2480
2577
  switchItem('自进化', evolutionOn ? '实验功能,会增加 token 消耗' : '从反馈里整理经验', evolutionOn, busy || !evolution || !canToggle || !memoryOn, toggleEvolution), // ui-copy-ignore 用户指定文案
2481
2578
  showSettings ? displayToggle('启用图谱', 'graph') : null,
2482
2579
  showSettings ? displayToggle('启用记忆宫殿', 'palace') : null,
@@ -2549,12 +2646,23 @@ window.__ModuleLoader__.load({
2549
2646
  h('div', { style: { flex: 1, minWidth: 0, fontSize: '13px', lineHeight: '1.45', fontWeight: 600, wordBreak: 'break-word' } }, detail.title),
2550
2647
  h(Action, { onClick: () => setSelectedMemory('') }, '关闭'),
2551
2648
  ),
2552
- h('div', { style: PANEL_MUTED }, `${KIND_LABEL[detailRecord && detailRecord.kind] || '记忆'} · ${detail.scopeLabel || ''}${detailProcessing ? ` · ${PROCESSING_LABEL[detailProcessing]}` : ''}`),
2649
+ h('div', { style: PANEL_MUTED }, `${KIND_LABEL[detailRecord && detailRecord.kind] || '记忆'} · ${detail.scopeLabel || ''}${detailRecord && detailRecord.origin === 'model_drafted' ? ' · [模型]' : ''}${detailProcessing ? ` · ${PROCESSING_LABEL[detailProcessing]}` : ''}`),
2553
2650
  Array.isArray(detailRecord && detailRecord.tags) && detailRecord.tags.length
2554
2651
  ? h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '4px' } },
2555
2652
  detailRecord.tags.slice(0, 6).map((tag) => h('span', { key: tag, style: { ...PANEL_MUTED, padding: '0 6px', border: '1px solid var(--dsw-alias-border-l1)', borderRadius: '999px' } }, `#${tag}`))) : null,
2556
2653
  memoryEditor,
2557
2654
  editingMemory ? null : h('div', { style: { fontSize: '12px', lineHeight: '1.65', whiteSpace: 'pre-wrap', wordBreak: 'break-word' } }, currentBodyOf(detail) || '没有正文'),
2655
+ !editingMemory && detailRecord && detailRecord.applicability === 'unresolved'
2656
+ ? h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' } },
2657
+ h('span', { style: PANEL_MUTED }, '适用范围待确认'),
2658
+ canManage
2659
+ ? h(Action, {
2660
+ disabled: busy || !currentBodyOf(detail),
2661
+ 'aria-label': '标为所有任务都适用',
2662
+ onClick: () => confirmMemoryScope(detailRecord),
2663
+ }, '都适用')
2664
+ : null)
2665
+ : null,
2558
2666
  editingMemory ? null : h(DetailExtras, { payload: detail }),
2559
2667
  !editingMemory && detailRecord && canManage
2560
2668
  ? h('div', { style: { display: 'flex', alignItems: 'center', gap: '12px', flexWrap: 'wrap', paddingTop: '2px' } },
@@ -2850,7 +2958,7 @@ window.__ModuleLoader__.load({
2850
2958
 
2851
2959
  // 测试缝:Node 里验引用投影,浏览器里没有任何副作用。
2852
2960
  if (typeof globalThis.__LITE_MEMORY_TEST__ === 'function') {
2853
- globalThis.__LITE_MEMORY_TEST__({ metaOf, citationOf, citationsOfMeta, citationsOfTurn, dedupe, undoRefOf, graphOf, ruleGraphOf, processingStatusOf, currentBodyOf, historyDiffsOf, MemoryPanel, MemoryGraph, MemoryPalace, TurnMemory, RefSection, displayKeyOf, readDisplaySettings, writeDisplaySettings, graphViewOf, viewModeOf, filterRecords, projectionOf, failureStateOf, evidenceTextOf, evidenceMetaOf, palaceRoomsOf, palaceRoomPageOf, palacePageWindow, palaceToggleOf, palaceMotionOf, palaceRoomIdOf, refMetaOf, explainKeyOf, explainModeOf, setExplainMode, recallLineOf, agoLabel, checkupOf, titleCountsOf, rowKeyOf, rowKeepFlags, Row });
2961
+ globalThis.__LITE_MEMORY_TEST__({ metaOf, citationOf, citationsOfMeta, citationsOfTurn, dedupe, undoRefOf, graphOf, ruleGraphOf, processingStatusOf, currentBodyOf, historyDiffsOf, MemoryPanel, MemoryGraph, MemoryPalace, TurnMemory, RefSection, displayKeyOf, readDisplaySettings, writeDisplaySettings, graphViewOf, viewModeOf, filterRecords, projectionOf, failureStateOf, evidenceTextOf, evidenceMetaOf, palaceRoomsOf, palaceRoomPageOf, palacePageWindow, palaceToggleOf, palaceMotionOf, palaceRoomIdOf, refMetaOf, explainKeyOf, explainModeOf, setExplainMode, recallLineOf, agoLabel, checkupOf, refSectionRowsOf, refDetailTitleOf, versionLine, titleCountsOf, rowKeyOf, rowKeepFlags, Row });
2854
2962
  }
2855
2963
 
2856
2964
  return {
@@ -36,9 +36,12 @@
36
36
  - 归档条目、实验来源(普通自动召回不碰,实验规则单独走 `experimentFor`)、`legacy_unreviewed` 全部排除。
37
37
  - 同一 `topicKey` 槽位只让胜出的记录进;槽位内冲突或条件未知的整组不进自动召回,不猜哪条对。
38
38
  - 默认值 `__tasks.defaults` 会参与任务上下文,但只是「范围提示」,不是把某条记忆设成必用。
39
+ - 「继续 / 然后呢」这类续说句没有检索词时,沿用同会话上一轮原话;别把新的短问题接到旧查询上。
39
40
 
40
41
  **预算**:普通卡片与实验规则**共用**一份账本:最多 3 张卡片、最终拼接文本最多 1200 字节。字节量的是 provider 最终看到的完整文本(含标题行、ID、换行、两段之间的空行),不是单张正文。超预算时跳过这张,不截半句、不把标题留在模型侧。
41
42
 
43
+ **实际 Token**:自动提示最多 3 条、1200 字节,不随全库条数线性增长;总量仍取决于命中频率、模型调用步数、主动读取正文,以及实验性自进化的整理次数。别把「每轮有上限」说成「不增加消耗」。
44
+
42
45
  **失败**:
43
46
 
44
47
  - 准备超过 `RECALL_DEADLINE_MS`、被取消、投影期间开关/代次变了:整批回退,预算退还,下一轮重新来。绝不发布半旧卡片。
@@ -77,7 +80,13 @@
77
80
  - `topicKey` 是槽位;同一槽位只允许一条当前胜出记录,冲突时先让人选,不自动合并。
78
81
  - `kind` 只影响展示分组,不参与检索和可用性判断。
79
82
 
80
- **范围路由**:模型给的 `scope` 是提案,不是命令。设备事实必须带设备条件(`machine` / `targetOs` / `runtime` / `shell` 至少一个);正文或依据里出现本机路径、回环地址、端口的「通用流程」,先留在项目层或问一次条件,不直接写全局。
83
+ **范围路由**:模型给的 `scope` 是提案,不是命令。
84
+
85
+ - 显式 `scope="global"` 表示「用户在这一次对话里要求存全局」,只有用户确实这么说才传;模型自己觉得通用不是理由。显式全局不再因为缺少 `evidenceRef` 被降级。
86
+ - 仍然会拦住的两类:设备事实没有条件(`machine` / `targetOs` / `runtime` / `shell` 至少一个),以及正文或依据里出现本机路径、回环地址、端口的「通用流程」——后者换台机器/换个项目就不成立,留在项目层。
87
+ - `memoryClass="project_fact"` 不因写了 `global` 就变全局:项目事实换个项目本来就不成立。
88
+ - 按类别推导(没有显式 `scope`)保持更严:`portable_procedure` 没有 `evidenceRef` 就落项目层。
89
+ - 每次降级都要能解释:只在降级时返回 `scopeNote` 一行,说明为什么没进全局、怎么改才进得去;正常落全局或落项目不带这行。
81
90
 
82
91
  **限额与腾位**:每层活跃条目软上限 200、硬上限 1000;单条正文 4096 字节;单层总字节 1 MiB。到硬上限时把最久没被读的活跃条目移进归档,并在结果里写明「已归档「标题」腾位」,不拒绝新记忆;单条正文超限只报错、不截断。
83
92
 
@@ -0,0 +1,6 @@
1
+ # 0.18.16:适用范围由人确认一次
2
+
3
+ - 面板详情里,适用范围还是「待确认」的记忆多一个「都适用」动作:标题、正文、版本照旧带上,只补适用范围。
4
+ - 确认后写入 `all_in_scope` 和空条件;这条记忆才进入自动提醒,不再要求模型再猜一次。
5
+ - 服务端只接受三档取值,其余拒绝;确认按版本写,过期仍返回 409,不覆盖别处改动。
6
+ - 适用范围不再只有模型能补;人看一眼就能定下来,别条不受影响。
@@ -0,0 +1,6 @@
1
+ # 0.18.17:沾一个词不再算命中
2
+
3
+ - 查询里有两个以上中文实词时,只重合一个词就不再给标题 / 关键词分:「启动服务器」不会带出「启动失败」那条改包名的记忆。
4
+ - 单实词查询(「发版」「样式」)不受影响,照常召回;没有抬高全局阈值,同义说法仍能进提示。
5
+ - 中文切词补出来的二元组先去重;只有切词结果全是单字时才补,避免重复词把命中数虚增。
6
+ - 中英混排查询(「排查 PostgreSQL 连接池报错」)按中文词边界补二元组,不会因为英文词整段丢掉中文。
@@ -0,0 +1,7 @@
1
+ # 0.18.18:触发方式按项目选
2
+
3
+ - 面板把「启用记忆」换成一排选择:自动提醒 / 仅搜索 / 关闭。选「仅搜索」后不再主动弹卡片,工具检索照常;选「关闭」等同原来的总开关,写入与学习一起停。
4
+ - 触发方式存在项目自己的状态里,不改插件配置,也不影响其他项目;没有选过的项目仍跟随插件默认。改方式会换代次,关闭前排队的召回不会补交。
5
+ - 记忆开关和触发方式在同一把锁里落盘,不会只改一半;关闭时切换方式不会顺手把记忆打开。
6
+ - 编辑碰到别人先改过时,面板载入最新正文并说明「已载入最新内容,再保存会覆盖它」,不再只报一句通用失败。
7
+ - 引用点开时条目已归档或移走,改说「这条记忆已经不在这里了」,不再误称已删除。
@@ -0,0 +1,7 @@
1
+ # 0.18.19
2
+
3
+ - 保存失败回执走统一形状:带 `error` 字段、过输出 schema,界面显示具体原因,不再出现 `saved "undefined"`。
4
+ - `memory_read` 的关联清单带上标题,关联卡片不再显示 undefined。
5
+ - 作用域推断收窄:「所有口播」「一律」不再被当成全项目授权;无可信任务条件时留待确认。
6
+ - 实验规则参与本轮相关度排序,和普通记忆共用 3 张卡片 / 1200 字节预算;条件已满足的规则仍按绑定注入。
7
+ - 契约测试按已注册 `output.schema` 校验成功与失败回执。
@@ -0,0 +1,4 @@
1
+ # 0.18.20
2
+
3
+ - 读取与检索回执带上来源:模型整理稿显示 `[模型]`,只作信任标记,不改变项目/全局层级;自进化仍是 `[自进化]`。
4
+ - 自动提醒的未命中原因细分:条件未确认与结论冲突分别给出,不再一律说「没有匹配」。
@@ -0,0 +1,4 @@
1
+ # 0.18.21
2
+
3
+ - 自动提示的词面匹配按整词比对英文:查询里的英文词没出现时,不再因为二元组恰好重合把不相关的记忆带进来;中文重合仍照常计分。
4
+ - 中英混排查询保留中文命中:英文专名没出现在条目里,不会把中文部分当成的命中一起清零。
@@ -0,0 +1,5 @@
1
+ # 0.18.22
2
+
3
+ - 自动召回跟上「继续」:只有明确的续说句(继续 / 接着 / 然后 / go on / continue)沿用同会话上一轮原话,短问题不拼旧上下文。
4
+ - 召回回执按真正发出的内容记:投影后过期、发出时被撤掉的卡片不再在面板上算作「已提醒」;全部过期时显示「相关内容已经过期」。
5
+ - 面板原因文案补 `expired` 一档,不再把过期说成「没有匹配的记忆」。
@@ -0,0 +1,5 @@
1
+ # 0.18.23
2
+
3
+ - 回执只在内容真正发出后保留:投影完成后被超时、取消、换代或回滚挡下的卡片,不再在面板上显示「已提醒」。
4
+ - 取消请求时同步撤掉回执(`clearRecall`),模型没见过的提醒不留在面板上。
5
+ - 过期/被取代的旧记录不参与同名合并、也不触发 `needsResolution` 的回归断言补齐(store 测试 73 条)。
@@ -0,0 +1,3 @@
1
+ # 0.18.24
2
+
3
+ - 面板的召回回执带上「多久以前」:旧提醒不再看起来像刚发生,也不再借列表的「更新」字样。
@@ -0,0 +1,5 @@
1
+ # 0.18.25
2
+
3
+ - 会话末尾的记忆列表:展开的正文直接放在被点的那条下面,标题用记忆自己的标题;再点一次只收起正文,不再把整个列表关掉。
4
+ - 用户明确说「存全局」时,不再因为模型自己觉得不够通用而被悄悄降级;本机路径/端口和项目事实仍留在项目层,并给出降级说明。
5
+ - 预防型记忆在用户只说「这个怎么弄」时不漏:用户原话没命中,就用上一轮助手的话和工具名补一次有界查询,仍受同一份召回预算。
@@ -0,0 +1,5 @@
1
+ # 0.18.26
2
+
3
+ - 只在本机成立、但跨项目复用的命令/流程(例如本机 npm 包装命令):声明 machine 或 runtime 条件后可以进全局层,换台机器条件不成立就不会拿出来用;仍然写死本机路径/端口又没声明环境的,继续留在项目层。
4
+ - 显式存全局的降级说明补了一句怎么改:补机器条件后可以存全局,不用把规则改写成一个不存在的通用说法。
5
+ - CI 稳定性:召回投影的耗时上限可以在测试里放宽(CI 冷启动偶尔超过 150ms,逻辑断言不该被「这轮先不注入」判失败);旧库正文比对按行尾归一(Windows 检出把 LF 转成 CRLF)。Windows 与 Ubuntu 六格矩阵全绿。