Browse Source

Memory: write triggers in protocol, stats read v2 entries, backfill 5 entries

master
Serge Shutov 1 week ago
parent
commit
8e42ba1769
  1. 12
      content/content.js
  2. 2
      content/panel.js
  3. 126
      memory/state.template.json

12
content/content.js

@ -372,5 +372,15 @@ if (parsed.calls.length === 0) { if (hasMarker) { try { var __dh = hashText(text @@ -372,5 +372,15 @@ if (parsed.calls.length === 0) { if (hasMarker) { try { var __dh = hashText(text
__refreshStats();
});
});
function __refreshStats() { try { var SB = globalThis.STORAGE_BRIDGE; if (!SB || !UI || !UI.setStats) return; SB.getState().then(function (st) { var mN = st && st.memories ? Object.keys(st.memories).length : 0; var sN = st && st.skills && st.skills.length ? st.skills.length : 0; return SB.authHeaders().then(function (h) { return fetch(SB.BU + '/health', { headers: h }); }).then(function (r) { return r.json(); }).then(function (j) { UI.setStats((j && j.workspace) || '?', mN, sN); }); }).catch(function () { UI.setStats('?', 0, 0); }); } catch (e) { console.warn('[dsh-memory] stats ' + String(e)); } }
function __refreshStats() { try { var SB = globalThis.STORAGE_BRIDGE; if (!SB || !UI || !UI.setStats) return; SB.authHeaders().then(function (h) {
return fetch(SB.BU + '/memory/state', { headers: h });
}).then(function (r) { return r.json(); }).then(function (j) {
var st = (j && j.ok && j.data) ? j.data : {};
var entries = st.entries || [];
var total = entries.length;
var alwaysN = entries.filter(function (e) { return e.importance === 'always'; }).length;
return SB.authHeaders().then(function (h) { return fetch(SB.BU + '/health', { headers: h }); }).then(function (r) { return r.json(); }).then(function (hj) {
UI.setStats((hj && hj.workspace) || '?', total, alwaysN);
});
}).catch(function (e) { console.warn('[dsh-memory] stats ' + String(e)); UI.setStats('?', 0, 0); }); } catch (e) { console.warn('[dsh-memory] stats ' + String(e)); } }
})();

2
content/panel.js

@ -147,7 +147,7 @@ @@ -147,7 +147,7 @@
}
},
setConnected: function (b) { state.connected = !!b; API._applyDot(); }, setDot: function (m) { state.dotMode = m || null; API._applyDot(); }, _applyDot: function () { if (!el.dot) return; var cls = 'dshmem-dot'; if (!state.connected) { cls += ' err'; } else { var m = state.dotMode || 'off'; if (m === 'busy' || m === 'ok-busy') cls += ' busy'; else if (m === 'error') cls += ' error'; else if (m === 'stopped') cls += ' stopped'; else if (m === 'ok') cls += ' ok'; else cls += ' off'; } el.dot.className = cls; },
setStartEnabled: function (b) { if (el.toggle) el.toggle.disabled = !b; }, setStats: function (ws, memN, skillN) { if (!el.stats) return; el.stats.textContent = 'WS: ' + (ws || '?') + ' mem: ' + (memN || 0) + ' skills: ' + (skillN || 0); }, setMetrics: function (m) { if (!el.metrics) return; if (!m) { el.metrics.textContent = 'metrics: —'; return; } state.lastMetrics = m; var __pct = 0; if (state.accLimit > 0) __pct = Math.round((m.accum || 0) * 100 / state.accLimit); var __cls = 'dshmem-acc-ok'; if (__pct > 100) __cls = 'dshmem-acc-crit'; else if (__pct >= 80) __cls = 'dshmem-acc-err'; else if (__pct >= 40) __cls = 'dshmem-acc-warn'; el.metrics.className = 'dshmem-stats ' + __cls; el.metrics.textContent = 'acc: ' + m.accum + ' · turn: ' + m.delta + ' · msg: ' + m.chars + 'ch (~' + m.est_tok + 't) ctx ' + (m.ctx_tok || 0) + 't ' + (m.ctx_pct || 0) + 'pct'; },
setStartEnabled: function (b) { if (el.toggle) el.toggle.disabled = !b; }, setStats: function (ws, totalN, alwaysN) { if (!el.stats) return; el.stats.textContent = 'WS: ' + (ws || '?') + ' entries: ' + (totalN || 0) + ' (always ' + (alwaysN || 0) + ')'; }, setMetrics: function (m) { if (!el.metrics) return; if (!m) { el.metrics.textContent = 'metrics: —'; return; } state.lastMetrics = m; var __pct = 0; if (state.accLimit > 0) __pct = Math.round((m.accum || 0) * 100 / state.accLimit); var __cls = 'dshmem-acc-ok'; if (__pct > 100) __cls = 'dshmem-acc-crit'; else if (__pct >= 80) __cls = 'dshmem-acc-err'; else if (__pct >= 40) __cls = 'dshmem-acc-warn'; el.metrics.className = 'dshmem-stats ' + __cls; el.metrics.textContent = 'acc: ' + m.accum + ' · turn: ' + m.delta + ' · msg: ' + m.chars + 'ch (~' + m.est_tok + 't) ctx ' + (m.ctx_tok || 0) + 't ' + (m.ctx_pct || 0) + 'pct'; },
toggleForceInject: function () {
state.forceInject = !state.forceInject;
try { chrome.storage.local.set({ 'dshmem-force-inject': state.forceInject }) } catch (e) { }

126
memory/state.template.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"version": 2,
"updatedAt": "2026-11-03T13:09:21.539Z",
"updatedAt": "2026-11-07T05:58:15.566Z",
"entries": [
{
"id": "0f90dcf0-5d43-4ca4-bf86-ad8b3ee20382",
@ -84,18 +84,19 @@ @@ -84,18 +84,19 @@
"scope": "user-global",
"workspaceKey": "",
"key": "protocol_memory_system",
"text": "МЕХАНИЗМ ПАМЯТИ dsh-memory v2\n\nСлои. Protocol (track agent, importance always) инжектится в первый ход. Session (directive scope workspace) инжектится в первый ход текущего чата. Reference (called) и Archive (state archive) только по запросу.\n\nЧто инжектится всегда: все track agent + importance always, плюс directive_ scope workspace state active для текущего chatId с проверкой workspaceKey. Fingerprint: если тот же набор уже в истории чата — пропуск всего блока.\n\nЗапросы через dsh_memory (op):\nlist track scope importance state since limit — items с preview и bytes, totalBytes, matched\nget ids — items с content, totalBytes\nrecent since — items\nunread chatId — новее lastInjectAt\nsearch query track — plain substring, totalBytes\ndirectives chatId state active|done|archive|all — директивы\nbudgets — used по track и scope, alwaysCount, лимиты\nadd entry — новая запись, entry включает track scope workspaceKey key text importance tags\nreplace id text tags importance state chatId — правка\nremove id — удаление\n\nСервер ВСЕГДА отдаёт totalBytes в meta ДО контента. Модель видит объём и решает: взять всё, взять по ids, взять preview, отложить.\n\nТаксономия ключей: user_ факты о пользователе, project_ проекты, directive_ правила и шаги, pref_ привычки, tech_ стек, interest_ интересы, pitfall_ грабли и обходы, decision_ архитектурные решения. Префикс задаёт группу, программа не решает релевантность.\n\nЛимиты: always записей 10, tags 5 на запись по 32 символа, директив на чат 5, preview 200. Калибровать по обратной связи.\n\nОбновление факта: replace по тому же id, не дубликат. AUTHENTIC EXTRACTION ONLY: только явно сказанное пользователем. NEVER cite memory tags в ответе.",
"text": "МЕХАНИЗМ ПАМЯТИ dsh-memory v2\n\nСлои. Protocol (track agent, importance always) инжектится в первый ход. Session (directive scope workspace) инжектится в первый ход текущего чата. Reference (called) и Archive (state archive) только по запросу.\n\nЧто инжектится всегда: все track agent + importance always, плюс directive_ scope workspace state active для текущего chatId с проверкой workspaceKey. Fingerprint: если тот же набор уже в истории чата — пропуск всего блока.\n\nЗапросы через dsh_memory (op):\nlist track scope importance state since limit — items с preview и bytes, totalBytes, matched\nget ids — items с content, totalBytes\nrecent since — items\nunread chatId — новее lastInjectAt\nsearch query track — plain substring, totalBytes\ndirectives chatId state active|done|archive|all — директивы\nbudgets — used по track и scope, alwaysCount, лимиты\nadd entry — новая запись, entry включает track scope workspaceKey key text importance tags\nreplace id text tags importance state chatId — правка\nremove id — удаление\n\nСервер ВСЕГДА отдаёт totalBytes в meta ДО контента. Модель видит объём и решает: взять всё, взять по ids, взять preview, отложить.\n\nТаксономия ключей: user_ факты о пользователе, project_ проекты, directive_ правила и шаги, pref_ привычки, tech_ стек, interest_ интересы, pitfall_ грабли и обходы, decision_ архитектурные решения. Префикс задаёт группу, программа не решает релевантность.\n\nКОГДА ПИСАТЬ (триггеры, важнее чем формат):\n1. Завершён этап работы (коммит, фича, интеграция) -> project_<тема> или decision_<тема>, importance called. Что сделано, где код, какие грабли.\n2. Наступил на грабли второй раз -> pitfall_<тема>, called. Симптом + обход.\n3. Изменилось правило поведения (новая политика, новый лимит) -> replace существующего protocol_*, всегда. Не дубликат.\n4. Появился новый инструмент/команда/приложение -> protocol_*, always. Чтобы модель в новом чате знала.\n5. Пользователь сказал факт о себе или предпочтение -> user_/pref_, called.\n6. Проект/сервис добавлен или изменён -> project_*, called.\n\nПравило записи: после каждого завершённого этапа (2+ коммита или значимая правка) — записать хотя бы одну project_ или decision_ запись. Если этап был без грабель и без новых правил — всё равно project_: что сделано, где смотреть. Иначе через неделю память не отражает реальность.\n\nЛимиты: always записей 10, tags 5 на запись по 32 символа, директив на чат 5, preview 200. Калибровать по обратной связи.\n\nОбновление факта: replace по тому же id, не дубликат. AUTHENTIC EXTRACTION ONLY: только явно сказанное пользователем. NEVER cite memory tags в ответе.",
"importance": "always",
"tags": [
"protocol",
"memory"
"memory",
"triggers"
],
"version": 4,
"version": 5,
"createdAt": "2026-11-02T23:35:16.258Z",
"updatedAt": "2026-11-03T03:41:45.792Z",
"updatedAt": "2026-11-07T05:57:12.533Z",
"lastRecalled": null,
"recallCount": 0,
"bytes": 2327
"bytes": 3807
},
{
"id": "2853ce0e-de50-455c-bd5a-ebbf9a3fe337",
@ -155,6 +156,119 @@ @@ -155,6 +156,119 @@
"lastRecalled": null,
"recallCount": 0,
"bytes": 2117
},
{
"id": "3e51562d-a56b-4919-9720-cf36597553b6",
"track": "agent",
"scope": "user-global",
"workspaceKey": "",
"key": "project_mcp_integration",
"text": "MCP-интеграция ArtCraft в dsh-memory. Сделано 06.11.2026, коммиты 45222a0, 93504ed, fe9c4eb, f03dd76, 45ace2c, 4090ba0.\n\nЧТО СДЕЛАНО. Мост научился управлять приложениями ArtCraft через MCP (stdio JSON-RPC). 17 приложений: vectorcraft, photocraft, figcraft, designcraft, deckcraft, cadcraft, craftanything, effectcraft, filmcraft, gridcraft, lightcraft, materialcraft, pdfcraft, solidcraft, soundcraft, texturecraft, wordcraft. Порты 7979-7995, у каждого свой. Клиент поднимается лениво при первом вызове. Инструменты <app>_mcp_tools и <app>_mcp_call.\n\nФАЙЛЫ. bridge/mcp-client.js (JSON-RPC по stdio), bridge/mcp-registry.js (discovery, порты, флаги коннекта), bridge/mcp-tools.js (менеджер, ленивый старт, launchGui, stop), bridge/server.js (маршруты /mcp/apps, /mcp/launch, /mcp/stop, /mcp/rescan, /mcp/root).\n\nLIVE РЕЖИМ. vectorcraft.exe --control 7979, CLI коннектится через --connect 127.0.0.1:7979. Флаги у приложений разные: --connect ADDR (vectorcraft, designcraft, gridcraft, wordcraft, cadcraft, solidcraft, soundcraft, deckcraft), --bridge (photocraft, effectcraft, filmcraft, materialcraft, texturecraft), --attach (figcraft). pdfcraft и craftanything live не умеют.\n\nГРАБЛИ. Мост кэширует CLI-клиент: если окно подняли после старта клиента, он не переподключится. launchGui это лечит (закрывает старый клиент). Мост выбрасывает inline-картинки из MCP-ответов (mcp-client.js:81) — всегда просить path. ui_screenshot не принимает path, файл пишет screenshot {window:true, path}. draw_shape поле shape не kind, add_text color не fill — схемы через _mcp_tools {filter}.",
"importance": "called",
"tags": [
"project",
"mcp",
"artcraft"
],
"state": null,
"chatId": "",
"version": 1,
"createdAt": "2026-11-07T05:57:38.065Z",
"updatedAt": "2026-11-07T05:57:38.065Z",
"lastRecalled": null,
"recallCount": 0,
"bytes": 2024
},
{
"id": "4bcc17ab-cc89-4877-8695-63042b0aeaab",
"track": "agent",
"scope": "user-global",
"workspaceKey": "",
"key": "decision_bash_policy",
"text": "Bash-политика dsh-memory: safe-list вместо блоков. Решение принято 07.11.2026, коммиты c2cf419, 69602ec, a298e6d.\n\nПОЧЕМУ. Раньше метасимволы (| & > < ; обратный апостроф $) и blacklist-паттерны блокировали команду намертво. Это создавало видимость защиты: обход через node script.mjs был тривиален, не логировался, а модель обходила блоки через .mjs-файлы. Пользователь сформулировал: программа не должна запрещать, она должна спрашивать.\n\nКАК СЕЙЧАС. Жёстко блокируются только пустая команда и >2000 символов. Safe-list (без подтверждения): pwd, dir, ls, tree, where, which, whoami, hostname, uname, ver, cd, echo, date, time, git status/diff/log/show/branch/rev-parse/ls-files/describe/remote/config (с пропуском -C, -c, --no-pager), npm/pnpm/yarn/bun test/run/build/lint/typecheck/check/ls/list/why/outdated/info/view (с пропуском --prefix, --workspace, -w), npx tsc/eslint/prettier/vitest/jest/mocha, node/npm/npx --version, tsc, eslint. Не включены сознательно: cat, head, tail, type, more, less — читают файлы в обход validatePath.\n\nPOLICY-ФЛАГИ (confirm): shell (метасимволы), multiline, destructive (rm, del, format, mv, cp, git push/commit/reset/clean, taskkill, reg, sc, schtasks), network (curl, wget, ssh, scp, ftp, powershell, iwr), absolute-path, parent-path. Любой флаг отменяет allow.\n\nДВА СЛОЯ. Pre-flight: модель объявляет в тексте до отправки, ждёт да (protocol_policy). Панельный confirm: Allow/Deny с ожиданием клика (content.js executeOne), fail-closed при недоступной панели (E_CONFIRM_UNAVAILABLE).\n\nГРАБЛИ. git -C path status раньше уходил в confirm (skipGlobalFlags лечит). Двойной E_NEEDS_CONFIRM в логе (server.js: при confirmToken сразу call(true)).",
"importance": "called",
"tags": [
"decision",
"bash",
"policy",
"security"
],
"state": null,
"chatId": "",
"version": 1,
"createdAt": "2026-11-07T05:57:41.076Z",
"updatedAt": "2026-11-07T05:57:41.076Z",
"lastRecalled": null,
"recallCount": 0,
"bytes": 2252
},
{
"id": "917a3647-9bb5-4283-8eff-f59dfa1c3648",
"track": "agent",
"scope": "user-global",
"workspaceKey": "",
"key": "pitfall_eol_normalize",
"text": "dsh_edit не находит oldText в файлах со смешанными CRLF/LF. Наступал многократно за 06-07.11.2026 на server.js, tools.js, panel.js, overlay.css, background.js.\n\nСИМПТОМ. E_ARGS_INVALID oldText not found (0 matches), хотя текст визуально совпадает. Причина: файл в CRLF, а oldText в LF (или наоборот).\n\nОБХОД. Перед правкой проверить EOL скриптом (посчитать CRLF vs LF), нормализовать файл к LF: fs.readFileSync -> replace(CRLF, LF) -> writeFileSync. Потом dsh_edit. После правки node -c для проверки синтаксиса. Для больших блоков (десятки строк) надёжнее не dsh_edit, а отдельный скрипт замены по маркерам: найти индексы start/end, собрать merged = before + section + after.",
"importance": "called",
"tags": [
"pitfall",
"eol",
"crlf",
"dsh_edit"
],
"state": null,
"chatId": "",
"version": 1,
"createdAt": "2026-11-07T05:57:44.088Z",
"updatedAt": "2026-11-07T05:57:44.088Z",
"lastRecalled": null,
"recallCount": 0,
"bytes": 972
},
{
"id": "1c3a7d4c-fef0-4575-ab7c-d6cf1978e9d0",
"track": "agent",
"scope": "user-global",
"workspaceKey": "",
"key": "pitfall_dsh_edit_large_oldtext",
"text": "dsh_edit с очень большим oldText (десятки строк) может применить замену криво: вставить новый блок, но не удалить старый, или задвоить содержимое. Произошло 07.11.2026 с README.md: файл раздулся с 690 до 1396 строк (дубли всех разделов).\n\nПРИЧИНА. Не подтверждена достоверно, но воспроизводилось.\n\nОБХОД. Для блоков больше 15-20 строк не использовать dsh_edit. Писать новый блок в отдельный .md/.txt файл, затем скрипт: прочитать целевой файл, найти indexOf(startMark) и indexOf(endMark), собрать merged = lines.slice(0,start) + sectionLines + lines.slice(end), записать. Это детерминированно. После — проверить размер (bytes, lines) и посчитать вхождения ключевых заголовков (должно быть по 1). При сомнениях git checkout -- file и повторить.",
"importance": "called",
"tags": [
"pitfall",
"dsh_edit",
"large-block"
],
"state": null,
"chatId": "",
"version": 1,
"createdAt": "2026-11-07T05:57:47.096Z",
"updatedAt": "2026-11-07T05:57:47.096Z",
"lastRecalled": null,
"recallCount": 0,
"bytes": 1176
},
{
"id": "06442ed5-f07c-4beb-bbaf-6084b5b2dddc",
"track": "agent",
"scope": "workspace",
"workspaceKey": "V:/Site",
"key": "project_warp_cz",
"text": "WARP на чешском VPS (cvetochny, 85.137.166.251): скрыть IP VPS от клиентов российского туннеля. Спроектировано 06.11.2026, скрипты написаны, на сервер не выкатывалось.\n\nЗАЧЕМ. Клиенты site-to-site туннеля Россия-Чехия (RU 188.127.239.17, CZ 85.137.166.251) выходят в интернет с чешского IP. WARP завернёт только клиентов (10.8.1.0/24) в Cloudflare, сервисы на самом VPS (3proxy :3128 для Telegram-бота starvery, nginx :8443) останутся на чешском IP.\n\nФАЙЛЫ. backup-20261104/warp-cz/: warp-on.sh (wg-quick up wgcf, таблица warp id 201, правило from 10.8.1.0/24 priority 50, NAT на wgcf, TCPMSS clamp, проверка trace), warp-off.sh, warp-status.sh, wgcf.conf.example, README.md. Раздел 18 в backup-20261104/RECOVERY-AWG-SITE-TO-SITE.md.\n\nРЕЖИМ. Fail-open: старый NAT -s 10.8.1.0/24 -o ens3 MASQUERADE не удаляется. При падении WARP клиенты получают чешский IP, не теряют связь. Геолокация не важна.\n\nГРАБЛИ. Table = off в wgcf.conf обязателен (иначе wg-quick перезапишет default route, SSH умрёт). DNS = закомментировать (иначе /etc/resolv.conf перепишется, 3proxy/nginx/apt начнут резолвить через 1.1.1.1). WARP-адреса общие, Google/банки дают капчу. Геолокация не привязана к Чехии (Cloudflare выходит через ближайший DC). Хостинг может блокировать WARP — проверять curl --interface wgcf https://www.cloudflare.com/cdn-cgi/trace до включения.",
"importance": "called",
"tags": [
"project",
"vpn",
"warp",
"czech"
],
"state": null,
"chatId": "",
"version": 1,
"createdAt": "2026-11-07T05:57:50.110Z",
"updatedAt": "2026-11-07T05:57:50.110Z",
"lastRecalled": null,
"recallCount": 0,
"bytes": 1880
}
],
"chats": {}

Loading…
Cancel
Save