berry-agent 0.1.0-alpha.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.
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/.api-emit.stamp +1 -0
- package/dist/.build-meta.json +6 -0
- package/dist/agent/events.js +1 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/loop.js +139 -0
- package/dist/agent/queue.js +119 -0
- package/dist/agent/stream.js +62 -0
- package/dist/agent/tools-batch.js +221 -0
- package/dist/agent/types.js +8 -0
- package/dist/api/berry-agent-llm.d.ts +18 -0
- package/dist/api/berry-agent.d.ts +9 -0
- package/dist/api/surface.json +1632 -0
- package/dist/api/tsconfig.paths.json +12 -0
- package/dist/api/typebox-compile.d.ts +2 -0
- package/dist/api/typebox-value.d.ts +2 -0
- package/dist/api/typebox.d.ts +2 -0
- package/dist/browser/cdp.js +256 -0
- package/dist/browser/codes.js +31 -0
- package/dist/browser/discover.js +107 -0
- package/dist/browser/engine.js +169 -0
- package/dist/browser/index.js +17 -0
- package/dist/browser/install.js +342 -0
- package/dist/browser/page.js +321 -0
- package/dist/browser/service.js +158 -0
- package/dist/browser/tools.js +198 -0
- package/dist/browser/types.js +81 -0
- package/dist/channels/ask-queue.js +82 -0
- package/dist/channels/commands.js +129 -0
- package/dist/channels/engine/cell.js +143 -0
- package/dist/channels/engine/diff.js +143 -0
- package/dist/channels/engine/engine.js +416 -0
- package/dist/channels/engine/index.js +8 -0
- package/dist/channels/engine/input-keys.js +86 -0
- package/dist/channels/engine/input.js +596 -0
- package/dist/channels/engine/memory-io.js +71 -0
- package/dist/channels/engine/process-io.js +38 -0
- package/dist/channels/engine/types.js +17 -0
- package/dist/channels/engine/width.js +122 -0
- package/dist/channels/index.js +15 -0
- package/dist/channels/registry.js +56 -0
- package/dist/channels/sdk/admit.js +29 -0
- package/dist/channels/sdk/backend.js +102 -0
- package/dist/channels/sdk/cursor.js +56 -0
- package/dist/channels/sdk/index.js +20 -0
- package/dist/channels/sdk/jsonl.js +208 -0
- package/dist/channels/sdk/protocol.js +24 -0
- package/dist/channels/sdk/schema.js +91 -0
- package/dist/channels/sdk/wire-core.js +446 -0
- package/dist/channels/service.js +169 -0
- package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
- package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
- package/dist/channels/tui/autocomplete/popup.js +148 -0
- package/dist/channels/tui/autocomplete/provider.js +7 -0
- package/dist/channels/tui/autocomplete/token.js +68 -0
- package/dist/channels/tui/backend/ansi-rows.js +211 -0
- package/dist/channels/tui/backend/main-screen.js +207 -0
- package/dist/channels/tui/backend/osc.js +91 -0
- package/dist/channels/tui/backend/transcript.js +230 -0
- package/dist/channels/tui/backend/tui-backend.js +915 -0
- package/dist/channels/tui/editor/editor-model.js +543 -0
- package/dist/channels/tui/editor/editor-view.js +140 -0
- package/dist/channels/tui/editor/editor.js +275 -0
- package/dist/channels/tui/editor/undo-stack.js +35 -0
- package/dist/channels/tui/editor/visual-lines.js +126 -0
- package/dist/channels/tui/editor/word-nav.js +106 -0
- package/dist/channels/tui/history/history-viewer.js +403 -0
- package/dist/channels/tui/index.js +3 -0
- package/dist/channels/tui/layout.js +167 -0
- package/dist/channels/tui/markdown/blocks.js +111 -0
- package/dist/channels/tui/markdown/inline.js +75 -0
- package/dist/channels/tui/markdown/markdown.js +182 -0
- package/dist/channels/tui/memory/memory-viewer.js +540 -0
- package/dist/channels/tui/overlay/alt-screen.js +105 -0
- package/dist/channels/tui/overlay/overlay.js +57 -0
- package/dist/channels/tui/overlay/select-confirm.js +156 -0
- package/dist/channels/tui/panels/todo-panel.js +54 -0
- package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
- package/dist/channels/tui/scroll/scroll-view.js +254 -0
- package/dist/channels/tui/status/status-line.js +76 -0
- package/dist/channels/tui/text.js +38 -0
- package/dist/channels/tui/theme.js +41 -0
- package/dist/channels/types.js +11 -0
- package/dist/channels/ui-core.js +209 -0
- package/dist/checkpoint/capture.js +45 -0
- package/dist/checkpoint/codes.js +33 -0
- package/dist/checkpoint/command.js +82 -0
- package/dist/checkpoint/gate.js +48 -0
- package/dist/checkpoint/index.js +20 -0
- package/dist/checkpoint/restore.js +172 -0
- package/dist/checkpoint/store.js +247 -0
- package/dist/checkpoint/types.js +4 -0
- package/dist/checkpoint/walk.js +146 -0
- package/dist/compaction/ccr-tools.js +112 -0
- package/dist/compaction/ccr.js +84 -0
- package/dist/compaction/codes.js +21 -0
- package/dist/compaction/index.js +18 -0
- package/dist/compaction/policy.js +200 -0
- package/dist/compaction/service.js +422 -0
- package/dist/compaction/slots.js +121 -0
- package/dist/compaction/types.js +10 -0
- package/dist/context/codes.js +40 -0
- package/dist/context/events.js +160 -0
- package/dist/context/index.js +13 -0
- package/dist/context/logger.js +136 -0
- package/dist/context/scope.js +182 -0
- package/dist/context/workspace.js +104 -0
- package/dist/contracts/agent-events.d.ts +73 -0
- package/dist/contracts/agent-events.js +16 -0
- package/dist/contracts/api.d.ts +249 -0
- package/dist/contracts/api.js +299 -0
- package/dist/contracts/approval.d.ts +37 -0
- package/dist/contracts/approval.js +9 -0
- package/dist/contracts/env-ref.d.ts +41 -0
- package/dist/contracts/env-ref.js +32 -0
- package/dist/contracts/errors.d.ts +56 -0
- package/dist/contracts/errors.js +266 -0
- package/dist/contracts/events.d.ts +70 -0
- package/dist/contracts/events.js +329 -0
- package/dist/contracts/index.d.ts +32 -0
- package/dist/contracts/index.js +26 -0
- package/dist/contracts/llm.d.ts +275 -0
- package/dist/contracts/llm.js +12 -0
- package/dist/contracts/messages.d.ts +63 -0
- package/dist/contracts/messages.js +65 -0
- package/dist/contracts/redact.d.ts +46 -0
- package/dist/contracts/redact.js +223 -0
- package/dist/contracts/tools.d.ts +197 -0
- package/dist/contracts/tools.js +33 -0
- package/dist/contracts/types.d.ts +302 -0
- package/dist/contracts/types.js +50 -0
- package/dist/contracts/ui.d.ts +122 -0
- package/dist/contracts/ui.js +15 -0
- package/dist/conversation/agent-service.js +61 -0
- package/dist/conversation/approval-wiring.js +82 -0
- package/dist/conversation/backoff.js +38 -0
- package/dist/conversation/codes.js +47 -0
- package/dist/conversation/control-tools.js +102 -0
- package/dist/conversation/control.js +210 -0
- package/dist/conversation/driver.js +922 -0
- package/dist/conversation/index.js +12 -0
- package/dist/conversation/model-visible.js +169 -0
- package/dist/conversation/open-tools.js +126 -0
- package/dist/conversation/reseed.js +132 -0
- package/dist/conversation/sessions.js +215 -0
- package/dist/conversation/todo.js +203 -0
- package/dist/conversation/types.js +35 -0
- package/dist/conversation/wiring.js +204 -0
- package/dist/credentials/codes.js +59 -0
- package/dist/credentials/commands.js +186 -0
- package/dist/credentials/env-ref.js +34 -0
- package/dist/credentials/index.js +28 -0
- package/dist/credentials/migration.js +18 -0
- package/dist/credentials/oauth.js +266 -0
- package/dist/credentials/refresh.js +152 -0
- package/dist/credentials/secrets.js +118 -0
- package/dist/credentials/types.js +30 -0
- package/dist/exec/bash.js +279 -0
- package/dist/exec/codes.js +37 -0
- package/dist/exec/env.js +99 -0
- package/dist/exec/environment.js +39 -0
- package/dist/exec/git-guard.js +623 -0
- package/dist/exec/index.js +24 -0
- package/dist/exec/registry.js +120 -0
- package/dist/exec/spawn.js +281 -0
- package/dist/exec/tail.js +77 -0
- package/dist/exec/types.js +5 -0
- package/dist/goal/codes.js +33 -0
- package/dist/goal/command.js +175 -0
- package/dist/goal/fold.js +121 -0
- package/dist/goal/gates.js +122 -0
- package/dist/goal/index.js +22 -0
- package/dist/goal/migration.js +48 -0
- package/dist/goal/service.js +501 -0
- package/dist/goal/todo-tool.js +174 -0
- package/dist/goal/types.js +1 -0
- package/dist/goal/update-tool.js +46 -0
- package/dist/host/approval-cmd.js +276 -0
- package/dist/host/assembly.js +1146 -0
- package/dist/host/boot-failures.js +104 -0
- package/dist/host/budget-advisory.js +74 -0
- package/dist/host/budget-broadcast.js +50 -0
- package/dist/host/builtins.js +54 -0
- package/dist/host/cli.js +544 -0
- package/dist/host/codes.js +120 -0
- package/dist/host/config-schema.js +195 -0
- package/dist/host/conversation-stack.js +654 -0
- package/dist/host/core-plugins.js +1685 -0
- package/dist/host/credentials-cmd.js +42 -0
- package/dist/host/disclosure.js +30 -0
- package/dist/host/dispatch.js +105 -0
- package/dist/host/doors-cmd.js +154 -0
- package/dist/host/dump-config.js +44 -0
- package/dist/host/hook-dispatch-guard.js +27 -0
- package/dist/host/import-gate.js +153 -0
- package/dist/host/index.js +54 -0
- package/dist/host/issue-session.js +241 -0
- package/dist/host/loader.js +372 -0
- package/dist/host/main.js +161 -0
- package/dist/host/manifest.js +381 -0
- package/dist/host/mcp-entry.js +53 -0
- package/dist/host/plugin-boot.js +952 -0
- package/dist/host/plugin-context.js +672 -0
- package/dist/host/plugin-install.js +480 -0
- package/dist/host/plugin-reload.js +124 -0
- package/dist/host/plugin-store.js +462 -0
- package/dist/host/plugin-tools.js +359 -0
- package/dist/host/plugin-uninstall.js +257 -0
- package/dist/host/plugins-cmd.js +302 -0
- package/dist/host/plugins-command.js +130 -0
- package/dist/host/plugins-config.js +195 -0
- package/dist/host/prompt-sections.js +133 -0
- package/dist/host/run-entry.js +544 -0
- package/dist/host/runtime.js +209 -0
- package/dist/host/scheduler-tick.js +294 -0
- package/dist/host/serve-daemon.js +501 -0
- package/dist/host/serve-entry.js +279 -0
- package/dist/host/session-anchor.js +41 -0
- package/dist/host/sessions-cmd.js +219 -0
- package/dist/host/sessions-face.js +47 -0
- package/dist/host/settings-store.js +112 -0
- package/dist/host/signals.js +69 -0
- package/dist/host/single-instance.js +105 -0
- package/dist/host/subagent-factory.js +291 -0
- package/dist/host/testkit/harness.js +283 -0
- package/dist/host/testkit/index.js +14 -0
- package/dist/host/testkit/matrix.js +302 -0
- package/dist/host/tool-policy-store.js +177 -0
- package/dist/host/triggers.js +212 -0
- package/dist/host/tui-entry.js +216 -0
- package/dist/host/webui-bridge.js +165 -0
- package/dist/issue/codes.js +38 -0
- package/dist/issue/filter.js +146 -0
- package/dist/issue/github.js +116 -0
- package/dist/issue/index.js +17 -0
- package/dist/issue/mount.js +70 -0
- package/dist/issue/poll.js +58 -0
- package/dist/issue/service.js +341 -0
- package/dist/issue/tools.js +70 -0
- package/dist/issue/types.js +34 -0
- package/dist/issue/webhook.js +126 -0
- package/dist/llm/codes.js +77 -0
- package/dist/llm/complete.js +196 -0
- package/dist/llm/events.js +15 -0
- package/dist/llm/index.js +24 -0
- package/dist/llm/inflight.js +65 -0
- package/dist/llm/model-id.js +58 -0
- package/dist/llm/provider-face.d.ts +32 -0
- package/dist/llm/provider-face.js +34 -0
- package/dist/llm/recovery.js +146 -0
- package/dist/llm/runtime.js +46 -0
- package/dist/llm/stream-fn.js +147 -0
- package/dist/lsp/codes.js +31 -0
- package/dist/lsp/connection.js +147 -0
- package/dist/lsp/frame.js +110 -0
- package/dist/lsp/index.js +16 -0
- package/dist/lsp/inject.js +58 -0
- package/dist/lsp/instance.js +264 -0
- package/dist/lsp/service.js +522 -0
- package/dist/lsp/tools.js +53 -0
- package/dist/lsp/types.js +201 -0
- package/dist/mcp/bridge.js +222 -0
- package/dist/mcp/codes.js +21 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/jsonrpc.js +230 -0
- package/dist/mcp/service.js +125 -0
- package/dist/mcp/tools.js +145 -0
- package/dist/mcp/types.js +108 -0
- package/dist/memory/cite.js +131 -0
- package/dist/memory/codes.js +50 -0
- package/dist/memory/command.js +71 -0
- package/dist/memory/consolidate.js +219 -0
- package/dist/memory/cycle.js +109 -0
- package/dist/memory/dao.js +878 -0
- package/dist/memory/diff.js +238 -0
- package/dist/memory/extract.js +181 -0
- package/dist/memory/fts.js +58 -0
- package/dist/memory/index.js +42 -0
- package/dist/memory/inject.js +354 -0
- package/dist/memory/merge.js +178 -0
- package/dist/memory/migration.js +132 -0
- package/dist/memory/pollution.js +59 -0
- package/dist/memory/port.js +385 -0
- package/dist/memory/review.js +201 -0
- package/dist/memory/scan.js +89 -0
- package/dist/memory/tools.js +508 -0
- package/dist/memory/types.js +140 -0
- package/dist/obs/codes.js +38 -0
- package/dist/obs/db.js +115 -0
- package/dist/obs/index.js +20 -0
- package/dist/obs/rollup.js +61 -0
- package/dist/obs/service.js +363 -0
- package/dist/obs/session-tools.js +282 -0
- package/dist/obs/session-view.js +225 -0
- package/dist/obs/tool.js +97 -0
- package/dist/obs/types.js +1 -0
- package/dist/persist/audit.js +63 -0
- package/dist/persist/aux.js +39 -0
- package/dist/persist/codes.js +35 -0
- package/dist/persist/index.js +30 -0
- package/dist/persist/load-history.js +50 -0
- package/dist/persist/migrations.js +37 -0
- package/dist/persist/paths.js +108 -0
- package/dist/persist/persistence.js +229 -0
- package/dist/persist/schema.js +99 -0
- package/dist/persist/secret-box.js +95 -0
- package/dist/persist/store.js +706 -0
- package/dist/persist/write-behind.js +238 -0
- package/dist/safety/approval.js +171 -0
- package/dist/safety/bwrap.js +123 -0
- package/dist/safety/codes.js +57 -0
- package/dist/safety/danger.js +505 -0
- package/dist/safety/gate.js +226 -0
- package/dist/safety/index.js +24 -0
- package/dist/safety/presets.js +73 -0
- package/dist/safety/roots.js +139 -0
- package/dist/safety/sandbox.js +214 -0
- package/dist/safety/seatbelt.js +88 -0
- package/dist/safety/sensitive.js +34 -0
- package/dist/safety/tool-policy.js +203 -0
- package/dist/safety/types.js +13 -0
- package/dist/scheduler/codes.js +54 -0
- package/dist/scheduler/cron-backend.js +135 -0
- package/dist/scheduler/engine.js +280 -0
- package/dist/scheduler/gates.js +60 -0
- package/dist/scheduler/index.js +25 -0
- package/dist/scheduler/migration.js +22 -0
- package/dist/scheduler/runner.js +171 -0
- package/dist/scheduler/schedule.js +193 -0
- package/dist/scheduler/service.js +283 -0
- package/dist/scheduler/tick.js +139 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/sdk/http.js +865 -0
- package/dist/sdk/index.js +22 -0
- package/dist/sdk/mcp.js +289 -0
- package/dist/sdk/plugin-route-registry.js +126 -0
- package/dist/sdk/plugin-routes.js +149 -0
- package/dist/sdk/security.js +116 -0
- package/dist/sdk/types.js +32 -0
- package/dist/session/budget.js +159 -0
- package/dist/session/codes.js +40 -0
- package/dist/session/derive.js +180 -0
- package/dist/session/event-data.js +1 -0
- package/dist/session/fork.js +72 -0
- package/dist/session/import-gates.js +148 -0
- package/dist/session/index.js +18 -0
- package/dist/session/recover.js +103 -0
- package/dist/session/session.js +279 -0
- package/dist/session/snapshot.js +107 -0
- package/dist/skills/agents.js +242 -0
- package/dist/skills/codes.js +51 -0
- package/dist/skills/discovery.js +266 -0
- package/dist/skills/frontmatter.js +215 -0
- package/dist/skills/index.js +28 -0
- package/dist/skills/load.js +118 -0
- package/dist/skills/manage.js +201 -0
- package/dist/skills/registry.js +138 -0
- package/dist/skills/render.js +124 -0
- package/dist/skills/sections.js +171 -0
- package/dist/skills/types.js +21 -0
- package/dist/subagent/codes.js +50 -0
- package/dist/subagent/declarative.js +55 -0
- package/dist/subagent/index.js +19 -0
- package/dist/subagent/notify.js +51 -0
- package/dist/subagent/provide.js +7 -0
- package/dist/subagent/registry.js +149 -0
- package/dist/subagent/service.js +338 -0
- package/dist/subagent/surface.js +44 -0
- package/dist/subagent/tool.js +153 -0
- package/dist/subagent/types.js +25 -0
- package/dist/tools/apply-patch.js +164 -0
- package/dist/tools/codes.js +109 -0
- package/dist/tools/fs.js +501 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/observed.js +99 -0
- package/dist/tools/pipeline.js +176 -0
- package/dist/tools/protected-read.js +54 -0
- package/dist/tools/registry.js +250 -0
- package/dist/tools/search.js +451 -0
- package/dist/tools/worktree.js +327 -0
- package/dist/web/codes.js +37 -0
- package/dist/web/gate.js +43 -0
- package/dist/web/hygiene.js +195 -0
- package/dist/web/index.js +16 -0
- package/dist/web/service.js +207 -0
- package/dist/web/ssrf-guard.js +44 -0
- package/dist/web/tool.js +76 -0
- package/dist/web/types.js +10 -0
- package/dist/webui/assets/index-B-t2O8gh.js +22 -0
- package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
- package/dist/webui/index.html +14 -0
- package/dist/webui/index.js +14 -0
- package/dist/webui/server.js +549 -0
- package/dist/webui/types.js +38 -0
- package/examples/README.md +26 -0
- package/examples/minimal-code-plugin/entry.js +43 -0
- package/examples/minimal-code-plugin/package.json +12 -0
- package/examples/pure-skill-pack/package.json +14 -0
- package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
- package/package.json +86 -0
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory 表族 DAO(06 §3/§5/§8.1——入库单点 + 合并三分支落库 + 版本链拍照;
|
|
3
|
+
* 批 18c-1)。
|
|
4
|
+
*
|
|
5
|
+
* **入库单点**(06 §8.1——一切写路径的物理汇入面):§4 即时路提取(经合并
|
|
6
|
+
* 管线)、§4 周期路候选(经合并管线)、memory_write 直写、导入直插四写点
|
|
7
|
+
* 汇入 `ingest()` 同一入口——写前 secret 扫描与坏形校验在此单点执法,没有
|
|
8
|
+
* 绕过的写入方。插入路径唯一:候选必经三分支合并(没有绕过合并的直通道)。
|
|
9
|
+
*
|
|
10
|
+
* **事务纪律**:ingest 全程单事务(目标扫描 + 裁决 + 双写〔主表 UPDATE 与
|
|
11
|
+
* 版本链 INSERT 同事务——06 §3「不许只写一边」〕);better-sqlite3 同步 API,
|
|
12
|
+
* 全件同步零 await。
|
|
13
|
+
*
|
|
14
|
+
* **TTL 读面谓词两形**(06 §3 单一来源;批 ev-1 生效起点条扩形——冷读闸 M2
|
|
15
|
+
* 拆形):注入形 = `status='active' AND (frozen=1 OR expires_at IS NULL OR
|
|
16
|
+
* expires_at > now) AND (valid_from IS NULL OR valid_from <= now)`——一切进
|
|
17
|
+
* 模型上下文与效用计量的面;管理形 = TTL 段 only(/memory 活体区、谱系、
|
|
18
|
+
* 导出)。合并目标扫描管理形 + frozen=0 叠加(frozen 豁免三分支——候选撞
|
|
19
|
+
* 冻结行作独立新条目;起点段同样不过滤——新证据并入未生效 keep 行)。
|
|
20
|
+
*
|
|
21
|
+
* 时钟与 id 生成注入(goal 先例同律——测试确定性);uuid v7 手卷(时间有序
|
|
22
|
+
* 主键,06 §3 DDL 注——crypto 随机位、无第三方依赖)。
|
|
23
|
+
*/
|
|
24
|
+
import { randomBytes } from 'node:crypto';
|
|
25
|
+
import { BaseError } from '../contracts/index.js';
|
|
26
|
+
import { decideMerge, unionSourceRefs, utilityScore } from './merge.js';
|
|
27
|
+
import { scanForSecrets } from './scan.js';
|
|
28
|
+
import { MEMORY_ACCESS_AGGREGATE_TOP_N, MEMORY_ACCESS_LOG_DEFAULT_LIMIT, MEMORY_ACCESS_LOG_MAX_LIMIT, MEMORY_ACCESS_WINDOW_DAYS, MEMORY_CONTENT_MAX_CHARS, MEMORY_DAY_MS, MEMORY_KINDS, MEMORY_RECENT_LIMIT, MEMORY_SEARCH_DEFAULT_LIMIT, MEMORY_SEARCH_MAX_LIMIT, MEMORY_SKILL_NAME_MAX, MEMORY_SKILL_NAME_RE, MEMORY_SOURCE_REFS_CAP, MEMORY_STATUSES, MEMORY_SUMMARY_MAX_CHARS, } from './types.js';
|
|
29
|
+
/* ---------------- uuid v7(时间有序主键——手卷,标准位布局) ---------------- */
|
|
30
|
+
/** uuid v7 生成(48-bit 毫秒时间戳 + ver 7 + variant 10 + 随机位) */
|
|
31
|
+
export function uuidv7(nowMs) {
|
|
32
|
+
const ts = BigInt(Math.floor(nowMs));
|
|
33
|
+
const b = randomBytes(16);
|
|
34
|
+
b[0] = Number((ts >> 40n) & 0xffn);
|
|
35
|
+
b[1] = Number((ts >> 32n) & 0xffn);
|
|
36
|
+
b[2] = Number((ts >> 24n) & 0xffn);
|
|
37
|
+
b[3] = Number((ts >> 16n) & 0xffn);
|
|
38
|
+
b[4] = Number((ts >> 8n) & 0xffn);
|
|
39
|
+
b[5] = Number(ts & 0xffn);
|
|
40
|
+
b[6] = (b[6] & 0x0f) | 0x70; // version 7
|
|
41
|
+
b[8] = (b[8] & 0x3f) | 0x80; // variant 10xx
|
|
42
|
+
const hex = b.toString('hex');
|
|
43
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* TTL 读面谓词**两形**(批 ev-1——06 §3「生效起点」条落码切分面,冷读闸 M2
|
|
47
|
+
* 钉死;承冷读闸 o3 先抽单源再扩段——四处内联手抄自此归一):
|
|
48
|
+
*
|
|
49
|
+
* 管理形 = TTL 段 only——管理与审计读面可见未生效行(/memory 活体区、谱系、
|
|
50
|
+
* 导出、访问流水);合并目标扫描同形(§5 起点段不过滤——新证据
|
|
51
|
+
* 并入未生效 keep 行、起点不漂移)。
|
|
52
|
+
* 注入形 = TTL 段 AND 起点段——一切进模型上下文与效用计量的面(常驻简报、
|
|
53
|
+
* memory_read 无 id 腿、memory_search 命中)。frozen **不豁免**
|
|
54
|
+
* 起点段(OR 只包住 TTL 段、AND 结构天然执法——m10 一锁双杀位)。
|
|
55
|
+
*
|
|
56
|
+
* 两形均带一个 now 绑定参数(注入形共两个——TTL 一个 + 起点一个)。
|
|
57
|
+
*/
|
|
58
|
+
const TTL_COND_MANAGEMENT = `(frozen = 1 OR expires_at IS NULL OR expires_at > ?)`;
|
|
59
|
+
const TTL_COND_INJECT = `(frozen = 1 OR expires_at IS NULL OR expires_at > ?)
|
|
60
|
+
AND (valid_from IS NULL OR valid_from <= ?)`;
|
|
61
|
+
/** memories 查列清单(单源——get/listVisible/目标扫描共用) */
|
|
62
|
+
const MEMORY_COLUMNS = `id, owner_key, kind, summary, content, confidence, evidence_count, status,
|
|
63
|
+
superseded_by, source_refs, created_at, updated_at, usage_count,
|
|
64
|
+
last_used_at, corrected_count, frozen, ttl_days, expires_at, valid_from`;
|
|
65
|
+
/** source_refs 解析(坏形 JSON 按 [] 兜底——读面不炸、写面才执法) */
|
|
66
|
+
function parseSourceRefs(raw) {
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** DB 行 → 驼峰行 */
|
|
76
|
+
function mapRow(row) {
|
|
77
|
+
return {
|
|
78
|
+
id: row.id,
|
|
79
|
+
ownerKey: row.owner_key,
|
|
80
|
+
kind: row.kind,
|
|
81
|
+
summary: row.summary,
|
|
82
|
+
content: row.content,
|
|
83
|
+
confidence: row.confidence,
|
|
84
|
+
evidenceCount: row.evidence_count,
|
|
85
|
+
status: row.status,
|
|
86
|
+
supersededBy: row.superseded_by,
|
|
87
|
+
sourceRefs: parseSourceRefs(row.source_refs),
|
|
88
|
+
createdAt: row.created_at,
|
|
89
|
+
updatedAt: row.updated_at,
|
|
90
|
+
usageCount: row.usage_count,
|
|
91
|
+
lastUsedAt: row.last_used_at,
|
|
92
|
+
correctedCount: row.corrected_count,
|
|
93
|
+
frozen: row.frozen === 1,
|
|
94
|
+
ttlDays: row.ttl_days,
|
|
95
|
+
expiresAt: row.expires_at,
|
|
96
|
+
validFrom: row.valid_from,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** 版本 DB 行 → 驼峰行 */
|
|
100
|
+
function mapVersionRow(row) {
|
|
101
|
+
return {
|
|
102
|
+
id: row.id,
|
|
103
|
+
memoryId: row.memory_id,
|
|
104
|
+
revision: row.revision,
|
|
105
|
+
ownerKey: row.owner_key,
|
|
106
|
+
kind: row.kind,
|
|
107
|
+
summary: row.summary,
|
|
108
|
+
content: row.content,
|
|
109
|
+
confidence: row.confidence,
|
|
110
|
+
evidenceCount: row.evidence_count,
|
|
111
|
+
cause: row.cause,
|
|
112
|
+
reason: row.reason,
|
|
113
|
+
createdAt: row.created_at,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** owner_key 两形判据('global' | 'project:<根路径哈希>'——06 §3 两层) */
|
|
117
|
+
const OWNER_KEY_RE = /^project:[0-9a-f]{8,64}$/i;
|
|
118
|
+
/* ---------------- 工厂 ---------------- */
|
|
119
|
+
/** 建 memory DAO(迁移须已应用——openStore 聚合 MEMORY_MIGRATIONS 后传入) */
|
|
120
|
+
export function createMemoryDao(deps) {
|
|
121
|
+
const { db, warn } = deps;
|
|
122
|
+
const newId = deps.newId ?? (() => uuidv7(deps.now()));
|
|
123
|
+
const stmtGet = db.prepare(`SELECT ${MEMORY_COLUMNS} FROM memories WHERE id = ?`);
|
|
124
|
+
// 注入形(listVisible 本体——常驻简报与 memory_read 无 id 腿共用的读面)
|
|
125
|
+
const stmtListAll = db.prepare(`SELECT ${MEMORY_COLUMNS} FROM memories
|
|
126
|
+
WHERE status = 'active' AND ${TTL_COND_INJECT}
|
|
127
|
+
ORDER BY updated_at DESC`);
|
|
128
|
+
// 合并目标扫描(落码定形注:同 owner+kind、active、frozen=0、TTL 可见、updated_at DESC 首中即断;
|
|
129
|
+
// **管理形**——起点段不过滤〔§5:新证据并入未生效 keep 行、起点不漂移〕)
|
|
130
|
+
const stmtScanTargets = db.prepare(`SELECT ${MEMORY_COLUMNS} FROM memories
|
|
131
|
+
WHERE owner_key = ? AND kind = ? AND status = 'active' AND frozen = 0
|
|
132
|
+
AND ${TTL_COND_MANAGEMENT}
|
|
133
|
+
ORDER BY updated_at DESC`);
|
|
134
|
+
const stmtInsertMemory = db.prepare(`INSERT INTO memories (id, owner_key, kind, summary, content, confidence, evidence_count,
|
|
135
|
+
status, superseded_by, source_refs, created_at, updated_at,
|
|
136
|
+
ttl_days, expires_at, valid_from)
|
|
137
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'active', NULL, ?, ?, ?, ?, ?, ?)`);
|
|
138
|
+
const stmtInsertFts = db.prepare(`INSERT INTO memory_fts (rowid, summary, content) VALUES (?, ?, ?)`);
|
|
139
|
+
const stmtMergeAbsorb = db.prepare(`UPDATE memories SET confidence = ?, evidence_count = evidence_count + 1,
|
|
140
|
+
source_refs = ?, updated_at = ?
|
|
141
|
+
WHERE id = ?`);
|
|
142
|
+
const stmtDismiss = db.prepare(`UPDATE memories SET status = 'dismissed', superseded_by = 'auto_resolved' WHERE id = ?`);
|
|
143
|
+
const stmtNextRevision = db.prepare(`SELECT COALESCE(MAX(revision), 0) + 1 AS next FROM memory_versions WHERE memory_id = ?`);
|
|
144
|
+
const stmtInsertVersion = db.prepare(`INSERT INTO memory_versions (id, memory_id, revision, owner_key, kind, summary, content,
|
|
145
|
+
confidence, evidence_count, cause, reason, created_at)
|
|
146
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
147
|
+
const stmtVersions = db.prepare(`SELECT id, memory_id, revision, owner_key, kind, summary, content, confidence,
|
|
148
|
+
evidence_count, cause, reason, created_at
|
|
149
|
+
FROM memory_versions WHERE memory_id = ? ORDER BY revision`);
|
|
150
|
+
const stmtRebuildFts = db.prepare(`INSERT INTO memory_fts (memory_fts) VALUES ('rebuild')`);
|
|
151
|
+
/* ---------------- 持有面动词与访问面语句(18c-2) ---------------- */
|
|
152
|
+
const stmtGetRowid = db.prepare(`SELECT rowid AS rid FROM memories WHERE id = ?`);
|
|
153
|
+
// external-content 删除(携**旧值**——FTS5 投影同步的删除语义面,内容回滚前清旧投影)
|
|
154
|
+
const stmtFtsDelete = db.prepare(`INSERT INTO memory_fts (memory_fts, rowid, summary, content) VALUES ('delete', ?, ?, ?)`);
|
|
155
|
+
const stmtDismissUser = db.prepare(`UPDATE memories SET status = 'dismissed', superseded_by = ? WHERE id = ?`);
|
|
156
|
+
// 状态复活(纯状态变更——不动 updated_at 不追加版本,同极性新胜旧条纪律)
|
|
157
|
+
const stmtRevive = db.prepare(`UPDATE memories SET status = 'active', superseded_by = NULL, expires_at = ? WHERE id = ?`);
|
|
158
|
+
// 内容回滚 + 复活一体(六列回写 + 状态面 + 续期 + updated_at——内容面变更腿)
|
|
159
|
+
const stmtRollback = db.prepare(`UPDATE memories SET owner_key = ?, kind = ?, summary = ?, content = ?, confidence = ?,
|
|
160
|
+
evidence_count = ?, status = 'active', superseded_by = NULL,
|
|
161
|
+
expires_at = ?, updated_at = ?
|
|
162
|
+
WHERE id = ?`);
|
|
163
|
+
const stmtFreeze = db.prepare(`UPDATE memories SET frozen = 1 WHERE id = ?`);
|
|
164
|
+
const stmtUnfreeze = db.prepare(`UPDATE memories SET frozen = 0, expires_at = ? WHERE id = ?`);
|
|
165
|
+
const stmtSetTtl = db.prepare(`UPDATE memories SET ttl_days = ?, expires_at = ? WHERE id = ?`);
|
|
166
|
+
const stmtSetTtlPolicy = db.prepare(`UPDATE memories SET ttl_days = ? WHERE id = ?`);
|
|
167
|
+
/* ---------------- 整理面语句(18c-5——absorb/decay/sweepExpired) ---------------- */
|
|
168
|
+
// absorb 物理形(与三分支吸收合并同族但 evidence 全数过继——非 +1 象征位)
|
|
169
|
+
const stmtAbsorbFull = db.prepare(`UPDATE memories SET confidence = ?, evidence_count = ?, source_refs = ?, updated_at = ?
|
|
170
|
+
WHERE id = ?`);
|
|
171
|
+
// decay 物化(只动 confidence——不刷 updated_at,防洗新出老化候选集)
|
|
172
|
+
const stmtDecay = db.prepare(`UPDATE memories SET confidence = ? WHERE id = ?`);
|
|
173
|
+
// TTL 物化(纯状态变更——frozen 免、不动 updated_at 不追加版本)
|
|
174
|
+
const stmtSweepExpired = db.prepare(`UPDATE memories SET status = 'expired', superseded_by = 'ttl'
|
|
175
|
+
WHERE status = 'active' AND frozen = 0 AND expires_at IS NOT NULL AND expires_at <= ?`);
|
|
176
|
+
// 访问日志窗口清扫(批 18c-8——与 TTL 物化同拍同事务;窗口下界整行删,聚合列不回退)
|
|
177
|
+
const stmtSweepAccess = db.prepare(`DELETE FROM memory_access WHERE ts <= ?`);
|
|
178
|
+
const stmtGetVersion = db.prepare(`SELECT id, memory_id, revision, owner_key, kind, summary, content, confidence,
|
|
179
|
+
evidence_count, cause, reason, created_at
|
|
180
|
+
FROM memory_versions WHERE memory_id = ? AND revision = ?`);
|
|
181
|
+
const stmtInsertAccess = db.prepare(`INSERT INTO memory_access (id, memory_id, op, session_id, ts) VALUES (?, ?, ?, ?, ?)`);
|
|
182
|
+
// 效用回写(批 18c-7——cite 四写一体):expired 已物化行整行跳过(WHERE 拒改),
|
|
183
|
+
// 续期仅 ttl_days 非 NULL 行重算 now + ttl_days × 天毫秒(NULL 永久行钟不动——
|
|
184
|
+
// CASE 保 expires_at 原值;天毫秒经参数绑定——MEMORY_DAY_MS 单源不落 SQL 字面量)
|
|
185
|
+
const stmtMarkUsed = db.prepare(`UPDATE memories SET usage_count = usage_count + 1, last_used_at = ?,
|
|
186
|
+
expires_at = CASE WHEN ttl_days IS NOT NULL THEN ? + ttl_days * ? ELSE expires_at END
|
|
187
|
+
WHERE id = ? AND status != 'expired'`);
|
|
188
|
+
// 短 id 归责(substr 定长前缀比对——与 accessLog 前缀过滤同法,免 LIKE 通配转义面);
|
|
189
|
+
// 起点段过滤(注入形同谓词——归责属效用计量面,未生效行不参与,接口 JSDoc 同注)
|
|
190
|
+
const stmtResolvePrefix = db.prepare(`SELECT id FROM memories WHERE substr(id, 1, 8) = ? AND (valid_from IS NULL OR valid_from <= ?)`);
|
|
191
|
+
// 纠正负效用回写(2026-09-08 消化批——§3 守卫①③):**只动 corrected_count**——
|
|
192
|
+
// 不保活(usage/last_used/expires 全不动);**无状态过滤**(终态行照记——
|
|
193
|
+
// 对照 markUsed 的 expired 跳过:那是防续期复活,负效用无此面)
|
|
194
|
+
const stmtMarkCorrected = db.prepare(`UPDATE memories SET corrected_count = corrected_count + 1 WHERE id = ?`);
|
|
195
|
+
// 健康面计数(/memory 管理面同源——按状态逐状态取数;全库不分 owner 假精度)
|
|
196
|
+
const stmtHealthStatuses = db.prepare(`SELECT status, count(*) AS n FROM memories GROUP BY status`);
|
|
197
|
+
const stmtHealthFrozen = db.prepare(`SELECT count(*) AS n FROM memories WHERE frozen = 1`);
|
|
198
|
+
const stmtHealthTotal = db.prepare(`SELECT count(*) AS n FROM memories`);
|
|
199
|
+
/* ---------------- 导入导出语句(18c-8——listForExport/importInsert) ---------------- */
|
|
200
|
+
// 导出取数(全状态含终态行 + TTL 不可见行——恢复式备份语义;id 升序确定性排列〔可 diff〕)
|
|
201
|
+
const stmtListForExportAll = db.prepare(`SELECT ${MEMORY_COLUMNS} FROM memories ORDER BY id`);
|
|
202
|
+
const stmtListForExportOwner = db.prepare(`SELECT ${MEMORY_COLUMNS} FROM memories WHERE owner_key = ? ORDER BY id`);
|
|
203
|
+
// 导入直插(全列 INSERT——id/owner_key/状态列原值;stmtInsertMemory 硬编码
|
|
204
|
+
// 'active'/NULL 不可复用,导入是状态面第二写点故独立语句);corrected_count
|
|
205
|
+
// 为容错位(旧 17 列文件缺席按 DEFAULT 0 收——port 解析面单点判定)、
|
|
206
|
+
// valid_from 同律容错(旧 18 列文件缺席按 NULL 收)
|
|
207
|
+
const stmtImportInsert = db.prepare(`INSERT INTO memories (id, owner_key, kind, summary, content, confidence, evidence_count,
|
|
208
|
+
status, superseded_by, source_refs, created_at, updated_at,
|
|
209
|
+
usage_count, last_used_at, corrected_count, frozen, ttl_days,
|
|
210
|
+
expires_at, valid_from)
|
|
211
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
212
|
+
// 导入链行直插(批 ev-1——随包 versions 原值直搬:id/revision/cause/reason/
|
|
213
|
+
// 快照六列/created_at 全原值。**revision 原值直搬是 restore 参数跨机互操作的
|
|
214
|
+
// 前提**;与无链行首版快造用本库 now 分立——历史事实不自造钟)
|
|
215
|
+
const stmtImportInsertVersion = db.prepare(`INSERT INTO memory_versions (id, memory_id, revision, owner_key, kind, summary, content,
|
|
216
|
+
confidence, evidence_count, cause, reason, created_at)
|
|
217
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
218
|
+
// 谱系前身反查(superseded_by 以 'llm:<本id>' 形指向本条的终态行——合并吸收
|
|
219
|
+
// 面与搬家前身;audit 面不过滤 valid_from——未生效行谱系照可查)
|
|
220
|
+
const stmtPredecessors = db.prepare(`SELECT ${MEMORY_COLUMNS} FROM memories WHERE superseded_by = ? ORDER BY created_at DESC`);
|
|
221
|
+
/** 坏形拒(MEMORY_ENTRY_INVALID——闭集/形状/越界判据全清单) */
|
|
222
|
+
function validate(candidate) {
|
|
223
|
+
const problems = [];
|
|
224
|
+
if (!MEMORY_KINDS.includes(candidate.kind))
|
|
225
|
+
problems.push(`kind 非七值闭集:${candidate.kind}`);
|
|
226
|
+
if (candidate.ownerKey !== 'global' && !OWNER_KEY_RE.test(candidate.ownerKey)) {
|
|
227
|
+
problems.push(`owner_key 形违例('global' | 'project:<根路径哈希>' 两形外):${candidate.ownerKey}`);
|
|
228
|
+
}
|
|
229
|
+
if (candidate.summary.trim() === '')
|
|
230
|
+
problems.push('summary 空');
|
|
231
|
+
if (candidate.summary.length > MEMORY_SUMMARY_MAX_CHARS)
|
|
232
|
+
problems.push('summary 超帽');
|
|
233
|
+
if (candidate.content.trim() === '')
|
|
234
|
+
problems.push('content 空');
|
|
235
|
+
if (candidate.content.length > MEMORY_CONTENT_MAX_CHARS)
|
|
236
|
+
problems.push('content 超帽');
|
|
237
|
+
if (!Number.isFinite(candidate.confidence) || candidate.confidence < 0 || candidate.confidence > 1) {
|
|
238
|
+
problems.push(`confidence 越界 [0,1]:${candidate.confidence}`);
|
|
239
|
+
}
|
|
240
|
+
for (const [i, ref] of candidate.sourceRefs.entries()) {
|
|
241
|
+
if (typeof ref?.sessionId !== 'string' || ref.sessionId === '' || !Number.isInteger(ref.seq) || ref.seq < 0) {
|
|
242
|
+
problems.push(`source_refs[${i}] 坏形({sessionId, seq} 形外)`);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// ttl_days 形(18c-2 扩——正整数或 null/缺席;标记即算 expires_at)
|
|
247
|
+
if (candidate.ttlDays !== undefined &&
|
|
248
|
+
candidate.ttlDays !== null &&
|
|
249
|
+
(!Number.isInteger(candidate.ttlDays) || candidate.ttlDays < 1)) {
|
|
250
|
+
problems.push(`ttl_days 形违例(正整数或 null):${candidate.ttlDays}`);
|
|
251
|
+
}
|
|
252
|
+
// valid_from 形(批 ev-1——Unix 毫秒整数或 null/缺席;ISO→毫秒转换在工具面,
|
|
253
|
+
// 此处只收毫秒形。工具面坏 ISO 拒 MEMORY_ENTRY_INVALID 与本判定同码同判据族)
|
|
254
|
+
if (candidate.validFrom !== undefined && candidate.validFrom !== null && !Number.isInteger(candidate.validFrom)) {
|
|
255
|
+
problems.push(`valid_from 形违例(Unix 毫秒整数或 null):${String(candidate.validFrom)}`);
|
|
256
|
+
}
|
|
257
|
+
if (problems.length > 0) {
|
|
258
|
+
throw new BaseError('MEMORY_ENTRY_INVALID', `记忆候选坏形拒:${problems.join(';')}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** 作用行守卫(MEMORY_NOT_FOUND——GOAL_NOT_FOUND 同构;工具动词与 /memory 管理面共用) */
|
|
262
|
+
function mustGet(id) {
|
|
263
|
+
const row = stmtGet.get(id);
|
|
264
|
+
if (!row)
|
|
265
|
+
throw new BaseError('MEMORY_NOT_FOUND', `记忆条目缺席:${id}`);
|
|
266
|
+
return mapRow(row);
|
|
267
|
+
}
|
|
268
|
+
/** 晋升搬家技能名校验(^[a-z0-9]+(-[a-z0-9]+)*$ 且 ≤64——与技能侧 name 校验同源的纯字面量档) */
|
|
269
|
+
function validSkillName(name) {
|
|
270
|
+
if (name.length > MEMORY_SKILL_NAME_MAX || !MEMORY_SKILL_NAME_RE.test(name)) {
|
|
271
|
+
throw new BaseError('MEMORY_ENTRY_INVALID', `promotedToSkill 技能名词法违例(^[a-z0-9]+(?:-[a-z0-9]+)*$ 且 ≤${MEMORY_SKILL_NAME_MAX}):${name}`);
|
|
272
|
+
}
|
|
273
|
+
return name;
|
|
274
|
+
}
|
|
275
|
+
/** 行帽钳制(1..max;非有限数走缺省) */
|
|
276
|
+
function clampLimit(value, fallback, max) {
|
|
277
|
+
const n = typeof value === 'number' && Number.isFinite(value) ? Math.floor(value) : fallback;
|
|
278
|
+
return Math.min(Math.max(n, 1), max);
|
|
279
|
+
}
|
|
280
|
+
/** 按 id 重取现行行(事务尾读——回执统一取变更后状态) */
|
|
281
|
+
function reload(id) {
|
|
282
|
+
return mapRow(stmtGet.get(id));
|
|
283
|
+
}
|
|
284
|
+
/** 版本链拍照(append-only——快照六列取**变更后**的现行值;单事务内由调用方保证) */
|
|
285
|
+
function appendVersion(memoryId, face, cause, now,
|
|
286
|
+
/** 因由叙述(批 ev-1 reason 入链——LLM absorb 携护栏校验后的建议 reason /
|
|
287
|
+
* decay 携判据描述;缺省 null:insert/rollback 与确定性三分支合并无自由文本) */
|
|
288
|
+
reason) {
|
|
289
|
+
const revision = stmtNextRevision.get(memoryId).next;
|
|
290
|
+
stmtInsertVersion.run(newId(), memoryId, revision, face.ownerKey, face.kind, face.summary, face.content, face.confidence, face.evidenceCount, cause, reason ?? null, now);
|
|
291
|
+
}
|
|
292
|
+
/** 新条目落库(独立插入 / 极性新胜同型——差异只在证据计数与 refs 继承,调用方定值) */
|
|
293
|
+
function insertEntry(candidate, evidenceCount, sourceRefs, now) {
|
|
294
|
+
const id = newId();
|
|
295
|
+
// ttl 标记即算(06 §7 memory_write 扩参——仅独立插入腿生效,合并腿不动既有策略)
|
|
296
|
+
const ttlDays = candidate.ttlDays ?? null;
|
|
297
|
+
const expiresAt = ttlDays !== null ? now + ttlDays * MEMORY_DAY_MS : null;
|
|
298
|
+
// 生效起点原值落库(批 ev-1——与 ttlDays 同律仅独立插入腿;合并吸收腿
|
|
299
|
+
// stmtMergeAbsorb 不写 valid_from,起点不漂移)
|
|
300
|
+
const validFrom = candidate.validFrom ?? null;
|
|
301
|
+
const info = stmtInsertMemory.run(id, candidate.ownerKey, candidate.kind, candidate.summary, candidate.content, candidate.confidence, evidenceCount, JSON.stringify(sourceRefs), now, now, ttlDays, expiresAt, validFrom);
|
|
302
|
+
// FTS external-content 同步:新行进投影(真身 rowid 即 lastInsertRowid)
|
|
303
|
+
stmtInsertFts.run(Number(info.lastInsertRowid), candidate.summary, candidate.content);
|
|
304
|
+
appendVersion(id, {
|
|
305
|
+
ownerKey: candidate.ownerKey,
|
|
306
|
+
kind: candidate.kind,
|
|
307
|
+
summary: candidate.summary,
|
|
308
|
+
content: candidate.content,
|
|
309
|
+
confidence: candidate.confidence,
|
|
310
|
+
evidenceCount,
|
|
311
|
+
}, 'insert', now);
|
|
312
|
+
return id;
|
|
313
|
+
}
|
|
314
|
+
/** ingest 事务体(扫描 → 逐行裁决首中即断 → 分支落库) */
|
|
315
|
+
const ingestTx = db.transaction((candidate) => {
|
|
316
|
+
const now = deps.now();
|
|
317
|
+
// —— 写前 secret 扫描(入库单点执法——四写点汇入面;诊断不回写疑似密钥本体)
|
|
318
|
+
const summaryHits = scanForSecrets(candidate.summary);
|
|
319
|
+
const contentHits = scanForSecrets(candidate.content);
|
|
320
|
+
if (summaryHits.length > 0 || contentHits.length > 0) {
|
|
321
|
+
const faces = [summaryHits.length > 0 && 'summary', contentHits.length > 0 && 'content']
|
|
322
|
+
.filter(Boolean)
|
|
323
|
+
.join('/');
|
|
324
|
+
const patterns = [...summaryHits, ...contentHits].map((h) => h.pattern).join(', ');
|
|
325
|
+
warn(`[memory] 写前 secret 扫描命中拒写:pattern=${patterns}(面:${faces})——疑似密钥本体不入诊断`);
|
|
326
|
+
throw new BaseError('MEMORY_SECRET_DETECTED', `写前 secret 扫描命中拒写(pattern:${patterns};面:${faces})`);
|
|
327
|
+
}
|
|
328
|
+
// —— 三分支:目标扫描(同 owner+kind、active、非 frozen、TTL 可见、最新摄入优先)
|
|
329
|
+
const targets = stmtScanTargets.all(candidate.ownerKey, candidate.kind, now);
|
|
330
|
+
for (const target of targets) {
|
|
331
|
+
const row = mapRow(target);
|
|
332
|
+
const decision = decideMerge(row, candidate);
|
|
333
|
+
if (decision.branch === 'exact' || decision.branch === 'fuzzy') {
|
|
334
|
+
// 分支 1/2:吸收合并——evidence++、confidence 取 max、refs 并集、updated_at 刷新。
|
|
335
|
+
// summary/content 不变 → FTS 投影零触达(external-content 只同步文本面变更)
|
|
336
|
+
const mergedRefs = unionSourceRefs(row.sourceRefs, candidate.sourceRefs);
|
|
337
|
+
const mergedConfidence = Math.max(row.confidence, candidate.confidence);
|
|
338
|
+
stmtMergeAbsorb.run(mergedConfidence, JSON.stringify(mergedRefs), now, row.id);
|
|
339
|
+
appendVersion(row.id, {
|
|
340
|
+
ownerKey: row.ownerKey,
|
|
341
|
+
kind: row.kind,
|
|
342
|
+
summary: row.summary,
|
|
343
|
+
content: row.content,
|
|
344
|
+
confidence: mergedConfidence,
|
|
345
|
+
evidenceCount: row.evidenceCount + 1,
|
|
346
|
+
}, 'merge', now);
|
|
347
|
+
return { action: decision.branch === 'exact' ? 'merged-exact' : 'merged-fuzzy', id: row.id };
|
|
348
|
+
}
|
|
349
|
+
if (decision.branch === 'polarity') {
|
|
350
|
+
if (decision.winner === 'incoming') {
|
|
351
|
+
// 分支 3 新胜:旧条 dismissed+auto_resolved(纯状态变更——不动 updated_at
|
|
352
|
+
// 防污染摄入水位与老化锚、不追加版本——06 §3「纯状态变更不追加」);
|
|
353
|
+
// 新条入库继承双方证据计数与 refs 并集(血缘继承:条目消亡,溯源不死)
|
|
354
|
+
const inheritedRefs = unionSourceRefs(row.sourceRefs, candidate.sourceRefs);
|
|
355
|
+
stmtDismiss.run(row.id);
|
|
356
|
+
const id = insertEntry(candidate, row.evidenceCount + 1, inheritedRefs, now);
|
|
357
|
+
return { action: 'inserted', id, supersededId: row.id };
|
|
358
|
+
}
|
|
359
|
+
// 分支 3 旧胜(对称吸收——落码定形注):旧条原位吸收候选证据与 refs,
|
|
360
|
+
// confidence 已高不动;候选不另立条目(落败来源不蒸腾)
|
|
361
|
+
const absorbedRefs = unionSourceRefs(row.sourceRefs, candidate.sourceRefs);
|
|
362
|
+
stmtMergeAbsorb.run(row.confidence, JSON.stringify(absorbedRefs), now, row.id);
|
|
363
|
+
appendVersion(row.id, {
|
|
364
|
+
ownerKey: row.ownerKey,
|
|
365
|
+
kind: row.kind,
|
|
366
|
+
summary: row.summary,
|
|
367
|
+
content: row.content,
|
|
368
|
+
confidence: row.confidence,
|
|
369
|
+
evidenceCount: row.evidenceCount + 1,
|
|
370
|
+
}, 'merge', now);
|
|
371
|
+
return { action: 'polarity-kept-existing', id: row.id };
|
|
372
|
+
}
|
|
373
|
+
// branch='none'——继续下一行(首中即断不成立时逐行比下去)
|
|
374
|
+
}
|
|
375
|
+
// —— 无中选行:独立新条目(插入即落首版 cause='insert')
|
|
376
|
+
const id = insertEntry(candidate, 1, candidate.sourceRefs.slice(0, MEMORY_SOURCE_REFS_CAP), now);
|
|
377
|
+
return { action: 'inserted', id };
|
|
378
|
+
});
|
|
379
|
+
/* ---------------- 持有面动词事务体(18c-2——同 ingest 单事务纪律) ---------------- */
|
|
380
|
+
/**
|
|
381
|
+
* forget:软删纯状态变更(不动 updated_at、不追加版本——§3 纪律)。
|
|
382
|
+
* 检查序 = missing → frozen 拒 → **dismissed 终态短路**(幂等返回现行行,
|
|
383
|
+
* 不覆写 superseded_by——用户终审 'user'/'skill:<名>' 不被后到 'llm:<id>'
|
|
384
|
+
* 腿覆盖;06 §5 触发与护栏第四件配套,批 18c-5 兑现)。
|
|
385
|
+
* 终态来源优先级:supersededBy 直取值('llm:<keepId>' 消费形)> promotedToSkill
|
|
386
|
+
* 搬家腿(词法前置校验)> 'user' 缺省。
|
|
387
|
+
*/
|
|
388
|
+
const forgetTx = db.transaction((id, promotedToSkill, supersededBy) => {
|
|
389
|
+
const row = mustGet(id);
|
|
390
|
+
if (row.frozen) {
|
|
391
|
+
throw new BaseError('MEMORY_FROZEN', `条目已冻结(forget 撞 frozen 拒——解冻-再忘唯一路径):${id}`);
|
|
392
|
+
}
|
|
393
|
+
if (row.status === 'dismissed')
|
|
394
|
+
return row; // 终态短路——先到终审定格
|
|
395
|
+
const origin = supersededBy !== undefined
|
|
396
|
+
? supersededBy
|
|
397
|
+
: promotedToSkill === undefined
|
|
398
|
+
? 'user'
|
|
399
|
+
: `skill:${validSkillName(promotedToSkill)}`;
|
|
400
|
+
stmtDismissUser.run(origin, id);
|
|
401
|
+
return reload(id);
|
|
402
|
+
});
|
|
403
|
+
/** absorb:显式合并物理动作(consolidation 执行腿——单事务:过继 + 版本 + drop 终态) */
|
|
404
|
+
const absorbTx = db.transaction((keepId, dropId, reason) => {
|
|
405
|
+
if (keepId === dropId) {
|
|
406
|
+
throw new BaseError('MEMORY_ENTRY_INVALID', `absorb 自指拒(keep 与 drop 同 id):${keepId}`);
|
|
407
|
+
}
|
|
408
|
+
const keep = mustGet(keepId);
|
|
409
|
+
const drop = mustGet(dropId);
|
|
410
|
+
// frozen 免整理全档(任一侧);整理面只作用在册行
|
|
411
|
+
if (keep.frozen || drop.frozen) {
|
|
412
|
+
throw new BaseError('MEMORY_FROZEN', `absorb 撞冻结拒(frozen 免整理):keep=${keepId} drop=${dropId}`);
|
|
413
|
+
}
|
|
414
|
+
if (keep.status !== 'active' || drop.status !== 'active') {
|
|
415
|
+
throw new BaseError('MEMORY_ENTRY_INVALID', `absorb 只作用在册行:keep=${keepId}(${keep.status}) drop=${dropId}(${drop.status})`);
|
|
416
|
+
}
|
|
417
|
+
const now = deps.now();
|
|
418
|
+
// 过继四件:evidence 全数(非 +1 象征位)/ confidence max / refs 并集(帽内——血缘继承)/ updated_at 刷新
|
|
419
|
+
const mergedRefs = unionSourceRefs(keep.sourceRefs, drop.sourceRefs);
|
|
420
|
+
const mergedConfidence = Math.max(keep.confidence, drop.confidence);
|
|
421
|
+
const mergedEvidence = keep.evidenceCount + drop.evidenceCount;
|
|
422
|
+
stmtAbsorbFull.run(mergedConfidence, mergedEvidence, JSON.stringify(mergedRefs), now, keepId);
|
|
423
|
+
appendVersion(keepId, {
|
|
424
|
+
ownerKey: keep.ownerKey,
|
|
425
|
+
kind: keep.kind,
|
|
426
|
+
summary: keep.summary,
|
|
427
|
+
content: keep.content,
|
|
428
|
+
confidence: mergedConfidence,
|
|
429
|
+
evidenceCount: mergedEvidence,
|
|
430
|
+
}, 'merge', now,
|
|
431
|
+
// 批 ev-1 reason 入链:consolidation 携 §5 护栏校验后的 LLM 建议组 reason
|
|
432
|
+
reason ?? null);
|
|
433
|
+
// drop 终态(同事务内联——active 前置检查已等价 forget 终态短路的检查面)
|
|
434
|
+
stmtDismissUser.run(`llm:${keepId}`, dropId);
|
|
435
|
+
return reload(keepId);
|
|
436
|
+
});
|
|
437
|
+
/** decay:降权物化(confidence × factor + 版本 cause='decay'——不刷 updated_at) */
|
|
438
|
+
const decayTx = db.transaction((id, factor, reason) => {
|
|
439
|
+
if (!Number.isFinite(factor) || factor <= 0 || factor > 1) {
|
|
440
|
+
throw new BaseError('MEMORY_ENTRY_INVALID', `decay factor 形违例((0,1] 区间):${factor}`);
|
|
441
|
+
}
|
|
442
|
+
const now = deps.now();
|
|
443
|
+
const row = mustGet(id);
|
|
444
|
+
if (row.frozen) {
|
|
445
|
+
throw new BaseError('MEMORY_FROZEN', `条目已冻结(frozen 免整理——decay 拒):${id}`);
|
|
446
|
+
}
|
|
447
|
+
if (row.status !== 'active') {
|
|
448
|
+
throw new BaseError('MEMORY_ENTRY_INVALID', `decay 只作用在册行(${row.status}):${id}`);
|
|
449
|
+
}
|
|
450
|
+
const next = row.confidence * factor;
|
|
451
|
+
stmtDecay.run(next, id);
|
|
452
|
+
appendVersion(id, {
|
|
453
|
+
ownerKey: row.ownerKey,
|
|
454
|
+
kind: row.kind,
|
|
455
|
+
summary: row.summary,
|
|
456
|
+
content: row.content,
|
|
457
|
+
confidence: next,
|
|
458
|
+
evidenceCount: row.evidenceCount,
|
|
459
|
+
}, 'decay', now,
|
|
460
|
+
// 批 ev-1 reason 入链:consolidation decay 判据描述(自由文本)
|
|
461
|
+
reason ?? null);
|
|
462
|
+
return reload(id);
|
|
463
|
+
});
|
|
464
|
+
/** restore:带版本 ⊃ 状态复活——两腿都按 ttl_days 重算续期;回滚腿 FTS 投影同步 + rollback 版本追加 */
|
|
465
|
+
const restoreTx = db.transaction((id, revision) => {
|
|
466
|
+
const now = deps.now();
|
|
467
|
+
const row = mustGet(id);
|
|
468
|
+
const expiresAt = row.ttlDays !== null ? now + row.ttlDays * MEMORY_DAY_MS : null;
|
|
469
|
+
if (revision === undefined) {
|
|
470
|
+
// 状态复活腿:现行内容不变——纯状态变更(复活唯 restore 的「复活」语义落位)
|
|
471
|
+
stmtRevive.run(expiresAt, id);
|
|
472
|
+
return reload(id);
|
|
473
|
+
}
|
|
474
|
+
// 内容回滚腿撞 frozen 免覆写(纯状态复活腿不动内容面——frozen 行无此拒)
|
|
475
|
+
if (row.frozen) {
|
|
476
|
+
throw new BaseError('MEMORY_FROZEN', `条目已冻结(restore 带 revision 内容回滚撞 frozen 免覆写):${id}`);
|
|
477
|
+
}
|
|
478
|
+
const v = stmtGetVersion.get(id, revision);
|
|
479
|
+
if (!v) {
|
|
480
|
+
// 无链条目带版本拒 / revision 越界——同码(版本缺席是统一执法面)
|
|
481
|
+
throw new BaseError('MEMORY_REVISION_NOT_FOUND', `版本缺席:${id}#revision=${revision}`);
|
|
482
|
+
}
|
|
483
|
+
const rid = stmtGetRowid.get(id).rid;
|
|
484
|
+
// FTS external-content 同步:删旧投影(携旧值)→ 六列回写 → 插新投影
|
|
485
|
+
stmtFtsDelete.run(rid, row.summary, row.content);
|
|
486
|
+
stmtRollback.run(v.owner_key, v.kind, v.summary, v.content, v.confidence, v.evidence_count, expiresAt, now, id);
|
|
487
|
+
stmtInsertFts.run(rid, v.summary, v.content);
|
|
488
|
+
appendVersion(id, {
|
|
489
|
+
ownerKey: v.owner_key,
|
|
490
|
+
kind: v.kind,
|
|
491
|
+
summary: v.summary,
|
|
492
|
+
content: v.content,
|
|
493
|
+
confidence: v.confidence,
|
|
494
|
+
evidenceCount: v.evidence_count,
|
|
495
|
+
}, 'rollback', now);
|
|
496
|
+
return reload(id);
|
|
497
|
+
});
|
|
498
|
+
/** freeze:幂等(重复冻结无害);纯持有面不动 updated_at(不污染老化锚) */
|
|
499
|
+
const freezeTx = db.transaction((id) => {
|
|
500
|
+
mustGet(id);
|
|
501
|
+
stmtFreeze.run(id);
|
|
502
|
+
return reload(id);
|
|
503
|
+
});
|
|
504
|
+
/** unfreeze:幂等;解冻即按 ttl_days 重算钟(冻结期不计时——重算非续算) */
|
|
505
|
+
const unfreezeTx = db.transaction((id) => {
|
|
506
|
+
const now = deps.now();
|
|
507
|
+
const row = mustGet(id);
|
|
508
|
+
const expiresAt = row.ttlDays !== null ? now + row.ttlDays * MEMORY_DAY_MS : null;
|
|
509
|
+
stmtUnfreeze.run(expiresAt, id);
|
|
510
|
+
return reload(id);
|
|
511
|
+
});
|
|
512
|
+
/** setTtl:有效可见行物化重算立即生效;已过期/终态行仅改未来策略(复活唯 restore) */
|
|
513
|
+
const setTtlTx = db.transaction((id, days) => {
|
|
514
|
+
const now = deps.now();
|
|
515
|
+
if (days !== null && (!Number.isInteger(days) || days < 1)) {
|
|
516
|
+
throw new BaseError('MEMORY_ENTRY_INVALID', `ttl days 形违例(正整数或 null):${days}`);
|
|
517
|
+
}
|
|
518
|
+
const row = mustGet(id);
|
|
519
|
+
if (row.frozen) {
|
|
520
|
+
throw new BaseError('MEMORY_FROZEN', `条目已冻结(frozen 免 TTL——setTtl 拒;解冻-再设唯一路径):${id}`);
|
|
521
|
+
}
|
|
522
|
+
if (row.status === 'active' && (row.expiresAt === null || row.expiresAt > now)) {
|
|
523
|
+
stmtSetTtl.run(days, days === null ? null : now + days * MEMORY_DAY_MS, id);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
// 终态/已过期行:expires_at 不动(读面谓词不因改策略复活),策略面供 restore/unfreeze 重算消费
|
|
527
|
+
stmtSetTtlPolicy.run(days, id);
|
|
528
|
+
}
|
|
529
|
+
return reload(id);
|
|
530
|
+
});
|
|
531
|
+
/** markUsed:效用回写批量(批 18c-7——单事务四写一体;流水与聚合同事务) */
|
|
532
|
+
const markUsedTx = db.transaction((ids, sessionId) => {
|
|
533
|
+
const now = deps.now();
|
|
534
|
+
let applied = 0;
|
|
535
|
+
for (const id of ids) {
|
|
536
|
+
const info = stmtMarkUsed.run(now, now, MEMORY_DAY_MS, id);
|
|
537
|
+
// expired 已物化行 changes=0 整行跳过(含流水——终态行不计数不续期)
|
|
538
|
+
if (info.changes > 0) {
|
|
539
|
+
applied += 1;
|
|
540
|
+
stmtInsertAccess.run(newId(), id, 'cite', sessionId, now);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return applied;
|
|
544
|
+
});
|
|
545
|
+
/**
|
|
546
|
+
* markCorrected:纠正负效用回写批量(2026-09-08 消化批——单事务两写一体:
|
|
547
|
+
* corrected_count+1 + op='corrected-cite' 流水)。无状态过滤(终态照记——
|
|
548
|
+
* 守卫③)、零保活面(守卫①——语句本体即防线);缺席 id changes=0 跳过。
|
|
549
|
+
*/
|
|
550
|
+
const markCorrectedTx = db.transaction((ids, sessionId) => {
|
|
551
|
+
const now = deps.now();
|
|
552
|
+
let applied = 0;
|
|
553
|
+
for (const id of ids) {
|
|
554
|
+
const info = stmtMarkCorrected.run(id);
|
|
555
|
+
if (info.changes > 0) {
|
|
556
|
+
applied += 1;
|
|
557
|
+
stmtInsertAccess.run(newId(), id, 'corrected-cite', sessionId, now);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return applied;
|
|
561
|
+
});
|
|
562
|
+
/** sweep 双清同拍单事务(批 18c-8——TTL 物化 + 访问日志窗口清扫,06 §3「同节拍同拍」兑现) */
|
|
563
|
+
const sweepTx = db.transaction((now) => {
|
|
564
|
+
const expired = stmtSweepExpired.run(now).changes;
|
|
565
|
+
// 窗口下界 = now - 90d(天毫秒经参数绑定——MEMORY_ACCESS_WINDOW_DAYS 单源不落 SQL 字面量)
|
|
566
|
+
const accessPruned = stmtSweepAccess.run(now - MEMORY_ACCESS_WINDOW_DAYS * MEMORY_DAY_MS).changes;
|
|
567
|
+
return { expired, accessPruned };
|
|
568
|
+
});
|
|
569
|
+
/**
|
|
570
|
+
* 导入直插事务体(批 18c-8——状态面第二写点):secret 扫描单点执法 →
|
|
571
|
+
* 幂等判定(id 已在整行跳过)→ 三写一体(全列直插 + FTS 投影 + 首版快照)。
|
|
572
|
+
* 行形校验归 port.parseMemoryImportRow(词法判定单点)——本事务只兜
|
|
573
|
+
* 闭集/区间防线(直调误用不破库)。
|
|
574
|
+
*/
|
|
575
|
+
const importInsertTx = db.transaction((row) => {
|
|
576
|
+
// —— 写前 secret 扫描(入库单点执法——导入面即写入面;诊断不回写疑似密钥本体)
|
|
577
|
+
const summaryHits = scanForSecrets(row.summary);
|
|
578
|
+
const contentHits = scanForSecrets(row.content);
|
|
579
|
+
if (summaryHits.length > 0 || contentHits.length > 0) {
|
|
580
|
+
const patterns = [...summaryHits, ...contentHits].map((h) => h.pattern).join(', ');
|
|
581
|
+
warn(`[memory] 导入行写前 secret 扫描命中拒写:pattern=${patterns}(id:${row.id})——疑似密钥本体不入诊断`);
|
|
582
|
+
throw new BaseError('MEMORY_SECRET_DETECTED', `导入行写前 secret 扫描命中拒写(pattern:${patterns})`);
|
|
583
|
+
}
|
|
584
|
+
// —— 域不变量兜底(闭集/owner 形/区间——词法全清单在 port;此处防直调坏形破库)
|
|
585
|
+
const problems = [];
|
|
586
|
+
if (!MEMORY_KINDS.includes(row.kind))
|
|
587
|
+
problems.push(`kind 非七值闭集:${String(row.kind)}`);
|
|
588
|
+
if (!MEMORY_STATUSES.includes(row.status))
|
|
589
|
+
problems.push(`status 非三值闭集:${String(row.status)}`);
|
|
590
|
+
if (row.owner_key !== 'global' && !OWNER_KEY_RE.test(row.owner_key)) {
|
|
591
|
+
problems.push(`owner_key 形违例:${row.owner_key}`);
|
|
592
|
+
}
|
|
593
|
+
if (row.summary.trim() === '' || row.content.trim() === '')
|
|
594
|
+
problems.push('summary/content 空');
|
|
595
|
+
if (!Number.isFinite(row.confidence) || row.confidence < 0 || row.confidence > 1) {
|
|
596
|
+
problems.push(`confidence 越界 [0,1]:${row.confidence}`);
|
|
597
|
+
}
|
|
598
|
+
if (problems.length > 0) {
|
|
599
|
+
throw new BaseError('MEMORY_ENTRY_INVALID', `导入行坏形拒:${problems.join(';')}`);
|
|
600
|
+
}
|
|
601
|
+
// —— 幂等判定:id 已在库整行跳过(恢复式语义——零合并零覆写,**含链**:
|
|
602
|
+
// 在库行的随包版本链不重放不覆盖)
|
|
603
|
+
if (stmtGet.get(row.id) !== undefined)
|
|
604
|
+
return false;
|
|
605
|
+
// —— 三写一体(同事务:主表全列直插 + FTS external-content 投影 + 版本链)
|
|
606
|
+
const info = stmtImportInsert.run(row.id, row.owner_key, row.kind, row.summary, row.content, row.confidence, row.evidence_count, row.status, row.superseded_by, JSON.stringify(row.source_refs), row.created_at, row.updated_at, row.usage_count, row.last_used_at, row.corrected_count ?? 0, row.frozen ? 1 : 0, row.ttl_days, row.expires_at, row.valid_from ?? null);
|
|
607
|
+
stmtInsertFts.run(Number(info.lastInsertRowid), row.summary, row.content);
|
|
608
|
+
if (row.versions && row.versions.length > 0) {
|
|
609
|
+
// 批 ev-1 随包链重建:按 revision 升序逐行原值直搬(id/revision/cause/
|
|
610
|
+
// reason/快照六列/created_at 全原值——历史事实不自造钟;revision 原值是
|
|
611
|
+
// restore 参数跨机互操作的前提)。解析面已保证升序与词法,此处排序兜底。
|
|
612
|
+
for (const v of [...row.versions].sort((a, b) => a.revision - b.revision)) {
|
|
613
|
+
stmtImportInsertVersion.run(v.id, row.id, v.revision, v.owner_key, v.kind, v.summary, v.content, v.confidence, v.evidence_count, v.cause, v.reason ?? null, v.created_at);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
// 无链行首版快造(18c-8 既有律):快照内容面取导入行原值,版本行
|
|
618
|
+
// id/created_at 用本库 now——本库时间线不自外来钟
|
|
619
|
+
appendVersion(row.id, {
|
|
620
|
+
ownerKey: row.owner_key,
|
|
621
|
+
kind: row.kind,
|
|
622
|
+
summary: row.summary,
|
|
623
|
+
content: row.content,
|
|
624
|
+
confidence: row.confidence,
|
|
625
|
+
evidenceCount: row.evidence_count,
|
|
626
|
+
}, 'insert', deps.now());
|
|
627
|
+
}
|
|
628
|
+
return true;
|
|
629
|
+
});
|
|
630
|
+
/* ---------------- 检索与读面实装 ---------------- */
|
|
631
|
+
/**
|
|
632
|
+
* listVisible 本体(**注入形**——批 ev-1 M2 拆形:常驻简报与 memory_read 无
|
|
633
|
+
* id 腿共用的读面,TTL 段 AND 起点段)。管理形消费方(/memory 活体区)走
|
|
634
|
+
* listVisibleForManagementImpl——两法同 SQL 骨架不同谓词形,直在本法扩段
|
|
635
|
+
* 会把管理面未生效行一并滤掉、静默违反 06 §6 呈现边界,故拆形。
|
|
636
|
+
*/
|
|
637
|
+
function listVisibleImpl(ownerKeys) {
|
|
638
|
+
const now = deps.now();
|
|
639
|
+
if (!ownerKeys || ownerKeys.length === 0) {
|
|
640
|
+
return stmtListAll.all(now, now).map(mapRow);
|
|
641
|
+
}
|
|
642
|
+
// owner 并集读(单语句按需 prepare——owner 键组合开放、缓存无意义)
|
|
643
|
+
const placeholders = ownerKeys.map(() => '?').join(', ');
|
|
644
|
+
const stmt = db.prepare(`SELECT ${MEMORY_COLUMNS} FROM memories
|
|
645
|
+
WHERE status = 'active' AND ${TTL_COND_INJECT}
|
|
646
|
+
AND owner_key IN (${placeholders})
|
|
647
|
+
ORDER BY updated_at DESC`);
|
|
648
|
+
return stmt.all(now, now, ...ownerKeys).map(mapRow);
|
|
649
|
+
}
|
|
650
|
+
/** listVisible 管理形(TTL 段 only——/memory 活体区:未生效行可见〔「N天后生效」标注位〕) */
|
|
651
|
+
function listVisibleForManagementImpl(ownerKeys) {
|
|
652
|
+
const now = deps.now();
|
|
653
|
+
if (!ownerKeys || ownerKeys.length === 0) {
|
|
654
|
+
const stmt = db.prepare(`SELECT ${MEMORY_COLUMNS} FROM memories
|
|
655
|
+
WHERE status = 'active' AND ${TTL_COND_MANAGEMENT}
|
|
656
|
+
ORDER BY updated_at DESC`);
|
|
657
|
+
return stmt.all(now).map(mapRow);
|
|
658
|
+
}
|
|
659
|
+
const placeholders = ownerKeys.map(() => '?').join(', ');
|
|
660
|
+
const stmt = db.prepare(`SELECT ${MEMORY_COLUMNS} FROM memories
|
|
661
|
+
WHERE status = 'active' AND ${TTL_COND_MANAGEMENT}
|
|
662
|
+
AND owner_key IN (${placeholders})
|
|
663
|
+
ORDER BY updated_at DESC`);
|
|
664
|
+
return stmt.all(now, ...ownerKeys).map(mapRow);
|
|
665
|
+
}
|
|
666
|
+
/** FTS 检索(记忆库腿——trigram 短语包裹防 MATCH 语法面;命中流水同事务落账) */
|
|
667
|
+
function searchImpl(query, opts) {
|
|
668
|
+
// 短语包裹:剥用户侧引号后整体成 phrase——防 FTS5 MATCH 语法注入(NEAR/AND 等运算符不生效)
|
|
669
|
+
const sanitized = query.replaceAll('"', ' ').trim();
|
|
670
|
+
if (sanitized === '')
|
|
671
|
+
return [];
|
|
672
|
+
const now = deps.now();
|
|
673
|
+
const limit = clampLimit(opts?.limit, MEMORY_SEARCH_DEFAULT_LIMIT, MEMORY_SEARCH_MAX_LIMIT);
|
|
674
|
+
// 注入形谓词(memory_search 命中进模型上下文——起点段同过滤;两 now:TTL + 起点)
|
|
675
|
+
const conds = [
|
|
676
|
+
`m.status = 'active'`,
|
|
677
|
+
`(m.frozen = 1 OR m.expires_at IS NULL OR m.expires_at > ?)
|
|
678
|
+
AND (m.valid_from IS NULL OR m.valid_from <= ?)`,
|
|
679
|
+
];
|
|
680
|
+
const params = [`"${sanitized}"`, now, now];
|
|
681
|
+
if (opts?.kind !== undefined) {
|
|
682
|
+
conds.push(`m.kind = ?`);
|
|
683
|
+
params.push(opts.kind);
|
|
684
|
+
}
|
|
685
|
+
if (opts?.ownerKeys && opts.ownerKeys.length > 0) {
|
|
686
|
+
conds.push(`m.owner_key IN (${opts.ownerKeys.map(() => '?').join(', ')})`);
|
|
687
|
+
params.push(...opts.ownerKeys);
|
|
688
|
+
}
|
|
689
|
+
const stmt = db.prepare(`SELECT m.id AS id, m.owner_key AS ownerKey, m.kind AS kind, m.summary AS summary,
|
|
690
|
+
memory_fts.rank AS score
|
|
691
|
+
FROM memory_fts JOIN memories m ON m.rowid = memory_fts.rowid
|
|
692
|
+
WHERE memory_fts MATCH ? AND ${conds.join(' AND ')}
|
|
693
|
+
ORDER BY memory_fts.rank
|
|
694
|
+
LIMIT ?`);
|
|
695
|
+
const hits = stmt.all(...params, limit);
|
|
696
|
+
if (hits.length > 0) {
|
|
697
|
+
// 命中流水落账(06 §7——缺省 op='search'/session_id NULL〔工具上下文无会话键〕;
|
|
698
|
+
// 按需检索注入路注入 op='recall' + 当轮会话键〔06 §6 三写点单 DAO 实现律〕;
|
|
699
|
+
// 读模型的计量写面,非领域状态变更——聚合只随 cite,流水面不动聚合)
|
|
700
|
+
const op = opts?.accessOp ?? 'search';
|
|
701
|
+
const sessionId = opts?.accessSessionId ?? null;
|
|
702
|
+
const logTx = db.transaction((rows) => {
|
|
703
|
+
const ts = deps.now();
|
|
704
|
+
for (const hit of rows)
|
|
705
|
+
stmtInsertAccess.run(newId(), hit.id, op, sessionId, ts);
|
|
706
|
+
});
|
|
707
|
+
logTx(hits);
|
|
708
|
+
}
|
|
709
|
+
return hits;
|
|
710
|
+
}
|
|
711
|
+
/** memory_read 无 id 腿整面(frozen 恒驻在前 + 效用综合分降序——§5 一把尺;§6 权威 builder 随 18c-4) */
|
|
712
|
+
function overviewImpl(ownerKeys) {
|
|
713
|
+
const visible = listVisibleImpl(ownerKeys);
|
|
714
|
+
const frozenFirst = visible.filter((r) => r.frozen);
|
|
715
|
+
const scored = visible
|
|
716
|
+
.filter((r) => !r.frozen)
|
|
717
|
+
.map((row) => ({ row, score: utilityScore(row) }))
|
|
718
|
+
.sort((a, b) => b.score - a.score)
|
|
719
|
+
.map((x) => x.row);
|
|
720
|
+
const counts = stmtHealthStatuses.all();
|
|
721
|
+
const byStatus = new Map(counts.map((c) => [c.status, c.n]));
|
|
722
|
+
return {
|
|
723
|
+
core: [...frozenFirst, ...scored],
|
|
724
|
+
recent: visible.slice(0, MEMORY_RECENT_LIMIT), // listVisibleImpl 已按 updated_at DESC
|
|
725
|
+
health: {
|
|
726
|
+
active: byStatus.get('active') ?? 0,
|
|
727
|
+
dismissed: byStatus.get('dismissed') ?? 0,
|
|
728
|
+
expired: byStatus.get('expired') ?? 0,
|
|
729
|
+
frozen: stmtHealthFrozen.get().n,
|
|
730
|
+
total: stmtHealthTotal.get().n,
|
|
731
|
+
},
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
/** 访问日志双面查询(聚合 top-N + 流水——前缀/时间窗/op 过滤共用同一套 WHERE) */
|
|
735
|
+
function accessLogImpl(query) {
|
|
736
|
+
const q = query ?? {};
|
|
737
|
+
const conds = [];
|
|
738
|
+
const params = [];
|
|
739
|
+
if (q.memoryIdPrefix !== undefined && q.memoryIdPrefix !== '') {
|
|
740
|
+
// 前缀精确比对(substr 定长截取——免 LIKE 通配转义面)
|
|
741
|
+
conds.push('substr(a.memory_id, 1, ?) = ?');
|
|
742
|
+
params.push(q.memoryIdPrefix.length, q.memoryIdPrefix);
|
|
743
|
+
}
|
|
744
|
+
if (typeof q.from === 'number') {
|
|
745
|
+
conds.push('a.ts >= ?');
|
|
746
|
+
params.push(q.from);
|
|
747
|
+
}
|
|
748
|
+
if (typeof q.to === 'number') {
|
|
749
|
+
conds.push('a.ts <= ?');
|
|
750
|
+
params.push(q.to);
|
|
751
|
+
}
|
|
752
|
+
if (q.op !== undefined) {
|
|
753
|
+
conds.push('a.op = ?');
|
|
754
|
+
params.push(q.op);
|
|
755
|
+
}
|
|
756
|
+
const where = conds.length > 0 ? `WHERE ${conds.join(' AND ')}` : '';
|
|
757
|
+
const limit = clampLimit(q.limit, MEMORY_ACCESS_LOG_DEFAULT_LIMIT, MEMORY_ACCESS_LOG_MAX_LIMIT);
|
|
758
|
+
const flow = db
|
|
759
|
+
.prepare(`SELECT a.id AS id, a.memory_id AS memoryId, a.op AS op, a.session_id AS sessionId, a.ts AS ts
|
|
760
|
+
FROM memory_access a ${where}
|
|
761
|
+
ORDER BY a.ts DESC LIMIT ?`)
|
|
762
|
+
.all(...params, limit);
|
|
763
|
+
const aggregates = db
|
|
764
|
+
.prepare(`SELECT a.memory_id AS memoryId, m.summary AS summary,
|
|
765
|
+
SUM(CASE WHEN a.op = 'recall' THEN 1 ELSE 0 END) AS recall,
|
|
766
|
+
SUM(CASE WHEN a.op = 'search' THEN 1 ELSE 0 END) AS search,
|
|
767
|
+
SUM(CASE WHEN a.op = 'cite' THEN 1 ELSE 0 END) AS cite,
|
|
768
|
+
count(*) AS total
|
|
769
|
+
FROM memory_access a JOIN memories m ON m.id = a.memory_id
|
|
770
|
+
${where}
|
|
771
|
+
GROUP BY a.memory_id
|
|
772
|
+
ORDER BY total DESC
|
|
773
|
+
LIMIT ${MEMORY_ACCESS_AGGREGATE_TOP_N}`)
|
|
774
|
+
.all(...params);
|
|
775
|
+
return { aggregates, flow };
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* 谱系查询(批 ev-1 06 §7 memory_lineage——id 一站三链一读)。纯只读零迁移;
|
|
779
|
+
* 管理面直读语义:不做起点/终态过滤(未生效行谱系照可查——audit 面不过滤
|
|
780
|
+
* valid_from)。successor 解析三形:'llm:<id>' 可导航直取(缺席回退 marker
|
|
781
|
+
* 诚实呈现原记号——链断不造钟);'skill:<名>' 指路名不导航;'user'/
|
|
782
|
+
* 'auto_resolved'/'ttl' 无目标形字面量呈现。active 行 null。
|
|
783
|
+
*/
|
|
784
|
+
function lineageImpl(id) {
|
|
785
|
+
const row = mustGet(id);
|
|
786
|
+
const versions = stmtVersions.all(id).map(mapVersionRow);
|
|
787
|
+
const predecessors = stmtPredecessors.all(`llm:${id}`).map(mapRow);
|
|
788
|
+
let successor = null;
|
|
789
|
+
if (row.supersededBy !== null) {
|
|
790
|
+
const llmTarget = row.supersededBy.startsWith('llm:') ? row.supersededBy.slice('llm:'.length) : null;
|
|
791
|
+
successor =
|
|
792
|
+
llmTarget !== null
|
|
793
|
+
? stmtGet.get(llmTarget) !== undefined
|
|
794
|
+
? mapRow(stmtGet.get(llmTarget))
|
|
795
|
+
: { marker: row.supersededBy } // 链断(目标行缺席)——原记号诚实呈现
|
|
796
|
+
: { marker: row.supersededBy }; // 'skill:<名>'/'user'/'auto_resolved'/'ttl' 无目标形
|
|
797
|
+
}
|
|
798
|
+
return { row, versions, predecessors, successor };
|
|
799
|
+
}
|
|
800
|
+
return {
|
|
801
|
+
ingest(candidate) {
|
|
802
|
+
validate(candidate); // 坏形拒在事务外(不入库不改库——纯请求面校验)
|
|
803
|
+
return ingestTx(candidate);
|
|
804
|
+
},
|
|
805
|
+
get(id) {
|
|
806
|
+
const row = stmtGet.get(id);
|
|
807
|
+
return row ? mapRow(row) : undefined;
|
|
808
|
+
},
|
|
809
|
+
listVisible(ownerKeys) {
|
|
810
|
+
return listVisibleImpl(ownerKeys);
|
|
811
|
+
},
|
|
812
|
+
/** 管理形(批 ev-1 M2 拆形——/memory 活体区消费;未生效行可见) */
|
|
813
|
+
listVisibleForManagement(ownerKeys) {
|
|
814
|
+
return listVisibleForManagementImpl(ownerKeys);
|
|
815
|
+
},
|
|
816
|
+
versions(memoryId) {
|
|
817
|
+
return stmtVersions.all(memoryId).map(mapVersionRow);
|
|
818
|
+
},
|
|
819
|
+
rebuildFts() {
|
|
820
|
+
stmtRebuildFts.run();
|
|
821
|
+
},
|
|
822
|
+
forget(id, opts) {
|
|
823
|
+
return forgetTx(id, opts?.promotedToSkill, opts?.supersededBy);
|
|
824
|
+
},
|
|
825
|
+
absorb(keepId, dropId, reason) {
|
|
826
|
+
return absorbTx(keepId, dropId, reason);
|
|
827
|
+
},
|
|
828
|
+
decay(id, factor, reason) {
|
|
829
|
+
return decayTx(id, factor, reason);
|
|
830
|
+
},
|
|
831
|
+
lineage(id) {
|
|
832
|
+
return lineageImpl(id);
|
|
833
|
+
},
|
|
834
|
+
sweepExpired() {
|
|
835
|
+
// 双清同拍单事务(TTL 物化 + 访问日志窗口清扫——纯状态变更不动 updated_at 不追加版本)
|
|
836
|
+
return sweepTx(deps.now());
|
|
837
|
+
},
|
|
838
|
+
restore(id, revision) {
|
|
839
|
+
return restoreTx(id, revision);
|
|
840
|
+
},
|
|
841
|
+
freeze(id) {
|
|
842
|
+
return freezeTx(id);
|
|
843
|
+
},
|
|
844
|
+
unfreeze(id) {
|
|
845
|
+
return unfreezeTx(id);
|
|
846
|
+
},
|
|
847
|
+
setTtl(id, days) {
|
|
848
|
+
return setTtlTx(id, days);
|
|
849
|
+
},
|
|
850
|
+
search(query, opts) {
|
|
851
|
+
return searchImpl(query, opts);
|
|
852
|
+
},
|
|
853
|
+
overview(ownerKeys) {
|
|
854
|
+
return overviewImpl(ownerKeys);
|
|
855
|
+
},
|
|
856
|
+
accessLog(query) {
|
|
857
|
+
return accessLogImpl(query);
|
|
858
|
+
},
|
|
859
|
+
resolveShortId(shortId) {
|
|
860
|
+
return stmtResolvePrefix.all(shortId, deps.now()).map((r) => r.id);
|
|
861
|
+
},
|
|
862
|
+
markUsed(ids, sessionId) {
|
|
863
|
+
return markUsedTx(ids, sessionId ?? null);
|
|
864
|
+
},
|
|
865
|
+
markCorrected(ids, sessionId) {
|
|
866
|
+
return markCorrectedTx(ids, sessionId ?? null);
|
|
867
|
+
},
|
|
868
|
+
listForExport(ownerKey) {
|
|
869
|
+
const rows = ownerKey === undefined
|
|
870
|
+
? stmtListForExportAll.all()
|
|
871
|
+
: stmtListForExportOwner.all(ownerKey);
|
|
872
|
+
return rows.map(mapRow);
|
|
873
|
+
},
|
|
874
|
+
importInsert(row) {
|
|
875
|
+
return importInsertTx(row);
|
|
876
|
+
},
|
|
877
|
+
};
|
|
878
|
+
}
|