@yottameta/yotta-memory 0.19.0 → 0.20.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.
@@ -25,7 +25,7 @@ const net = require('net');
25
25
  const child_process = require('child_process');
26
26
  const { AsyncLocalStorage } = require('async_hooks');
27
27
 
28
- const VERSION = '0.19.0';
28
+ const VERSION = '0.20.1';
29
29
  const CLI_VALUE_OPTS = new Set(['--type', '--limit', '--days', '--out', '--owner', '--agent', '--agent-id', '--agent-key', '--agent-key-file', '--plugin-data', '--threshold', '--scope', '--host', '--port', '--dir', '--name', '--user', '--relationship', '--source', '--weight', '--budget', '--password', '--new-password', '--recovery-key', '--recovery-key-out', '--reason', '--merge', '--model', '--subject', '--embedding', '--focus', '--embedding-timeout', '--min-age', '--min-idle', '--max-utility', '--min-group', '--period', '--to', '--id', '--time', '--tools', '--mcp-config', '--skill-dir', '--year', '--evalset', '--k', '--seed', '--bootstrap', '--gate', '--against', '--template', '--path', '--from']);
30
30
  const CLI_FLAG_OPTS = new Set(['--project', '--all', '--unsafe', '--no-auth', '--stdio', '--onstart', '--from-current', '--restart', '--force', '--attach', '--allow-same-volume', '--verify', '--no-hint', '--encrypt', '--no-encrypt', '--password-stdin', '--json', '--manual', '--skip-schedule', '--useful', '--useless', '--undo', '--dry-run', '--apply', '--purge', '--dedup', '--batches', '--propose', '--audit', '--capacity', '--rules', '--explain', '--semantic', '--runtime', '--ablate', '--timing', '--baseline', '--probe', '--quarantine', '--restore', '--no-usage', '--yes', '--keep-memories', '--keep-identity']);
31
31
 
@@ -7460,30 +7460,55 @@ function cmdWhoami(opts) {
7460
7460
 
7461
7461
  // ---- 用户画像聚合(v0.6.0,零推断:只归组呈现原文,结论由承载 AI 依据「记忆守则」内部形成)----
7462
7462
  function profileGroups(root, owner) {
7463
- const groups = [];
7464
- const map = {};
7463
+ const entries = [];
7465
7464
  for (const t of PRIVATE_LEAF) {
7466
7465
  const dir = path.join(root, PRIVATE_DIR, owner, t);
7467
7466
  if (!fs.existsSync(dir)) continue;
7468
- for (const f of fs.readdirSync(dir)) {
7469
- if (!/\.md(\.enc)?$/.test(f)) continue;
7470
- const fp = path.join(dir, f);
7471
- if (!fs.statSync(fp).isFile()) continue;
7467
+ const files = [];
7468
+ walkEntryFiles(dir, files);
7469
+ for (const fp of files) {
7472
7470
  let e;
7473
7471
  try { e = readEntry(fp, root); } catch (err) { continue; }
7474
- const key = e.type + '\u0000' + (e.subject || '') + '\u0000' + e.tags.join(',');
7475
- if (!map[key]) {
7476
- map[key] = { type: e.type, subject: e.subject || '', tags: e.tags || [], items: [] };
7477
- groups.push(map[key]);
7478
- }
7479
- map[key].items.push({
7480
- file: e.file, statement: e.statement, confidence: e.confidence,
7481
- last_accessed: e.last_accessed, updated: e.updated,
7482
- });
7472
+ entries.push(e);
7483
7473
  }
7484
7474
  }
7475
+ return profileGroupsFromEntries(entries);
7476
+ }
7477
+ function profileGroupsFromEntries(entries) {
7478
+ const groups = [];
7479
+ const map = {};
7480
+ for (const e of (entries || [])) {
7481
+ const key = e.type + '\u0000' + (e.subject || '') + '\u0000' + (e.tags || []).join(',');
7482
+ if (!map[key]) {
7483
+ map[key] = { type: e.type, subject: e.subject || '', tags: e.tags || [], items: [] };
7484
+ groups.push(map[key]);
7485
+ }
7486
+ map[key].items.push({
7487
+ file: e.file, statement: e.statement, confidence: e.confidence,
7488
+ last_accessed: e.last_accessed, updated: e.updated,
7489
+ });
7490
+ }
7485
7491
  return groups;
7486
7492
  }
7493
+ function renderProfileText(owner, groups) {
7494
+ const lines = [];
7495
+ lines.push('# 用户画像(' + owner + ')');
7496
+ lines.push('');
7497
+ lines.push('> 生成: yotta-memory profile | 引擎零推断:以下为私密记忆原文的结构化归组,画像结论由承载 AI 依据「记忆守则」在内部形成,不当面贴标签。');
7498
+ lines.push('> 刷新: ' + today());
7499
+ lines.push('');
7500
+ for (const g of (groups || [])) {
7501
+ lines.push('## ' + g.type + ' · ' + (g.subject || '(无主题)') + (g.tags.length ? ' [tags: ' + g.tags.join(', ') + ']' : ''));
7502
+ lines.push('');
7503
+ for (const it of g.items) {
7504
+ const acc = it.last_accessed ? '最近访问 ' + it.last_accessed : '未访问';
7505
+ lines.push('- ' + it.statement + '(confidence ' + it.confidence + ' · ' + acc + ')');
7506
+ lines.push(' - ' + it.file);
7507
+ }
7508
+ lines.push('');
7509
+ }
7510
+ return lines.join('\n');
7511
+ }
7487
7512
  function profileCore(opts) {
7488
7513
  opts = opts || {};
7489
7514
  const root = userRoot();
@@ -7511,23 +7536,7 @@ function profileCore(opts) {
7511
7536
  }
7512
7537
  const groups = profileGroups(root, owner);
7513
7538
  if (!groups.length) return { error: false, text: '该智能体(' + owner + ')暂无画像:private/' + owner + '/ 下无 PREF / BOUND / COMMIT 条目。' };
7514
- const lines = [];
7515
- lines.push('# 用户画像(' + owner + ')');
7516
- lines.push('');
7517
- lines.push('> 生成: yotta-memory profile | 引擎零推断:以下为私密记忆原文的结构化归组,画像结论由承载 AI 依据「记忆守则」在内部形成,不当面贴标签。');
7518
- lines.push('> 刷新: ' + today());
7519
- lines.push('');
7520
- for (const g of groups) {
7521
- lines.push('## ' + g.type + ' · ' + (g.subject || '(无主题)') + (g.tags.length ? ' [tags: ' + g.tags.join(', ') + ']' : ''));
7522
- lines.push('');
7523
- for (const it of g.items) {
7524
- const acc = it.last_accessed ? '最近访问 ' + it.last_accessed : '未访问';
7525
- lines.push('- ' + it.statement + '(confidence ' + it.confidence + ' · ' + acc + ')');
7526
- lines.push(' - ' + it.file);
7527
- }
7528
- lines.push('');
7529
- }
7530
- const text = lines.join('\n');
7539
+ const text = renderProfileText(owner, groups);
7531
7540
  const outFile = writeProfileText(root, owner, text + '\n');
7532
7541
  return { error: false, text: text + '\n\n[已写入] ' + outFile };
7533
7542
  }
@@ -7566,6 +7575,25 @@ function compareContextRecent(a, b) {
7566
7575
  const MEMORY_HOOK_MAX_CANDIDATES = 500;
7567
7576
  const MEMORY_HOOK_STATEMENT_LIMIT = 2000;
7568
7577
 
7578
+ function contextEntryLine(entry) {
7579
+ return '- [' + entry.type + '] ' + entry.subject + ': ' + entry.statement;
7580
+ }
7581
+
7582
+ function isSelfProfileEntry(entry, owner) {
7583
+ if (!entry || entry.type !== 'PREF' || entry.subject !== SELF_PROFILE_SUBJECT) return false;
7584
+ if (owner && entry.owner && entry.owner !== owner) return false;
7585
+ const file = String(entry.file || '').replace(/\\/g, '/');
7586
+ return owner ? file.indexOf('private/' + owner + '/prefs/') === 0 : false;
7587
+ }
7588
+
7589
+ function contextPageableEntries(readableEntries, owner) {
7590
+ return (readableEntries || [])
7591
+ .filter(function (entry) {
7592
+ return entry.type !== 'BOUND' && entry.type !== 'COMMIT' && !isSelfProfileEntry(entry, owner);
7593
+ })
7594
+ .sort(compareContextRecent);
7595
+ }
7596
+
7569
7597
  function hookBlock(status, providerId, note) {
7570
7598
  return {
7571
7599
  status: status || 'not_installed',
@@ -7590,6 +7618,130 @@ function hookStatusText(hook) {
7590
7618
  return hook.status;
7591
7619
  }
7592
7620
 
7621
+ function pagingBlock(status, providerId, note) {
7622
+ return {
7623
+ status: status || 'not_installed',
7624
+ provider_id: providerId || '',
7625
+ applied: false,
7626
+ dropped: [],
7627
+ ordered: [],
7628
+ truncated: false,
7629
+ budget: 0,
7630
+ used: 0,
7631
+ note: note || '',
7632
+ };
7633
+ }
7634
+
7635
+ function pagingStatusText(paging) {
7636
+ if (!paging) return '';
7637
+ if (paging.status === 'active') {
7638
+ const who = paging.provider_id ? '提供方 ' + paging.provider_id : '提供方';
7639
+ return '已应用(' + who + ';丢弃 ' + paging.dropped.length + ' / 排序 ' + paging.ordered.length + ')';
7640
+ }
7641
+ if (paging.status === 'license_required') return '需授权(该能力需要授权后使用;普通记忆不受影响)';
7642
+ if (paging.status === 'timeout') return '未生效(提供方超时,已回落普通记忆)';
7643
+ if (paging.status === 'invalid_output') return '未生效(提供方输出无效,已回落普通记忆)';
7644
+ if (paging.status === 'error') return '未生效(提供方异常,已回落普通记忆)';
7645
+ return '';
7646
+ }
7647
+
7648
+ function pagingCandidatePayload(entry, baselineSection) {
7649
+ return {
7650
+ file: entry.file,
7651
+ type: entry.type,
7652
+ subject: entry.subject,
7653
+ statement: String(entry.statement || '').slice(0, MEMORY_HOOK_STATEMENT_LIMIT),
7654
+ created: entry.created || '',
7655
+ updated: entry.updated || '',
7656
+ chars: contextEntryLine(entry).length,
7657
+ baseline_section: baselineSection || 'high_value',
7658
+ };
7659
+ }
7660
+
7661
+ function applyContextPaging(pageable, opts, meta) {
7662
+ opts = opts || {};
7663
+ meta = meta || {};
7664
+ const budget = parseInt(opts.budget || 0, 10) || 0;
7665
+ if (budget <= 0) {
7666
+ return {
7667
+ block: pagingBlock('not_requested', '', '未传 --budget,未调用 context.paging'),
7668
+ drop: new Set(),
7669
+ order: null,
7670
+ applied: false,
7671
+ };
7672
+ }
7673
+ const block = pagingBlock('not_installed', '', '');
7674
+ block.budget = budget;
7675
+ const truncated = pageable.length > MEMORY_HOOK_MAX_CANDIDATES;
7676
+ block.truncated = truncated;
7677
+ const candidates = pageable.slice(0, MEMORY_HOOK_MAX_CANDIDATES);
7678
+ const payload = {
7679
+ agent: opts.owner || '',
7680
+ budget: budget,
7681
+ focus: opts.focus || '',
7682
+ truncated: truncated,
7683
+ protected: Array.isArray(meta.protected) ? meta.protected.slice() : [],
7684
+ sections: meta.sections || {},
7685
+ candidates: candidates.map(function (entry) {
7686
+ return pagingCandidatePayload(entry, (meta.baselineSections || {})[entry.file]);
7687
+ }),
7688
+ };
7689
+ let run;
7690
+ try {
7691
+ run = require('./provider').runCapability('context.paging', payload);
7692
+ } catch (e) {
7693
+ block.status = 'error';
7694
+ block.note = '预算分页失败:' + e.message;
7695
+ return { block: block, drop: new Set(), order: null, applied: false };
7696
+ }
7697
+ block.status = run.status;
7698
+ block.provider_id = run.provider_id || '';
7699
+ block.note = run.note || run.message || '';
7700
+ if (run.status !== 'active' || !run.data || typeof run.data !== 'object') {
7701
+ return { block: block, drop: new Set(), order: null, applied: false };
7702
+ }
7703
+ const sent = new Set(payload.candidates.map(function (item) { return item.file; }));
7704
+ const rejected = [];
7705
+ const dropped = [];
7706
+ for (const item of (Array.isArray(run.data.drop) ? run.data.drop : [])) {
7707
+ const file = String(item || '');
7708
+ if (!file) continue;
7709
+ if (!sent.has(file)) {
7710
+ if (rejected.indexOf(file) === -1) rejected.push(file);
7711
+ continue;
7712
+ }
7713
+ if (dropped.indexOf(file) === -1) dropped.push(file);
7714
+ }
7715
+ let order = null;
7716
+ if (Array.isArray(run.data.order)) {
7717
+ if (run.data.complete === true && !truncated) {
7718
+ const picked = [];
7719
+ for (const item of run.data.order) {
7720
+ const file = String(item || '');
7721
+ if (!file) continue;
7722
+ if (!sent.has(file)) {
7723
+ if (rejected.indexOf(file) === -1) rejected.push(file);
7724
+ continue;
7725
+ }
7726
+ if (picked.indexOf(file) === -1) picked.push(file);
7727
+ }
7728
+ order = picked;
7729
+ } else if (truncated) {
7730
+ block.note = block.note || '候选超过 ' + MEMORY_HOOK_MAX_CANDIDATES + ',order 未应用(保护)';
7731
+ } else {
7732
+ block.note = block.note || 'provider 未声明 complete=true,order 未应用(保护)';
7733
+ }
7734
+ }
7735
+ if (rejected.length) {
7736
+ const text = '候选集外文件已忽略:' + rejected.join(', ');
7737
+ block.note = block.note ? block.note + ';' + text : text;
7738
+ }
7739
+ block.dropped = dropped;
7740
+ block.ordered = order || [];
7741
+ block.applied = dropped.length > 0 || (order !== null && order.length > 0);
7742
+ return { block: block, drop: new Set(dropped), order: order, applied: block.applied };
7743
+ }
7744
+
7593
7745
  /**
7594
7746
  * memory.hook 装载点:只把「可驱逐」条目(非 BOUND / COMMIT)交给 provider。
7595
7747
  * provider 只能返回本次候选集内的 evict(驱逐清单);complete=true 时可选 selected(白名单)。
@@ -7598,9 +7750,7 @@ function hookStatusText(hook) {
7598
7750
  */
7599
7751
  function applyMemoryHook(readableEntries, opts) {
7600
7752
  const block = hookBlock('not_installed', '', '');
7601
- const pageable = readableEntries
7602
- .filter(function (entry) { return entry.type !== 'BOUND' && entry.type !== 'COMMIT'; })
7603
- .sort(compareContextRecent);
7753
+ const pageable = contextPageableEntries(readableEntries, opts.owner || '');
7604
7754
  const truncated = pageable.length > MEMORY_HOOK_MAX_CANDIDATES;
7605
7755
  const payload = {
7606
7756
  agent: opts.owner || '',
@@ -7665,6 +7815,25 @@ function applyMemoryHook(readableEntries, opts) {
7665
7815
  return { block: block, evict: new Set(evicted), selected: selected };
7666
7816
  }
7667
7817
 
7818
+ function replaceContextProfileSection(lines, profileText, fence) {
7819
+ const start = lines.indexOf('## 2. 用户画像摘要');
7820
+ if (start === -1) return false;
7821
+ const marker = lines.indexOf('## 2.5 长期理解摘要');
7822
+ const end = marker === -1 ? lines.length : marker;
7823
+ if (end < start) return false;
7824
+ const block = ['## 2. 用户画像摘要', ''];
7825
+ if (profileText) {
7826
+ block.push(fence + 'markdown');
7827
+ block.push(String(profileText).trim());
7828
+ block.push(fence);
7829
+ } else {
7830
+ block.push('(该身份暂无画像;可运行 yotta-memory profile 生成)');
7831
+ }
7832
+ block.push('');
7833
+ lines.splice.apply(lines, [start, end - start].concat(block));
7834
+ return true;
7835
+ }
7836
+
7668
7837
  function contextCore(opts) {
7669
7838
  opts = opts || {};
7670
7839
  const roots = memoryRoots();
@@ -7698,6 +7867,7 @@ function contextCore(opts) {
7698
7867
  const lines = [];
7699
7868
  const shownFiles = new Set();
7700
7869
  const usedFiles = new Set();
7870
+ let usedDynamicChars = 0;
7701
7871
  const FENCE = String.fromCharCode(96, 96, 96);
7702
7872
  function usedChars() { return lines.reduce(function (s, x) { return s + String(x).length + 1; }, 0); }
7703
7873
  function appendContextEntry(entry, reason, options) {
@@ -7707,7 +7877,7 @@ function contextCore(opts) {
7707
7877
  trace.push('[dropped] ' + entry.file + ' reason: duplicate');
7708
7878
  return false;
7709
7879
  }
7710
- const line = '- [' + entry.type + '] ' + entry.subject + ': ' + entry.statement;
7880
+ const line = contextEntryLine(entry);
7711
7881
  if (options.budget !== false && budget > 0 && usedChars() + line.length > budget) {
7712
7882
  trace.push('[dropped] ' + entry.file + ' reason: budget_exceeded');
7713
7883
  return false;
@@ -7715,6 +7885,7 @@ function contextCore(opts) {
7715
7885
  lines.push(line);
7716
7886
  shownFiles.add(entry.file);
7717
7887
  usedFiles.add(entry.file);
7888
+ usedDynamicChars += line.length;
7718
7889
  trace.push('[included] ' + entry.file + ' reason: ' + reason);
7719
7890
  return true;
7720
7891
  }
@@ -7784,19 +7955,124 @@ function contextCore(opts) {
7784
7955
  if (memHook.evict.has(entry.file)) return false;
7785
7956
  return true;
7786
7957
  }
7958
+ let focusedEntries = [];
7959
+ if (focus) {
7960
+ const focused = recallCore(focus, {
7961
+ limit: limit,
7962
+ owner: owner,
7963
+ unsafe: unsafe,
7964
+ selfAgent: selfAgent,
7965
+ years: years,
7966
+ embedding: embeddingCommand,
7967
+ embeddingTimeout: embeddingTimeout,
7968
+ noUsage: true
7969
+ });
7970
+ focusedEntries = focused.entries || [];
7971
+ }
7972
+ const pageable = contextPageableEntries(readableEntries, owner).filter(hookAllows);
7973
+ const summaryLimit = Math.min(3, Math.max(0, limit));
7974
+ const summaryCandidates = pageable.filter(isConsolidatedSummary).slice(0, summaryLimit);
7975
+ const summaryFiles = new Set(summaryCandidates.map(function (entry) { return entry.file; }));
7976
+ const pageableFiles = new Set(pageable.map(function (entry) { return entry.file; }));
7977
+ const focusCandidates = focusedEntries.filter(function (entry) {
7978
+ return hookAllows(entry) && pageableFiles.has(entry.file);
7979
+ });
7980
+ const focusFiles = new Set(focusCandidates.map(function (entry) { return entry.file; }));
7981
+ const corridorCandidates = pageable
7982
+ .filter(function (entry) {
7983
+ return !isConsolidatedSummary(entry) && !summaryFiles.has(entry.file) && !focusFiles.has(entry.file);
7984
+ })
7985
+ .slice(0, limit);
7986
+ const corridorFiles = new Set(corridorCandidates.map(function (entry) { return entry.file; }));
7987
+ const highValueCandidates = pageable
7988
+ .filter(function (entry) {
7989
+ return !isConsolidatedSummary(entry) && !summaryFiles.has(entry.file) && !focusFiles.has(entry.file) && !corridorFiles.has(entry.file);
7990
+ })
7991
+ .map(function (entry) { return { e: entry, s: 0.5 * importanceScore(entry) + 0.5 * utilityScore(entry) }; })
7992
+ .sort(function (a, b) { return b.s - a.s; })
7993
+ .slice(0, limit)
7994
+ .map(function (item) { return item.e; });
7995
+ const baselineSections = {};
7996
+ function markBaseline(entries, section) {
7997
+ for (const entry of entries) baselineSections[entry.file] = section;
7998
+ }
7999
+ markBaseline(summaryCandidates, 'summary');
8000
+ markBaseline(focusCandidates, 'focus');
8001
+ markBaseline(corridorCandidates, 'corridor');
8002
+ markBaseline(highValueCandidates, 'high_value');
8003
+ const paging = applyContextPaging(pageable, {
8004
+ owner: owner,
8005
+ budget: budget,
8006
+ focus: focus,
8007
+ limit: limit,
8008
+ }, {
8009
+ protected: readableEntries
8010
+ .filter(function (entry) { return isSelfProfileEntry(entry, owner); })
8011
+ .map(function (entry) { return entry.file; }),
8012
+ sections: {
8013
+ summary: summaryCandidates.map(function (entry) { return entry.file; }),
8014
+ focus: focusCandidates.map(function (entry) { return entry.file; }),
8015
+ corridor: corridorCandidates.map(function (entry) { return entry.file; }),
8016
+ high_value: highValueCandidates.map(function (entry) { return entry.file; }),
8017
+ },
8018
+ baselineSections: baselineSections,
8019
+ });
8020
+ const pagingState = paging.block;
8021
+ const pagingOrder = new Map();
8022
+ for (let i = 0; i < pagingState.ordered.length; i++) pagingOrder.set(pagingState.ordered[i], i);
8023
+ function pagingAllows(entry) {
8024
+ return !entry || !paging.drop.has(entry.file);
8025
+ }
8026
+ function decisionAllows(entry) {
8027
+ return hookAllows(entry) && pagingAllows(entry);
8028
+ }
8029
+ function pagingOrderIndex(entry) {
8030
+ if (!entry || !pagingOrder.has(entry.file)) return Number.MAX_SAFE_INTEGER;
8031
+ return pagingOrder.get(entry.file);
8032
+ }
8033
+ function compareWithPaging(a, b, fallback) {
8034
+ const byOrder = pagingOrderIndex(a) - pagingOrderIndex(b);
8035
+ if (byOrder !== 0) return byOrder;
8036
+ return fallback(a, b);
8037
+ }
8038
+ if ((hookState.applied || pagingState.applied) && owner) {
8039
+ const liveGroups = profileGroupsFromEntries(
8040
+ readableEntries
8041
+ .filter(function (entry) { return entry.type === 'PREF' || entry.type === 'BOUND' || entry.type === 'COMMIT'; })
8042
+ .filter(decisionAllows)
8043
+ );
8044
+ replaceContextProfileSection(lines, renderProfileText(owner, liveGroups), FENCE);
8045
+ trace.push('[profile] source: live reason: ' + (hookState.applied ? 'memory.hook' : 'context.paging'));
8046
+ }
8047
+ const statusLines = [];
7787
8048
  if (hookState.status !== 'not_installed') {
7788
- const anchor = lines.indexOf('## 1. 身份');
7789
- lines.splice(anchor === -1 ? 2 : anchor, 0, '- 扩展装载(memory.hook): ' + hookStatusText(hookState), '');
8049
+ statusLines.push('- 扩展装载(memory.hook): ' + hookStatusText(hookState));
7790
8050
  trace.push('[hook] status: ' + hookState.status
7791
8051
  + ' provider: ' + (hookState.provider_id || '-')
7792
8052
  + ' evicted: ' + hookState.evicted.length
7793
8053
  + ' dropped: ' + hookState.dropped.length);
7794
8054
  }
8055
+ const pagingText = pagingStatusText(pagingState);
8056
+ if (pagingText) {
8057
+ statusLines.push('- 预算分页(context.paging): ' + pagingText);
8058
+ trace.push('[paging] status: ' + pagingState.status
8059
+ + ' provider: ' + (pagingState.provider_id || '-')
8060
+ + ' dropped: ' + pagingState.dropped.length
8061
+ + ' ordered: ' + pagingState.ordered.length
8062
+ + ' truncated: ' + (pagingState.truncated ? 'true' : 'false'));
8063
+ }
8064
+ if (statusLines.length) {
8065
+ const anchor = lines.indexOf('## 1. 身份');
8066
+ statusLines.push('');
8067
+ lines.splice.apply(lines, [anchor === -1 ? 2 : anchor, 0].concat(statusLines));
8068
+ }
7795
8069
 
7796
8070
  lines.push('## 2.5 长期理解摘要');
7797
8071
  lines.push('');
7798
- const summaries = readableEntries.filter(isConsolidatedSummary).filter(hookAllows).sort(compareContextRecent);
7799
- const summaryLimit = Math.min(3, Math.max(0, limit));
8072
+ const summaries = readableEntries
8073
+ .filter(isConsolidatedSummary)
8074
+ .filter(decisionAllows)
8075
+ .sort(function (a, b) { return compareWithPaging(a, b, compareContextRecent); });
7800
8076
  if (!summaries.length) lines.push('(暂无周期摘要;可用 yotta-memory consolidate --apply 生成)');
7801
8077
  for (const e of summaries.slice(0, summaryLimit)) {
7802
8078
  appendContextEntry(e, 'summary_priority', { budget: false });
@@ -7806,19 +8082,11 @@ function contextCore(opts) {
7806
8082
  if (focus) {
7807
8083
  lines.push('## 2.6 任务相关记忆(--focus)');
7808
8084
  lines.push('');
7809
- const focused = recallCore(focus, {
7810
- limit: limit,
7811
- owner: owner,
7812
- unsafe: unsafe,
7813
- selfAgent: selfAgent,
7814
- years: years,
7815
- embedding: embeddingCommand,
7816
- embeddingTimeout: embeddingTimeout,
7817
- noUsage: true
7818
- });
7819
- const focusedEntries = focused.entries || [];
7820
8085
  if (!focusedEntries.length) lines.push('(无匹配记忆)');
7821
- for (const e of focusedEntries.filter(hookAllows)) {
8086
+ const focusedVisible = focusedEntries
8087
+ .filter(decisionAllows)
8088
+ .sort(function (a, b) { return compareWithPaging(a, b, function () { return 0; }); });
8089
+ for (const e of focusedVisible) {
7822
8090
  appendContextEntry(e, 'focus_match score: ' + round3(e.score));
7823
8091
  }
7824
8092
  lines.push('');
@@ -7828,8 +8096,8 @@ function contextCore(opts) {
7828
8096
  lines.push('');
7829
8097
  const corridor = readableEntries
7830
8098
  .filter(function (e) { return !isConsolidatedSummary(e) && e.type !== 'BOUND' && e.type !== 'COMMIT'; })
7831
- .sort(compareContextRecent)
7832
- .filter(hookAllows)
8099
+ .filter(decisionAllows)
8100
+ .sort(function (a, b) { return compareWithPaging(a, b, compareContextRecent); })
7833
8101
  .filter(function (e) { return !shownFiles.has(e.file); })
7834
8102
  .slice(0, limit);
7835
8103
  if (!corridor.length) lines.push('(暂无近期记忆)');
@@ -7839,9 +8107,9 @@ function contextCore(opts) {
7839
8107
  lines.push('## 4. 近期高价值记忆(补位)');
7840
8108
  lines.push('');
7841
8109
  const highValue = readableEntries
7842
- .filter(function (e) { return !isConsolidatedSummary(e) && e.type !== 'BOUND' && e.type !== 'COMMIT' && !shownFiles.has(e.file) && hookAllows(e); })
8110
+ .filter(function (e) { return !isConsolidatedSummary(e) && e.type !== 'BOUND' && e.type !== 'COMMIT' && !shownFiles.has(e.file) && decisionAllows(e); })
7843
8111
  .map(function (e) { return { e: e, s: 0.5 * importanceScore(e) + 0.5 * utilityScore(e) }; })
7844
- .sort(function (a, b) { return b.s - a.s; })
8112
+ .sort(function (a, b) { return compareWithPaging(a.e, b.e, function (x, y) { return y.s - x.s; }); })
7845
8113
  .slice(0, limit);
7846
8114
  if (!highValue.length) lines.push('(暂无补位条目)');
7847
8115
  for (const h of highValue) appendContextEntry(h.e, 'high_value_backfill');
@@ -7899,13 +8167,14 @@ function contextCore(opts) {
7899
8167
  for (const message of reliability.warnings) lines.push('- [警告] ' + message);
7900
8168
  if (!reliability.ok) lines.push('- 破坏性写入已锁定:先运行 yotta-memory doctor 修复严重问题。');
7901
8169
  }
7902
- return { error: false, exitCode: 0, text: lines.join('\n'), trace: trace, hook: hookState };
8170
+ pagingState.used = usedDynamicChars;
8171
+ return { error: false, exitCode: 0, text: lines.join('\n'), trace: trace, hook: hookState, paging: pagingState };
7903
8172
  }
7904
8173
  function cmdContext(opts) {
7905
8174
  const r = contextCore(opts);
7906
8175
  if (opts.json) {
7907
8176
  if (r.report) console.log(JSON.stringify(r.report, null, 2));
7908
- else console.log(JSON.stringify({ schema: 1, hook: r.hook || null, text: r.text }, null, 2));
8177
+ else console.log(JSON.stringify({ schema: 1, hook: r.hook || null, paging: r.paging || null, text: r.text }, null, 2));
7909
8178
  } else {
7910
8179
  console.log(r.text);
7911
8180
  }