@yottameta/yotta-memory 0.22.5 → 0.22.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/SKILL.md +1 -1
- package/assets/view.html +40 -6
- package/bin/install.js +12 -1
- package/bin/yotta-memory.js +376 -130
- package/install.sh +10 -1
- package/package.json +1 -1
- package/skill-manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## v0.22.7 (2026-10-09)
|
|
2
|
+
|
|
3
|
+
- **管理台 / 官网「配置键」补全人话说明**:高级 / CLI 页的 39 个配置键不再只显示「键名 + 命令」,每个键改为「键名 + 它是做什么的 + 默认值 / 取值 / 什么时候改」,与命令选项同一排版;键说明为单一真源(`CONFIG_KEY_HELP`),管理台 `/api/help` 与官网 `/product/` 同步渲染。
|
|
4
|
+
- 新增配置键说明覆盖回归:每个 `CONFIG_VALUE_KEYS` 键必须有非空 `what` / `when` 说明,且不允许出现孤儿说明(说明里存在但键列表没有)。
|
|
5
|
+
|
|
6
|
+
## v0.22.6 (2026-10-09)
|
|
7
|
+
|
|
8
|
+
- **并发写入加固**:索引更新改为跨进程互斥(`.index.lock`,mkdir 原子语义 + 陈旧锁自动回收)+ 原子落盘(临时文件 + rename);序号分配 / 记忆落盘 / 索引更新同处一个临界区,多智能体(多宿主、多 MCP 进程)同时写入更稳。
|
|
9
|
+
- **索引一致性维护**:以记忆文件为唯一真源,读取路径与用户平台在后台做轻量一致性校准(自动对齐,无需用户操作;不修改任何记忆文件)。
|
|
10
|
+
- **管理台搜索增强**:搜索支持按编号(文件名)与归属智能体查找(原支持标题 / 内容 / 标签)。
|
|
11
|
+
- **管理台 CLI 页补全配置键**:高级 / CLI 页新增 39 个配置键速查(`config set` 单一真源,含 `memory_home` / `backup_*` / `maintain_*` / `consolidate_*` / `scale_*` 等),可直接搜索与复制。
|
|
12
|
+
- **doctor**:新增索引一致性检查(条数 = 文件数)。
|
|
13
|
+
- **安装器洁净度**:顶层跳过并清理 `*.tgz` 与 `test-fixtures/`(与 npm 包白名单一致),安装副本不再携带源码侧残留。
|
|
14
|
+
- 新增 `test/index-consistency.test.js` 5 项(一致性检测与校准、幂等、6 进程并发 120 条零丢失、跨进程锁互斥、显示与实际对账 + 编号 / 归属搜索)。
|
|
15
|
+
- 新增 `test/password-stdin-bom.test.js`(Windows PowerShell 管道 BOM)与 `test/installer-hygiene.test.js`(薄片洁净度 + 残留清理)。
|
|
16
|
+
|
|
1
17
|
## v0.22.5 (2026-10-07)
|
|
2
18
|
|
|
3
19
|
- **修复 Electron 宿主(YottaCode shim)下开机自启拉起 GUI 而非服务**:宿主运行时为 Electron 时(`process.versions.electron`;`process.execPath` 指向 Electron 可执行文件),此前生成的任务 / 兜底 / 定时配置不带 `ELECTRON_RUN_AS_NODE=1`,开机拉起的是桌面程序本体、8787 无监听(新机首例实测)。现在自动检测并统一注入:Windows 主任务与每日备份任务(cmd /c + `set "ELECTRON_RUN_AS_NODE=1"`)、Windows Startup 兜底 .cmd(set 前缀)、Linux systemd 单元 / crontab @reboot / 备份定时器(`Environment=` / 行内前缀)、macOS 备份 LaunchAgent(EnvironmentVariables)。
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yotta-memory
|
|
3
3
|
description: 元忆 —— 有权限边界的文件式智能体记忆。文件式、零依赖、可 diff/可回滚:让任何 AI 智能体活过会话,开工 recall 恢复上下文、重要信息 remember 落盘、收工归档。类型体系 FACT(公共共享)/ PREF / BOUND / COMMIT(私密隔离)。触发:记住、别忘了、记一笔、记忆、remember、recall、跨会话、上次说到、续测、交接、归档、记忆盘、共享记忆、局域网记忆、画像、开工上下文、长期理解摘要、近期走廊、会话闭环、记忆守则、profile、context、越用越懂、语义检索、反馈、维护、蒸馏、feedback、maintain、distill、explain、自我学习、自我进化、自我提升、查看平台分页、recall 候选预过滤、任务相关记忆、--focus、--embedding、压缩遗忘、consolidate、周期摘要、自动合并、分类型衰减、回滚、备份、backup、防误删、doctor、事务快照、基线探针、恢复演练、记忆库安全扫描、scan、命中打点、容量水位、--capacity、--propose、上下文压缩审计、context --audit
|
|
4
|
-
version: 0.22.
|
|
4
|
+
version: 0.22.7
|
|
5
5
|
license: MIT
|
|
6
6
|
---
|
|
7
7
|
|
package/assets/view.html
CHANGED
|
@@ -157,6 +157,8 @@
|
|
|
157
157
|
.opt-what{color:var(--muted)}
|
|
158
158
|
.opt-when{color:var(--faint)}
|
|
159
159
|
.opt-risk{color:var(--danger)}
|
|
160
|
+
.opt-row.with-copy{grid-template-columns:minmax(150px,auto) minmax(0,1fr) auto;align-items:start}
|
|
161
|
+
.opt-row.with-copy .ibtn{width:26px;height:26px}
|
|
160
162
|
|
|
161
163
|
@media (max-width:960px){.g4{grid-template-columns:repeat(2,minmax(0,1fr))}.bars{grid-template-columns:repeat(2,minmax(0,1fr))}.quick-list{grid-template-columns:minmax(0,1fr)}}
|
|
162
164
|
@media (max-width:760px){
|
|
@@ -250,7 +252,7 @@
|
|
|
250
252
|
|
|
251
253
|
<section class="view" id="view-memories">
|
|
252
254
|
<div class="card toolbar">
|
|
253
|
-
<label class="search" style="min-width:280px;flex:1"><svg class="sv" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><input id="memSearch" placeholder="搜索记忆(标题 / 内容 /
|
|
255
|
+
<label class="search" style="min-width:280px;flex:1"><svg class="sv" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><input id="memSearch" placeholder="搜索记忆(标题 / 内容 / 标签 / 编号)"></label>
|
|
254
256
|
<div class="chips" id="typeChips">
|
|
255
257
|
<button class="chip active" data-type="all">全部</button>
|
|
256
258
|
<button class="chip" data-type="FACT">FACT</button>
|
|
@@ -258,6 +260,7 @@
|
|
|
258
260
|
<button class="chip" data-type="BOUND">BOUND</button>
|
|
259
261
|
<button class="chip" data-type="COMMIT">COMMIT</button>
|
|
260
262
|
</div>
|
|
263
|
+
<select id="ownerFilter" title="按智能体筛选" style="width:auto;min-width:140px"><option value="">全部智能体</option></select>
|
|
261
264
|
<div class="spacer"></div>
|
|
262
265
|
<button class="btn" id="btnArchived">已藏</button>
|
|
263
266
|
<button class="btn" id="btnTrash">最近删除</button>
|
|
@@ -329,7 +332,7 @@
|
|
|
329
332
|
(function () {
|
|
330
333
|
'use strict';
|
|
331
334
|
var $ = function (s) { return document.querySelector(s); };
|
|
332
|
-
var state = { view: 'overview', entries: [], count: 0, offset: 0, limit: 20, query: '', type: 'all', owners: [], overview: null, current: null, help: null };
|
|
335
|
+
var state = { view: 'overview', entries: [], count: 0, offset: 0, limit: 20, query: '', type: 'all', owner: '', owners: [], overview: null, current: null, help: null };
|
|
333
336
|
var typeNames = { FACT: 'FACT 事实', PREF: 'PREF 偏好', BOUND: 'BOUND 边界', COMMIT: 'COMMIT 承诺' };
|
|
334
337
|
|
|
335
338
|
function esc(s) {
|
|
@@ -437,14 +440,24 @@
|
|
|
437
440
|
$('#ovBytes').textContent = fmtBytes(d.bytes);
|
|
438
441
|
$('#accBackup').textContent = d.backupDir || '未配置';
|
|
439
442
|
var colors = { FACT: 'var(--gold)', PREF: '#b79cdd', BOUND: '#8fb4de', COMMIT: 'var(--ok)' };
|
|
443
|
+
var byType = d.byType || {};
|
|
444
|
+
var typeCounts = { all: d.total || 0, FACT: byType.FACT || 0, PREF: byType.PREF || 0, BOUND: byType.BOUND || 0, COMMIT: byType.COMMIT || 0 };
|
|
440
445
|
$('#typeBars').innerHTML = ['FACT', 'PREF', 'BOUND', 'COMMIT'].map(function (t) {
|
|
441
|
-
|
|
442
|
-
return '<div class="bar"><span class="dot" style="background:' + colors[t] + '"></span><span class="faint">' + typeNames[t] + '</span><b>' + n + '</b></div>';
|
|
446
|
+
return '<div class="bar" data-type="' + t + '" style="cursor:pointer" title="查看 ' + typeNames[t] + '"><span class="dot" style="background:' + colors[t] + '"></span><span class="faint">' + typeNames[t] + '</span><b>' + typeCounts[t] + '</b></div>';
|
|
443
447
|
}).join('');
|
|
448
|
+
Array.prototype.forEach.call(document.querySelectorAll('#typeChips .chip'), function (c) {
|
|
449
|
+
c.textContent = (c.dataset.type === 'all' ? '全部' : c.dataset.type) + ' ' + (typeCounts[c.dataset.type] || 0);
|
|
450
|
+
});
|
|
444
451
|
}
|
|
445
452
|
async function loadOwners() {
|
|
446
453
|
var d = await api('/api/owners');
|
|
447
454
|
state.owners = (d && d.owners) || [];
|
|
455
|
+
var sel = $('#ownerFilter');
|
|
456
|
+
if (sel) {
|
|
457
|
+
sel.innerHTML = '<option value="">全部智能体</option>' + state.owners.map(function (o) {
|
|
458
|
+
return '<option value="' + esc(o.owner) + '"' + (o.owner === state.owner ? ' selected' : '') + '>' + esc(o.owner) + '</option>';
|
|
459
|
+
}).join('');
|
|
460
|
+
}
|
|
448
461
|
var box = $('#ownerRows');
|
|
449
462
|
if (!state.owners.length) { box.innerHTML = '<tr><td colspan="5" class="faint">' + esc((d && d.hint) || '当前没有 owner,请先在 CLI 执行 yotta-memory iam <id>。') + '</td></tr>'; return; }
|
|
450
463
|
box.innerHTML = state.owners.map(function (o) {
|
|
@@ -456,7 +469,7 @@
|
|
|
456
469
|
}).join('');
|
|
457
470
|
}
|
|
458
471
|
async function loadEntries() {
|
|
459
|
-
var d = await api('/api/entries', { query: state.query, offset: state.offset, limit: state.limit, type: state.type === 'all' ? '' : state.type });
|
|
472
|
+
var d = await api('/api/entries', { query: state.query, offset: state.offset, limit: state.limit, type: state.type === 'all' ? '' : state.type, owner: state.owner || '' });
|
|
460
473
|
if (d.error) { toast(errText(d, '加载失败')); return; }
|
|
461
474
|
state.entries = d.entries || [];
|
|
462
475
|
state.count = d.count || 0;
|
|
@@ -669,6 +682,15 @@
|
|
|
669
682
|
Array.prototype.forEach.call(document.querySelectorAll('#typeChips .chip'), function (c) { c.classList.toggle('active', c === b); });
|
|
670
683
|
loadEntries();
|
|
671
684
|
});
|
|
685
|
+
$('#typeBars').addEventListener('click', function (e) {
|
|
686
|
+
var bar = e.target.closest('.bar[data-type]');
|
|
687
|
+
if (!bar) return;
|
|
688
|
+
state.type = bar.dataset.type; state.offset = 0;
|
|
689
|
+
Array.prototype.forEach.call(document.querySelectorAll('#typeChips .chip'), function (c) { c.classList.toggle('active', c.dataset.type === state.type); });
|
|
690
|
+
switchView('memories');
|
|
691
|
+
loadEntries();
|
|
692
|
+
});
|
|
693
|
+
$('#ownerFilter').addEventListener('change', function () { state.owner = this.value; state.offset = 0; loadEntries(); });
|
|
672
694
|
$('#prevPage').addEventListener('click', function () { if (state.offset > 0) { state.offset -= state.limit; loadEntries(); } });
|
|
673
695
|
$('#nextPage').addEventListener('click', function () { state.offset += state.limit; loadEntries(); });
|
|
674
696
|
|
|
@@ -803,7 +825,7 @@
|
|
|
803
825
|
}
|
|
804
826
|
function renderFull() {
|
|
805
827
|
if (!state.help) return;
|
|
806
|
-
|
|
828
|
+
var groupsHtml = (state.help.groups || []).map(function (g) {
|
|
807
829
|
var cmds = (g.commands || []).map(function (c) {
|
|
808
830
|
var subs = (c.subcommands || []).map(function (s) {
|
|
809
831
|
return '<div class="help-sub"><div class="cmd-head"><code class="cmd-name">' + esc(c.name + ' ' + s.name) + '</code></div>' +
|
|
@@ -817,6 +839,18 @@
|
|
|
817
839
|
}).join('');
|
|
818
840
|
return '<div class="quick-group"><h4>' + esc(g.group) + '</h4>' + cmds + '</div>';
|
|
819
841
|
}).join('');
|
|
842
|
+
var keys = state.help.configKeys || [];
|
|
843
|
+
var keyHelp = state.help.configKeyHelp || {};
|
|
844
|
+
var keysHtml = keys.length ? '<div class="quick-group" id="cliConfigKeys"><h4>配置键(config set)</h4>' +
|
|
845
|
+
'<div class="cmd-when">全部可配置键(共 ' + keys.length + ' 个)。用法:yotta-memory config set <键> <值>,改完用 yotta-memory config get 复查;配置保存在 ~/.yottamemory/config.json。</div>' +
|
|
846
|
+
keys.map(function (k) {
|
|
847
|
+
var h = keyHelp[k] || {};
|
|
848
|
+
return '<div class="opt-row with-copy"><div class="opt-flag">' + esc(k) + '</div><div>' +
|
|
849
|
+
'<div class="opt-what">' + esc(h.what || '') + '</div>' +
|
|
850
|
+
(h.when ? '<div class="opt-when">' + esc(h.when) + '</div>' : '') +
|
|
851
|
+
'</div>' + copyBtn('yotta-memory config set ' + k + ' <值>') + '</div>';
|
|
852
|
+
}).join('') + '</div>' : '';
|
|
853
|
+
$('#cliFull').innerHTML = groupsHtml + keysHtml;
|
|
820
854
|
}
|
|
821
855
|
async function ensureHelp() {
|
|
822
856
|
if (state.help) { renderQuick(); return; }
|
package/bin/install.js
CHANGED
|
@@ -27,7 +27,7 @@ const PKG_ROOT = path.join(__dirname, '..');
|
|
|
27
27
|
const TOP_SKIP = [
|
|
28
28
|
'package.json', 'package-lock.json', 'bin', 'lib', 'test',
|
|
29
29
|
'.github', '.git', '.gitignore', '.npmignore', '.gitattributes',
|
|
30
|
-
'.yotta', '.tmp', 'install.sh', 'node_modules',
|
|
30
|
+
'.yotta', '.tmp', 'install.sh', 'node_modules', 'test-fixtures',
|
|
31
31
|
];
|
|
32
32
|
const TOP_SKIP_SET = new Set(TOP_SKIP);
|
|
33
33
|
// 缓存 / 编译产物:任意层级跳过,安装时同样清理。
|
|
@@ -190,6 +190,7 @@ function copyDir(src, dst, topLevel) {
|
|
|
190
190
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
191
191
|
// 只跳过安装包顶层开发文件;template/ 等嵌套同名载荷必须保留。
|
|
192
192
|
if (topLevel && TOP_SKIP_SET.has(entry.name)) continue;
|
|
193
|
+
if (topLevel && entry.isFile() && /\.tgz$/i.test(entry.name)) continue;
|
|
193
194
|
if (shouldSkipCache(entry.name, entry.isFile())) continue;
|
|
194
195
|
const s = path.join(src, entry.name);
|
|
195
196
|
const d = path.join(dst, entry.name);
|
|
@@ -219,6 +220,16 @@ function cleanResidue(target) {
|
|
|
219
220
|
fs.rmSync(p, { recursive: true, force: true });
|
|
220
221
|
removed.push(name);
|
|
221
222
|
}
|
|
223
|
+
try {
|
|
224
|
+
for (const name of fs.readdirSync(target)) {
|
|
225
|
+
if (!/\.tgz$/i.test(name)) continue;
|
|
226
|
+
const p = path.join(target, name);
|
|
227
|
+
const entry = fs.lstatSync(p);
|
|
228
|
+
if (entry.isSymbolicLink() || !entry.isFile()) continue;
|
|
229
|
+
fs.rmSync(p, { force: true });
|
|
230
|
+
removed.push(name);
|
|
231
|
+
}
|
|
232
|
+
} catch (_) { /* ignore */ }
|
|
222
233
|
const walk = (dir) => {
|
|
223
234
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
224
235
|
const p = path.join(dir, entry.name);
|