@desplega.ai/agent-swarm 1.158.0 → 1.159.0
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/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-qhch0n2v.js} +1 -1
- package/dist/{actions-p9hz5yat.js → actions-dtsh1mfb.js} +33 -29
- package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-f2h06k64.js} +8 -3
- package/dist/{app-g26wdcz6.js → app-y1b9srva.js} +10 -9
- package/dist/{assistant-wydfqkb3.js → assistant-31hwy5xj.js} +34 -30
- package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-0k1mfax5.js} +12 -13
- package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-48d2qpkt.js} +11 -11
- package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-w0bryhkr.js} +12 -12
- package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-t65wbrn7.js} +9 -8
- package/dist/{boot-seeds-s3q01xj3.js → boot-seeds-h7c32hvm.js} +12 -11
- package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-1t6611ne.js} +26 -21
- package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-werp659j.js} +4 -2
- package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-y6rdgbc1.js} +11 -2
- package/dist/claude-sdk-session-zhkyk8yw.js +29319 -0
- package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
- package/dist/{cli-wscdvcqz.js → cli-0n2e4x49.js} +1 -1
- package/dist/cli-0xqde53b.js +176 -0
- package/dist/{cli-xberxa1r.js → cli-10ym38qc.js} +2 -2
- package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
- package/dist/{cli-rqz8qbw7.js → cli-20035trh.js} +50 -18
- package/dist/{cli-k0g1sjzm.js → cli-31nkw3g3.js} +3 -3
- package/dist/cli-3gvrr8vj.js +1422 -0
- package/dist/{cli-c07r9a2m.js → cli-48527fpw.js} +9 -8
- package/dist/{cli-24k806sc.js → cli-4aj25szq.js} +2 -2
- package/dist/{cli-7gtvvpbh.js → cli-4tvgz99m.js} +7 -7
- package/dist/cli-503ya656.js +455 -0
- package/dist/{cli-cw9zck8j.js → cli-5f2hq128.js} +8 -4
- package/dist/{cli-s8eet254.js → cli-68bmme5p.js} +11078 -27833
- package/dist/{cli-ftfvggzx.js → cli-79aq9cgb.js} +794 -563
- package/dist/cli-7cxw8b60.js +14910 -0
- package/dist/cli-7kyk9jr3.js +22 -0
- package/dist/cli-7zttzetj.js +324 -0
- package/dist/{cli-y95wp3tg.js → cli-96qd5psa.js} +1 -1
- package/dist/{cli-g8djp6qe.js → cli-9yg65c1j.js} +22 -0
- package/dist/{cli-3gmcwfyx.js → cli-ardpzhjm.js} +5 -5
- package/dist/{cli-7s3syk22.js → cli-b6thtzac.js} +5 -5
- package/dist/{cli-00w2c66p.js → cli-bgv70kym.js} +13 -2
- package/dist/{cli-j55n64b6.js → cli-bv0a38v9.js} +163 -44
- package/dist/cli-ca2w7zv7.js +115 -0
- package/dist/cli-cpvsaqsw.js +333 -0
- package/dist/cli-cygyqj3b.js +754 -0
- package/dist/{cli-x24pshtq.js → cli-dvjpkkjd.js} +6 -6
- package/dist/{cli-5d10vd2m.js → cli-e0ndxhtk.js} +4 -4
- package/dist/{cli-4198qc9v.js → cli-e4xnc0qp.js} +15 -3
- package/dist/{cli-rjehvrts.js → cli-eg7cdg68.js} +8 -8
- package/dist/cli-ejpygnqz.js +3815 -0
- package/dist/{cli-fgqxwrpv.js → cli-fjtp3463.js} +1631 -127
- package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
- package/dist/{cli-47m6dkd3.js → cli-g9df2m37.js} +409 -168
- package/dist/cli-gzyh0j0g.js +8455 -0
- package/dist/cli-hgqfttfc.js +183 -0
- package/dist/cli-hj65wsts.js +17755 -0
- package/dist/cli-j9z8mfx3.js +476 -0
- package/dist/{cli-aehm1rbq.js → cli-kc3dp84d.js} +1135 -61
- package/dist/{cli-h38x0qhg.js → cli-kdnehe0t.js} +4 -3
- package/dist/{cli-20cj37vt.js → cli-m4excttz.js} +136 -2216
- package/dist/{cli-8qmme6vd.js → cli-mg24hkt4.js} +2 -2
- package/dist/{cli-xhaeg2nc.js → cli-mzkqpxee.js} +1 -1
- package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
- package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
- package/dist/{cli-rj19hc34.js → cli-qjxy52w7.js} +1 -1
- package/dist/{cli-53sz8gkz.js → cli-qv4jv8bx.js} +1 -1
- package/dist/cli-qvnnx4ct.js +1051 -0
- package/dist/cli-rb9m6rpe.js +4980 -0
- package/dist/{cli-tt9k8r35.js → cli-rfezr056.js} +476 -909
- package/dist/{cli-ggm92wcn.js → cli-rgjprct3.js} +1 -0
- package/dist/{cli-9httdpyq.js → cli-rrqmqr1s.js} +1 -1
- package/dist/cli-rykfmgwg.js +102 -0
- package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
- package/dist/{cli-68w09sjw.js → cli-vn0qv9cj.js} +25 -88
- package/dist/{cli-987hg0aa.js → cli-vwbbaecp.js} +2 -2
- package/dist/cli-wd9hz40d.js +69 -0
- package/dist/{cli-j57qkrpq.js → cli-wgjx0q33.js} +13 -13
- package/dist/cli-xgpf00sk.js +2149 -0
- package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
- package/dist/{cli-aky8tctm.js → cli-z9x0sh1t.js} +3 -3
- package/dist/{cli-1hkrch3k.js → cli-zw65mab6.js} +5 -5
- package/dist/cli.js +38 -29
- package/dist/cloudflare-workers-ai-system-one-1sg586qv.js +46 -0
- package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-jqfqqa21.js} +21 -14
- package/dist/{codex-hook-9hptmdhj.js → codex-hook-5eys3dya.js} +2 -2
- package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-zd341dsm.js} +21 -14
- package/dist/{commands-e3bn0p75.js → commands-vfsysz4s.js} +9 -8
- package/dist/{db-03v9kzr2.js → db-s3znhvbn.js} +15 -10
- package/dist/{dispatcher-nrmg8yht.js → dispatcher-5xn0c7rf.js} +32 -28
- package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-894mgfvs.js} +3 -3
- package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
- package/dist/execute-71ysgv35.js +965 -0
- package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-1nfcdphp.js} +5 -4
- package/dist/{google-vertex-809af514.js → google-vertex-1tqcd7b1.js} +5 -4
- package/dist/{handlers-aqcedbyb.js → handlers-5w4jwycr.js} +35 -31
- package/dist/{hook-7vkskkre.js → hook-853bxrr6.js} +16 -13
- package/dist/{hook-ybvmrmhq.js → hook-z0hbxj85.js} +16 -13
- package/dist/{http-2eqa8t24.js → http-akc3wy3m.js} +600 -123
- package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
- package/dist/{index-a3vn96n8.js → index-5sz16ksh.js} +187 -47
- package/dist/{index-c6zsv1bd.js → index-bjmjh9h4.js} +11 -11
- package/dist/{index-chgdkq36.js → index-cd151qkq.js} +32 -28
- package/dist/{index-x71bvq0p.js → index-k661dzzr.js} +33 -29
- package/dist/{index-1yks3qcr.js → index-ttpbhnjg.js} +32 -28
- package/dist/{keepalive-vtkf5n99.js → keepalive-8p9rpvz5.js} +11 -10
- package/dist/{lead-h3sp357x.js → lead-jp69z5h3.js} +49 -45
- package/dist/llama-cpp-classify-t2tccams.js +335 -0
- package/dist/{maintenance-jzeqtk3q.js → maintenance-fwx4ev0k.js} +12 -12
- package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-bhvwqcnk.js} +14 -17
- package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-cwcq1ctx.js} +10 -9
- package/dist/{onboard-tmsp0qbz.js → onboard-2b4q0t84.js} +6 -5
- package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-nekthgks.js} +21 -8
- package/dist/{openai-completions-9hjrqbw8.js → openai-completions-hxtzwj0s.js} +8 -11
- package/dist/{openai-responses-7b1pn0xt.js → openai-responses-w4r118w7.js} +22 -17
- package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-zz7xvjj6.js} +6 -4
- package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-b6sqqjgn.js} +4 -6
- package/dist/{otel-impl-9mz73q65.js → otel-impl-g8rs4vwr.js} +1 -1
- package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
- package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-n0me28wt.js} +29 -17
- package/dist/pricing-refresh-0v77385v.js +39 -0
- package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-krjyazbj.js} +10 -9
- package/dist/{render-v2-gryrh9h8.js → render-v2-3wjsp9aw.js} +10 -9
- package/dist/runtime-ktjysacx.js +24 -0
- package/dist/{setup-q9720bt6.js → setup-yeqryke6.js} +6 -5
- package/dist/typesafe-system-one-96960jxp.js +27 -0
- package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-dzdeq1sf.js} +21 -13
- package/dist/{work-objects-b76x7deb.js → work-objects-3pakpkw0.js} +35 -31
- package/dist/{worker-6vrxbv41.js → worker-85testgj.js} +49 -45
- package/openapi.json +863 -14
- package/package.json +9 -8
- package/src/be/db/agents.ts +20 -0
- package/src/be/db/tasks/human-free.ts +442 -0
- package/src/be/db/tasks/read.ts +56 -1
- package/src/be/db/tasks/write.ts +107 -11
- package/src/be/db.ts +89 -165
- package/src/be/harness-model-support.ts +182 -0
- package/src/be/human-free-drain.ts +67 -0
- package/src/be/migrations/182_task_human_free_flag.sql +63 -0
- package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
- package/src/be/migrations/184_model_catalog.sql +120 -0
- package/src/be/model-catalog-store.ts +644 -0
- package/src/be/model-tier-keys.ts +31 -0
- package/src/be/model-tier-resolution.ts +489 -0
- package/src/be/model-validation.ts +112 -0
- package/src/be/models-catalog.ts +61 -7
- package/src/be/modelsdev-cache.json +82 -0
- package/src/be/modelsdev-cache.ts +2 -0
- package/src/be/pricing-refresh.ts +154 -12
- package/src/be/scripts/typecheck.ts +15 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
- package/src/be/swarm-config-guard.ts +6 -0
- package/src/cli.tsx +10 -0
- package/src/commands/claude-managed-setup.ts +3 -1
- package/src/commands/runner.ts +76 -2
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/heartbeat/heartbeat.ts +64 -15
- package/src/heartbeat/templates.ts +2 -2
- package/src/http/agents.ts +49 -1
- package/src/http/index.ts +8 -0
- package/src/http/models-catalog.ts +357 -8
- package/src/http/poll.ts +45 -2
- package/src/http/schedules.ts +36 -6
- package/src/http/session-data.ts +2 -5
- package/src/http/stats.ts +6 -8
- package/src/http/tasks.ts +26 -1
- package/src/http/usage-cache.ts +56 -15
- package/src/http/workflows.ts +35 -8
- package/src/prompts/session-templates.ts +1 -0
- package/src/providers/claude-managed-models.ts +47 -161
- package/src/providers/codex-adapter.ts +4 -4
- package/src/providers/codex-models.ts +65 -198
- package/src/providers/reasoning-effort.ts +44 -140
- package/src/rbac/legacy-policy.ts +25 -0
- package/src/rbac/permissions.ts +10 -0
- package/src/scripts-runtime/sdk-allowlist.ts +4 -0
- package/src/scripts-runtime/types/stdlib.d.ts +15 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
- package/src/server.ts +6 -0
- package/src/slack/message-text.ts +56 -2
- package/src/slack/thread-buffer.ts +10 -2
- package/src/tests/agents-list-model-display.test.ts +4 -6
- package/src/tests/base-prompt.test.ts +3 -0
- package/src/tests/claude-adapter.test.ts +10 -0
- package/src/tests/claude-managed-adapter.test.ts +12 -18
- package/src/tests/codex-adapter.test.ts +2 -1
- package/src/tests/codex-gpt6-release.test.ts +66 -5
- package/src/tests/context-window.test.ts +7 -5
- package/src/tests/db-extraction-contract.test.ts +1 -0
- package/src/tests/db-task-read-contract.test.ts +5 -1
- package/src/tests/db-task-write-contract.test.ts +4 -1
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/explicit-model-validation.test.ts +332 -0
- package/src/tests/harness-model-support.test.ts +374 -0
- package/src/tests/heartbeat-checklist-config.test.ts +71 -0
- package/src/tests/heartbeat-checklist.test.ts +4 -2
- package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
- package/src/tests/human-free-flag.test.ts +279 -0
- package/src/tests/human-free-oracle.ts +57 -0
- package/src/tests/human-free-reclassify.test.ts +669 -0
- package/src/tests/migration-046-budgets.test.ts +3 -3
- package/src/tests/model-catalog-migration.test.ts +134 -0
- package/src/tests/model-catalog-table.test.ts +249 -0
- package/src/tests/model-catalog-write-access.test.ts +387 -0
- package/src/tests/model-tier-resolution.test.ts +519 -0
- package/src/tests/models-catalog.test.ts +1 -1
- package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
- package/src/tests/package-publish.test.ts +137 -36
- package/src/tests/pricing-routes.test.ts +2 -2
- package/src/tests/rbac-engine.test.ts +42 -2
- package/src/tests/reasoning-effort.test.ts +109 -0
- package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
- package/src/tests/runtime-model-catalog.test.ts +86 -0
- package/src/tests/script-connections.test.ts +5 -0
- package/src/tests/seed-scripts.test.ts +89 -0
- package/src/tests/session-costs.test.ts +14 -11
- package/src/tests/slack-message-text.test.ts +101 -0
- package/src/tests/slack-thread-buffer-context.test.ts +93 -0
- package/src/tests/task-cascade-fail.test.ts +148 -2
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/usage-cache.test.ts +166 -0
- package/src/tests/workflow-executors.test.ts +5 -2
- package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
- package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
- package/src/tests/workflow-system-one-decision.test.ts +2063 -0
- package/src/tools/model-catalog/index.ts +137 -0
- package/src/tools/schedules/create-schedule.ts +16 -1
- package/src/tools/schedules/patch-schedule.ts +17 -0
- package/src/tools/schedules/update-schedule.ts +17 -0
- package/src/tools/send-task.ts +14 -0
- package/src/tools/task-action.ts +12 -0
- package/src/tools/tool-config.ts +3 -1
- package/src/tools/workflows/create-workflow.ts +9 -2
- package/src/tools/workflows/patch-workflow-node.ts +17 -7
- package/src/tools/workflows/patch-workflow.ts +9 -2
- package/src/tools/workflows/update-workflow.ts +9 -2
- package/src/types.ts +30 -1
- package/src/utils/context-window.ts +47 -53
- package/src/utils/harness-cli-version.ts +115 -0
- package/src/utils/harness-model-error.ts +33 -0
- package/src/utils/runtime-model-catalog.ts +137 -0
- package/src/workflows/approval-resolution.ts +39 -0
- package/src/workflows/definition.ts +7 -0
- package/src/workflows/engine.ts +69 -1
- package/src/workflows/executors/agent-task.ts +9 -0
- package/src/workflows/executors/base.ts +53 -0
- package/src/workflows/executors/human-in-the-loop.ts +8 -8
- package/src/workflows/executors/index.ts +1 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/executors/system-one-decision.ts +1232 -0
- package/src/workflows/executors/system-one-providers.ts +301 -0
- package/src/workflows/executors/workflow-llm.ts +13 -1
- package/src/workflows/readiness.ts +86 -0
- package/src/workflows/recovery.ts +12 -12
- package/src/workflows/resume.ts +23 -9
- package/templates/schedules/daily-blocker-digest/content.md +4 -5
- package/templates/skills/agent-browser/SKILL.md +6 -5
- package/templates/skills/agent-browser/content.md +6 -5
- package/templates/skills/heartbeat-runbook/config.json +1 -1
- package/templates/skills/heartbeat-runbook/content.md +2 -2
- package/templates/skills/swarm-scripts/SKILL.md +1 -1
- package/templates/skills/swarm-scripts/content.md +1 -1
- package/templates/skills/workflow-iterate/content.md +81 -1
- package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
- package/templates/workflows/pr-review-status-sweep/content.md +2 -0
- package/dist/claude-sdk-session-t442qz6f.js +0 -39108
- package/dist/cli-0xjkj1j7.js +0 -61
- package/dist/cli-65hjmev5.js +0 -1141
- package/dist/cli-aw1jwp1r.js +0 -14075
- package/dist/cli-h967nb6r.js +0 -6603
- package/dist/cli-vapyk8sw.js +0 -20
- package/dist/cli-ws6rqycm.js +0 -247
- package/dist/pricing-refresh-v1cbr9te.js +0 -27
- package/src/providers/modelsdev-reasoning.json +0 -6453
- package/dist/{codex-login-x3jk4etn.js → codex-login-he9vrn90.js} +3 -3
- package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
package/dist/cli-65hjmev5.js
DELETED
|
@@ -1,1141 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
contentSha256,
|
|
3
|
-
init_profile_sync
|
|
4
|
-
} from "./cli-xberxa1r.js";
|
|
5
|
-
import {
|
|
6
|
-
OpenAI,
|
|
7
|
-
init_openai
|
|
8
|
-
} from "./cli-aehm1rbq.js";
|
|
9
|
-
import {
|
|
10
|
-
getDb,
|
|
11
|
-
getDbClient,
|
|
12
|
-
init_db,
|
|
13
|
-
isSqliteVecAvailable
|
|
14
|
-
} from "./cli-47m6dkd3.js";
|
|
15
|
-
import {
|
|
16
|
-
__esm,
|
|
17
|
-
__export,
|
|
18
|
-
__toCommonJS
|
|
19
|
-
} from "./cli-bc05qbss.js";
|
|
20
|
-
|
|
21
|
-
// src/be/memory/constants.ts
|
|
22
|
-
function numEnv(key, fallback) {
|
|
23
|
-
const val = process.env[key];
|
|
24
|
-
if (val === undefined)
|
|
25
|
-
return fallback;
|
|
26
|
-
const parsed = Number(val);
|
|
27
|
-
return Number.isNaN(parsed) ? fallback : parsed;
|
|
28
|
-
}
|
|
29
|
-
function recencyDecayHalfLifeDays(source = "task_completion") {
|
|
30
|
-
const raw = process.env.MEMORY_RECENCY_HALF_LIFE_DAYS;
|
|
31
|
-
if (raw != null && raw !== "") {
|
|
32
|
-
const parsed = Number(raw);
|
|
33
|
-
if (Number.isFinite(parsed))
|
|
34
|
-
return parsed;
|
|
35
|
-
}
|
|
36
|
-
return DEFAULT_RECENCY_DECAY_HALF_LIFE[source];
|
|
37
|
-
}
|
|
38
|
-
function minSimilarity() {
|
|
39
|
-
return numEnv("MEMORY_MIN_SIMILARITY", 0.1);
|
|
40
|
-
}
|
|
41
|
-
function accessBoostMaxMultiplier() {
|
|
42
|
-
return numEnv("MEMORY_ACCESS_BOOST_MAX", 1.5);
|
|
43
|
-
}
|
|
44
|
-
function isHybridSearchEnabled() {
|
|
45
|
-
const val = process.env.MEMORY_HYBRID_SEARCH?.trim().toLowerCase();
|
|
46
|
-
return val !== "0" && val !== "false";
|
|
47
|
-
}
|
|
48
|
-
function isGraphExpansionEnabled() {
|
|
49
|
-
const val = process.env.MEMORY_GRAPH_EXPANSION?.trim().toLowerCase();
|
|
50
|
-
return val !== "0" && val !== "false";
|
|
51
|
-
}
|
|
52
|
-
var TTL_DEFAULTS, DEFAULT_RECENCY_DECAY_HALF_LIFE, SOURCE_QUALITY_MULTIPLIER, ACCESS_BOOST_RECENCY_WINDOW_HOURS, CANDIDATE_SET_MULTIPLIER, EMBEDDING_DIMENSIONS, DEFAULT_EMBEDDING_MODEL = "openai/text-embedding-3-small", PROTECTED_SOURCES;
|
|
53
|
-
var init_constants = __esm(() => {
|
|
54
|
-
TTL_DEFAULTS = {
|
|
55
|
-
task_completion: 7,
|
|
56
|
-
session_summary: 3,
|
|
57
|
-
file_index: 30,
|
|
58
|
-
manual: null
|
|
59
|
-
};
|
|
60
|
-
DEFAULT_RECENCY_DECAY_HALF_LIFE = {
|
|
61
|
-
manual: Number.POSITIVE_INFINITY,
|
|
62
|
-
file_index: 180,
|
|
63
|
-
task_completion: 14,
|
|
64
|
-
session_summary: 7
|
|
65
|
-
};
|
|
66
|
-
SOURCE_QUALITY_MULTIPLIER = {
|
|
67
|
-
manual: 1.5,
|
|
68
|
-
file_index: 1,
|
|
69
|
-
task_completion: 0.7,
|
|
70
|
-
session_summary: 0.5
|
|
71
|
-
};
|
|
72
|
-
ACCESS_BOOST_RECENCY_WINDOW_HOURS = numEnv("MEMORY_ACCESS_RECENCY_HOURS", 48);
|
|
73
|
-
CANDIDATE_SET_MULTIPLIER = numEnv("MEMORY_CANDIDATE_MULTIPLIER", 3);
|
|
74
|
-
EMBEDDING_DIMENSIONS = numEnv("EMBEDDING_DIMENSIONS", 512);
|
|
75
|
-
PROTECTED_SOURCES = new Set(["manual"]);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// src/be/memory/providers/openai-embedding.ts
|
|
79
|
-
var exports_openai_embedding = {};
|
|
80
|
-
__export(exports_openai_embedding, {
|
|
81
|
-
OpenAIEmbeddingProvider: () => OpenAIEmbeddingProvider
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
class OpenAIEmbeddingProvider {
|
|
85
|
-
name;
|
|
86
|
-
dimensions;
|
|
87
|
-
client = null;
|
|
88
|
-
model;
|
|
89
|
-
apiKey;
|
|
90
|
-
constructor(config) {
|
|
91
|
-
this.apiKey = config?.apiKey ?? process.env.EMBEDDING_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
92
|
-
this.model = config?.model ?? process.env.EMBEDDING_MODEL ?? "text-embedding-3-small";
|
|
93
|
-
this.dimensions = config?.dimensions ?? EMBEDDING_DIMENSIONS;
|
|
94
|
-
this.name = config?.model ? `openai/${config.model}` : process.env.EMBEDDING_MODEL ?? DEFAULT_EMBEDDING_MODEL;
|
|
95
|
-
}
|
|
96
|
-
isConfigured() {
|
|
97
|
-
return !!this.apiKey;
|
|
98
|
-
}
|
|
99
|
-
getClient() {
|
|
100
|
-
if (!this.apiKey)
|
|
101
|
-
return null;
|
|
102
|
-
if (!this.client)
|
|
103
|
-
this.client = new OpenAI({
|
|
104
|
-
baseURL: process.env.EMBEDDING_API_BASE_URL,
|
|
105
|
-
apiKey: this.apiKey
|
|
106
|
-
});
|
|
107
|
-
return this.client;
|
|
108
|
-
}
|
|
109
|
-
async embed(text) {
|
|
110
|
-
const client = this.getClient();
|
|
111
|
-
if (!client)
|
|
112
|
-
return null;
|
|
113
|
-
try {
|
|
114
|
-
const cleaned = text.replace(/[\n\r]/g, " ").trim();
|
|
115
|
-
if (!cleaned)
|
|
116
|
-
return null;
|
|
117
|
-
const response = await client.embeddings.create({
|
|
118
|
-
model: this.model,
|
|
119
|
-
input: cleaned,
|
|
120
|
-
dimensions: this.dimensions,
|
|
121
|
-
encoding_format: "float"
|
|
122
|
-
});
|
|
123
|
-
const values = response.data[0]?.embedding;
|
|
124
|
-
if (!values)
|
|
125
|
-
return null;
|
|
126
|
-
if (values.length !== this.dimensions) {
|
|
127
|
-
console.error(`[memory] Embedding dimension mismatch: expected=${this.dimensions} got=${values.length}. Provider may not support the 'dimensions' parameter.`);
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
return new Float32Array(values);
|
|
131
|
-
} catch (err) {
|
|
132
|
-
console.error("[memory] Embedding failed:", err.message);
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
async embedBatch(texts) {
|
|
137
|
-
const client = this.getClient();
|
|
138
|
-
if (!client)
|
|
139
|
-
return texts.map(() => null);
|
|
140
|
-
const cleaned = texts.map((t) => t.replace(/[\n\r]/g, " ").trim());
|
|
141
|
-
const nonEmptyIndices = [];
|
|
142
|
-
const nonEmptyTexts = [];
|
|
143
|
-
for (let i = 0;i < cleaned.length; i++) {
|
|
144
|
-
if (cleaned[i]) {
|
|
145
|
-
nonEmptyIndices.push(i);
|
|
146
|
-
nonEmptyTexts.push(cleaned[i]);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (nonEmptyTexts.length === 0)
|
|
150
|
-
return texts.map(() => null);
|
|
151
|
-
try {
|
|
152
|
-
const response = await client.embeddings.create({
|
|
153
|
-
model: this.model,
|
|
154
|
-
input: nonEmptyTexts,
|
|
155
|
-
dimensions: this.dimensions,
|
|
156
|
-
encoding_format: "float"
|
|
157
|
-
});
|
|
158
|
-
const results = texts.map(() => null);
|
|
159
|
-
for (const item of response.data) {
|
|
160
|
-
const originalIndex = nonEmptyIndices[item.index];
|
|
161
|
-
if (originalIndex !== undefined && item.embedding) {
|
|
162
|
-
if (item.embedding.length !== this.dimensions) {
|
|
163
|
-
console.error(`[memory] Batch embedding dimension mismatch: expected=${this.dimensions} got=${item.embedding.length}. Provider may not support the 'dimensions' parameter.`);
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
results[originalIndex] = new Float32Array(item.embedding);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return results;
|
|
170
|
-
} catch (err) {
|
|
171
|
-
console.error("[memory] Batch embedding failed:", err.message);
|
|
172
|
-
return texts.map(() => null);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
var init_openai_embedding = __esm(() => {
|
|
177
|
-
init_openai();
|
|
178
|
-
init_constants();
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// src/be/embedding.ts
|
|
182
|
-
function cosineSimilarity(a, b) {
|
|
183
|
-
if (a.length !== b.length)
|
|
184
|
-
throw new Error("Vectors must have the same length");
|
|
185
|
-
if (a.length === 0)
|
|
186
|
-
return 0;
|
|
187
|
-
let dotProduct = 0;
|
|
188
|
-
let normA = 0;
|
|
189
|
-
let normB = 0;
|
|
190
|
-
for (let i = 0;i < a.length; i++) {
|
|
191
|
-
const ai = a[i];
|
|
192
|
-
const bi = b[i];
|
|
193
|
-
dotProduct += ai * bi;
|
|
194
|
-
normA += ai * ai;
|
|
195
|
-
normB += bi * bi;
|
|
196
|
-
}
|
|
197
|
-
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
198
|
-
if (denominator === 0)
|
|
199
|
-
return 0;
|
|
200
|
-
return dotProduct / denominator;
|
|
201
|
-
}
|
|
202
|
-
function serializeEmbedding(embedding) {
|
|
203
|
-
return Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
204
|
-
}
|
|
205
|
-
function deserializeEmbedding(buffer) {
|
|
206
|
-
const copy = new ArrayBuffer(buffer.length);
|
|
207
|
-
const view = new Uint8Array(copy);
|
|
208
|
-
view.set(buffer);
|
|
209
|
-
return new Float32Array(copy);
|
|
210
|
-
}
|
|
211
|
-
var init_embedding = () => {};
|
|
212
|
-
|
|
213
|
-
// src/be/memory/reranker.ts
|
|
214
|
-
function recencyDecay(createdAt, now, source) {
|
|
215
|
-
const halfLife = recencyDecayHalfLifeDays(source);
|
|
216
|
-
if (!Number.isFinite(halfLife))
|
|
217
|
-
return 1;
|
|
218
|
-
const ageDays = (now.getTime() - new Date(createdAt).getTime()) / MS_PER_DAY;
|
|
219
|
-
if (ageDays <= 0)
|
|
220
|
-
return 1;
|
|
221
|
-
return 2 ** (-ageDays / halfLife);
|
|
222
|
-
}
|
|
223
|
-
function accessBoost(accessedAt, accessCount, now) {
|
|
224
|
-
if (accessCount <= 0)
|
|
225
|
-
return 1;
|
|
226
|
-
const hoursSinceAccess = (now.getTime() - new Date(accessedAt).getTime()) / MS_PER_HOUR;
|
|
227
|
-
const recencyFactor = hoursSinceAccess <= ACCESS_BOOST_RECENCY_WINDOW_HOURS ? 1 : 0.5;
|
|
228
|
-
const boost = 1 + Math.min(accessCount / 10, accessBoostMaxMultiplier() - 1) * recencyFactor;
|
|
229
|
-
return boost;
|
|
230
|
-
}
|
|
231
|
-
function sourceQuality(source) {
|
|
232
|
-
return SOURCE_QUALITY_MULTIPLIER[source] ?? 1;
|
|
233
|
-
}
|
|
234
|
-
function readDemotionFloor() {
|
|
235
|
-
const raw = process.env.MEMORY_DEMOTION_FLOOR;
|
|
236
|
-
const n = raw == null || raw === "" ? 1 : Number(raw);
|
|
237
|
-
return Number.isFinite(n) ? n : 1;
|
|
238
|
-
}
|
|
239
|
-
function usefulness(alpha, beta) {
|
|
240
|
-
const denom = alpha + beta;
|
|
241
|
-
if (denom <= 0)
|
|
242
|
-
return 1;
|
|
243
|
-
const mean = alpha / denom;
|
|
244
|
-
return Math.max(readDemotionFloor(), Math.min(2, 2 * mean));
|
|
245
|
-
}
|
|
246
|
-
function computeScore(candidate, now) {
|
|
247
|
-
const decay = candidate.recencyDecayApplied ? 1 : recencyDecay(candidate.createdAt, now, candidate.source);
|
|
248
|
-
return candidate.similarity * decay * accessBoost(candidate.accessedAt, candidate.accessCount, now) * sourceQuality(candidate.source) * usefulness(candidate.alpha, candidate.beta);
|
|
249
|
-
}
|
|
250
|
-
function rerank(candidates, options) {
|
|
251
|
-
const { limit, now = new Date } = options;
|
|
252
|
-
const uncapped = new Map;
|
|
253
|
-
for (const candidate of candidates)
|
|
254
|
-
uncapped.set(candidate.id, computeScore(candidate, now));
|
|
255
|
-
const byId = new Map(candidates.map((c) => [c.id, c]));
|
|
256
|
-
const capped = new Map;
|
|
257
|
-
const cappedScore = (candidate, seen) => {
|
|
258
|
-
const cached = capped.get(candidate.id);
|
|
259
|
-
if (cached !== undefined)
|
|
260
|
-
return cached;
|
|
261
|
-
let score = uncapped.get(candidate.id);
|
|
262
|
-
const parent = candidate.graphParentId ? byId.get(candidate.graphParentId) : undefined;
|
|
263
|
-
if (parent && !seen.has(parent.id)) {
|
|
264
|
-
seen.add(candidate.id);
|
|
265
|
-
score = Math.min(score, cappedScore(parent, seen));
|
|
266
|
-
}
|
|
267
|
-
capped.set(candidate.id, score);
|
|
268
|
-
return score;
|
|
269
|
-
};
|
|
270
|
-
const scored = candidates.map((candidate) => {
|
|
271
|
-
const rawSimilarity = candidate.rawSimilarity ?? candidate.similarity;
|
|
272
|
-
const compositeScore = cappedScore(candidate, new Set);
|
|
273
|
-
return {
|
|
274
|
-
...candidate,
|
|
275
|
-
rawSimilarity,
|
|
276
|
-
compositeScore,
|
|
277
|
-
similarity: compositeScore
|
|
278
|
-
};
|
|
279
|
-
});
|
|
280
|
-
scored.sort((a, b) => b.similarity - a.similarity || b.rawSimilarity - a.rawSimilarity);
|
|
281
|
-
return scored.slice(0, limit);
|
|
282
|
-
}
|
|
283
|
-
var MS_PER_DAY, MS_PER_HOUR;
|
|
284
|
-
var init_reranker = __esm(() => {
|
|
285
|
-
init_constants();
|
|
286
|
-
MS_PER_DAY = 1000 * 60 * 60 * 24;
|
|
287
|
-
MS_PER_HOUR = 1000 * 60 * 60;
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// src/be/memory/providers/sqlite-store.ts
|
|
291
|
-
var exports_sqlite_store = {};
|
|
292
|
-
__export(exports_sqlite_store, {
|
|
293
|
-
SqliteMemoryStore: () => SqliteMemoryStore,
|
|
294
|
-
applyEditMode: () => applyEditMode,
|
|
295
|
-
computeRrfScore: () => computeRrfScore,
|
|
296
|
-
fusedSimilarity: () => fusedSimilarity,
|
|
297
|
-
rowToCandidate: () => rowToCandidate
|
|
298
|
-
});
|
|
299
|
-
function rowToAgentMemory(row) {
|
|
300
|
-
return {
|
|
301
|
-
id: row.id,
|
|
302
|
-
agentId: row.agentId,
|
|
303
|
-
scope: row.scope,
|
|
304
|
-
key: row.key ?? null,
|
|
305
|
-
name: row.name,
|
|
306
|
-
content: row.content,
|
|
307
|
-
summary: row.summary,
|
|
308
|
-
source: row.source,
|
|
309
|
-
sourceTaskId: row.sourceTaskId,
|
|
310
|
-
sourcePath: row.sourcePath,
|
|
311
|
-
chunkIndex: row.chunkIndex,
|
|
312
|
-
totalChunks: row.totalChunks,
|
|
313
|
-
tags: JSON.parse(row.tags || "[]"),
|
|
314
|
-
createdAt: row.createdAt,
|
|
315
|
-
updatedAt: row.updatedAt ?? null,
|
|
316
|
-
accessedAt: row.accessedAt,
|
|
317
|
-
expiresAt: row.expiresAt ?? null,
|
|
318
|
-
accessCount: row.accessCount ?? 0,
|
|
319
|
-
embeddingModel: row.embeddingModel ?? null,
|
|
320
|
-
contentHash: row.contentHash ?? null,
|
|
321
|
-
version: row.version ?? 1
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
function rowToCandidate(row, similarity) {
|
|
325
|
-
return {
|
|
326
|
-
...rowToAgentMemory(row),
|
|
327
|
-
similarity,
|
|
328
|
-
accessCount: row.accessCount ?? 0,
|
|
329
|
-
expiresAt: row.expiresAt ?? null,
|
|
330
|
-
embeddingModel: row.embeddingModel ?? null,
|
|
331
|
-
alpha: row.alpha ?? 1,
|
|
332
|
-
beta: row.beta ?? 1
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
function retrievalSourceFor(sources) {
|
|
336
|
-
if (sources.has("fts") && sources.has("vec"))
|
|
337
|
-
return "hybrid";
|
|
338
|
-
if (sources.has("fts"))
|
|
339
|
-
return "fts";
|
|
340
|
-
if (sources.has("vec"))
|
|
341
|
-
return "vec";
|
|
342
|
-
return "fallback";
|
|
343
|
-
}
|
|
344
|
-
function computeRrfScore(rank, decayFactor, k = 60) {
|
|
345
|
-
return 1 / (k + rank + 1) * decayFactor;
|
|
346
|
-
}
|
|
347
|
-
function fusedSimilarity(cosine, rrfScore, k = 60) {
|
|
348
|
-
const agreement = Math.min(1, rrfScore * (k + 1) / 2);
|
|
349
|
-
return cosine * (0.5 + 0.5 * agreement);
|
|
350
|
-
}
|
|
351
|
-
function applyEditMode(mode, currentContent, fields) {
|
|
352
|
-
if (mode === "replace") {
|
|
353
|
-
if (fields.content == null)
|
|
354
|
-
throw new Error("replace mode requires content");
|
|
355
|
-
return fields.content;
|
|
356
|
-
}
|
|
357
|
-
if (!fields.oldString || fields.newString == null) {
|
|
358
|
-
throw new Error("exact mode requires oldString and newString");
|
|
359
|
-
}
|
|
360
|
-
const first = currentContent.indexOf(fields.oldString);
|
|
361
|
-
if (first === -1)
|
|
362
|
-
throw new Error("oldString not found");
|
|
363
|
-
if (currentContent.indexOf(fields.oldString, first + fields.oldString.length) !== -1) {
|
|
364
|
-
throw new Error("oldString is ambiguous");
|
|
365
|
-
}
|
|
366
|
-
return currentContent.slice(0, first) + fields.newString + currentContent.slice(first + fields.oldString.length);
|
|
367
|
-
}
|
|
368
|
-
function computeExpiresAt(source) {
|
|
369
|
-
const ttlDays = TTL_DEFAULTS[source];
|
|
370
|
-
if (ttlDays == null)
|
|
371
|
-
return null;
|
|
372
|
-
return new Date(Date.now() + ttlDays * 86400000).toISOString();
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
class SqliteMemoryStore {
|
|
376
|
-
vecInitialized = false;
|
|
377
|
-
ftsInitialized = false;
|
|
378
|
-
lastPopulate = null;
|
|
379
|
-
constructor() {
|
|
380
|
-
this.ensureVecTable();
|
|
381
|
-
this.ensureFtsTable();
|
|
382
|
-
}
|
|
383
|
-
ensureFtsTable() {
|
|
384
|
-
if (this.ftsInitialized)
|
|
385
|
-
return;
|
|
386
|
-
const db = getDb();
|
|
387
|
-
try {
|
|
388
|
-
db.run(`
|
|
389
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
|
|
390
|
-
memory_id UNINDEXED,
|
|
391
|
-
name,
|
|
392
|
-
content,
|
|
393
|
-
tokenize='porter unicode61'
|
|
394
|
-
)
|
|
395
|
-
`);
|
|
396
|
-
this.populateFtsTable();
|
|
397
|
-
this.ftsInitialized = true;
|
|
398
|
-
} catch (err) {
|
|
399
|
-
this.ftsInitialized = false;
|
|
400
|
-
console.error("[memory-fts] Failed to initialize memory_fts:", err.message);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
async getFtsTableSchema() {
|
|
404
|
-
try {
|
|
405
|
-
const row = await getDbClient().get("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'memory_fts'");
|
|
406
|
-
return row?.sql ?? null;
|
|
407
|
-
} catch {
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
populateFtsTable() {
|
|
412
|
-
const db = getDb();
|
|
413
|
-
const deletedExtra = db.prepare(`DELETE FROM memory_fts
|
|
414
|
-
WHERE memory_id NOT IN (SELECT id FROM agent_memory)`).run();
|
|
415
|
-
if (deletedExtra.changes > 0) {
|
|
416
|
-
console.warn(`[memory-fts] removed_extra_rows count=${deletedExtra.changes}`);
|
|
417
|
-
}
|
|
418
|
-
const inserted = db.prepare(`INSERT INTO memory_fts(memory_id, name, content)
|
|
419
|
-
SELECT m.id, m.name, m.content
|
|
420
|
-
FROM agent_memory m
|
|
421
|
-
WHERE NOT EXISTS (SELECT 1 FROM memory_fts f WHERE f.memory_id = m.id)`).run();
|
|
422
|
-
console.log(`[memory-fts] populate inserted=${inserted.changes}`);
|
|
423
|
-
}
|
|
424
|
-
async syncFtsRow(memoryId, name, content) {
|
|
425
|
-
if (!this.ftsInitialized && !await this.getFtsTableSchema())
|
|
426
|
-
return;
|
|
427
|
-
try {
|
|
428
|
-
await getDbClient().run("DELETE FROM memory_fts WHERE memory_id = ?", [memoryId]);
|
|
429
|
-
await getDbClient().run("INSERT INTO memory_fts(memory_id, name, content) VALUES (?, ?, ?)", [
|
|
430
|
-
memoryId,
|
|
431
|
-
name,
|
|
432
|
-
content
|
|
433
|
-
]);
|
|
434
|
-
} catch (err) {
|
|
435
|
-
console.error(`[memory-fts] sync failed memory_id=${memoryId}: ${err.message}`);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
async deleteFtsRows(ids) {
|
|
439
|
-
if (ids.length === 0 || !await this.getFtsTableSchema())
|
|
440
|
-
return;
|
|
441
|
-
const placeholders = ids.map(() => "?").join(",");
|
|
442
|
-
await getDbClient().run(`DELETE FROM memory_fts WHERE memory_id IN (${placeholders})`, ids);
|
|
443
|
-
}
|
|
444
|
-
ensureVecTable() {
|
|
445
|
-
if (this.vecInitialized)
|
|
446
|
-
return;
|
|
447
|
-
if (!isSqliteVecAvailable()) {
|
|
448
|
-
console.warn("[memory-vec] sqlite-vec extension_loaded=false; retrieval_mode=fallback");
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
const db = getDb();
|
|
452
|
-
try {
|
|
453
|
-
console.log(`[memory-vec] sqlite-vec extension_loaded=true vector_dimensions=${EMBEDDING_DIMENSIONS}`);
|
|
454
|
-
const existingSchema = this.getVecTableSchema();
|
|
455
|
-
if (existingSchema && !existingSchema.includes("distance_metric=cosine")) {
|
|
456
|
-
console.warn("[memory-vec] Existing memory_vec table is missing cosine distance metric; rebuilding from agent_memory");
|
|
457
|
-
db.run("DROP TABLE memory_vec");
|
|
458
|
-
}
|
|
459
|
-
db.run(`
|
|
460
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memory_vec USING vec0(
|
|
461
|
-
memory_id TEXT PRIMARY KEY,
|
|
462
|
-
embedding float[${EMBEDDING_DIMENSIONS}] distance_metric=cosine
|
|
463
|
-
)
|
|
464
|
-
`);
|
|
465
|
-
const healthBefore = this.getHealthCounts();
|
|
466
|
-
if (healthBefore.missingFromVec > 0 || healthBefore.extraInVec > 0) {
|
|
467
|
-
this.populateVecTable(healthBefore.memoryVec);
|
|
468
|
-
} else {
|
|
469
|
-
console.log(`[memory-vec] populate skipped attempted=0 inserted=0 memory_vec=${healthBefore.memoryVec} valid_embedding=${healthBefore.validEmbedding}`);
|
|
470
|
-
}
|
|
471
|
-
this.vecInitialized = true;
|
|
472
|
-
} catch (err) {
|
|
473
|
-
this.vecInitialized = false;
|
|
474
|
-
console.error("[memory-vec] Failed to initialize memory_vec:", err.message);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
getVecTableSchema() {
|
|
478
|
-
try {
|
|
479
|
-
return getDb().prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'memory_vec'").get()?.sql ?? null;
|
|
480
|
-
} catch {
|
|
481
|
-
return null;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
getVecCount() {
|
|
485
|
-
if (!this.getVecTableSchema())
|
|
486
|
-
return 0;
|
|
487
|
-
return getDb().prepare("SELECT COUNT(*) as count FROM memory_vec").get()?.count ?? 0;
|
|
488
|
-
}
|
|
489
|
-
populateVecTable(beforeCount) {
|
|
490
|
-
const db = getDb();
|
|
491
|
-
const deletedExtra = db.prepare(`DELETE FROM memory_vec
|
|
492
|
-
WHERE memory_id NOT IN (SELECT id FROM agent_memory)`).run();
|
|
493
|
-
if (deletedExtra.changes > 0) {
|
|
494
|
-
console.warn(`[memory-vec] removed_extra_rows count=${deletedExtra.changes}`);
|
|
495
|
-
}
|
|
496
|
-
const rows = db.prepare("SELECT id, embedding FROM agent_memory WHERE embedding IS NOT NULL").all();
|
|
497
|
-
const deleteVec = db.prepare("DELETE FROM memory_vec WHERE memory_id = ?");
|
|
498
|
-
const insertVec = db.prepare("INSERT INTO memory_vec(memory_id, embedding) VALUES (?, ?)");
|
|
499
|
-
let attempted = 0;
|
|
500
|
-
let inserted = 0;
|
|
501
|
-
let skippedInvalidDimensions = 0;
|
|
502
|
-
let failed = 0;
|
|
503
|
-
for (const row of rows) {
|
|
504
|
-
const embeddingBuffer = this.toVecBuffer(row.embedding);
|
|
505
|
-
if (!embeddingBuffer) {
|
|
506
|
-
skippedInvalidDimensions++;
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
attempted++;
|
|
510
|
-
try {
|
|
511
|
-
deleteVec.run(row.id);
|
|
512
|
-
insertVec.run(row.id, embeddingBuffer);
|
|
513
|
-
inserted++;
|
|
514
|
-
} catch (err) {
|
|
515
|
-
failed++;
|
|
516
|
-
console.error(`[memory-vec] populate failed memory_id=${row.id}: ${err.message}`);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
const afterCount = this.getVecCount();
|
|
520
|
-
this.lastPopulate = {
|
|
521
|
-
attempted,
|
|
522
|
-
inserted,
|
|
523
|
-
skippedInvalidDimensions,
|
|
524
|
-
failed,
|
|
525
|
-
beforeCount,
|
|
526
|
-
afterCount
|
|
527
|
-
};
|
|
528
|
-
console.log(`[memory-vec] populate attempted=${attempted} inserted=${inserted} skipped_invalid_dimensions=${skippedInvalidDimensions} failed=${failed} before_count=${beforeCount} after_count=${afterCount}`);
|
|
529
|
-
if (failed > 0 || afterCount < attempted) {
|
|
530
|
-
console.error(`[memory-vec] populate incomplete attempted=${attempted} after_count=${afterCount} failed=${failed}`);
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
toVecBuffer(embedding) {
|
|
534
|
-
if (embedding instanceof Float32Array) {
|
|
535
|
-
if (embedding.length !== EMBEDDING_DIMENSIONS)
|
|
536
|
-
return null;
|
|
537
|
-
return serializeEmbedding(embedding);
|
|
538
|
-
}
|
|
539
|
-
if (embedding.length !== VECTOR_BYTES)
|
|
540
|
-
return null;
|
|
541
|
-
return embedding;
|
|
542
|
-
}
|
|
543
|
-
async store(input) {
|
|
544
|
-
const id = crypto.randomUUID();
|
|
545
|
-
const now = new Date().toISOString();
|
|
546
|
-
const expiresAt = computeExpiresAt(input.source);
|
|
547
|
-
const key = input.key ?? `${input.scope}/${input.source}/${id}`;
|
|
548
|
-
const contentHash = contentSha256(input.content);
|
|
549
|
-
const version = 1;
|
|
550
|
-
const row = await getDbClient().transaction(async (tx) => {
|
|
551
|
-
const inserted = await tx.get(`INSERT INTO agent_memory (id, agentId, scope, key, name, content, summary, source, sourceTaskId, sourcePath, chunkIndex, totalChunks, tags, createdAt, updatedAt, accessedAt, expiresAt, accessCount, embeddingModel, contextKey, contentHash, version)
|
|
552
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`, [
|
|
553
|
-
id,
|
|
554
|
-
input.agentId ?? null,
|
|
555
|
-
input.scope,
|
|
556
|
-
key,
|
|
557
|
-
input.name,
|
|
558
|
-
input.content,
|
|
559
|
-
input.summary ?? null,
|
|
560
|
-
input.source,
|
|
561
|
-
input.sourceTaskId ?? null,
|
|
562
|
-
input.sourcePath ?? null,
|
|
563
|
-
input.chunkIndex ?? 0,
|
|
564
|
-
input.totalChunks ?? 1,
|
|
565
|
-
JSON.stringify(input.tags ?? []),
|
|
566
|
-
now,
|
|
567
|
-
now,
|
|
568
|
-
now,
|
|
569
|
-
expiresAt,
|
|
570
|
-
0,
|
|
571
|
-
null,
|
|
572
|
-
input.contextKey ?? null,
|
|
573
|
-
contentHash,
|
|
574
|
-
version
|
|
575
|
-
]);
|
|
576
|
-
if (!inserted)
|
|
577
|
-
throw new Error("Failed to create memory");
|
|
578
|
-
await tx.run(`INSERT INTO agent_memory_version (id, memory_id, version, content, contentHash, intent, operation, changedByAgentId, createdAt, updatedAt, created_by, updated_by)
|
|
579
|
-
VALUES (?, ?, ?, ?, ?, ?, 'create', ?, ?, ?, ?, ?)`, [
|
|
580
|
-
crypto.randomUUID(),
|
|
581
|
-
inserted.id,
|
|
582
|
-
version,
|
|
583
|
-
input.content,
|
|
584
|
-
contentHash,
|
|
585
|
-
input.intent ?? "create memory",
|
|
586
|
-
input.agentId ?? null,
|
|
587
|
-
now,
|
|
588
|
-
now,
|
|
589
|
-
input.agentId ?? null,
|
|
590
|
-
input.agentId ?? null
|
|
591
|
-
]);
|
|
592
|
-
return inserted;
|
|
593
|
-
});
|
|
594
|
-
await this.syncFtsRow(row.id, row.name, row.content);
|
|
595
|
-
return rowToAgentMemory(row);
|
|
596
|
-
}
|
|
597
|
-
async storeBatch(inputs) {
|
|
598
|
-
const results = [];
|
|
599
|
-
await getDbClient().transaction(async () => {
|
|
600
|
-
for (const input of inputs) {
|
|
601
|
-
results.push(await this.store(input));
|
|
602
|
-
}
|
|
603
|
-
});
|
|
604
|
-
return results;
|
|
605
|
-
}
|
|
606
|
-
async get(id) {
|
|
607
|
-
const db = getDbClient();
|
|
608
|
-
const row = await db.get("SELECT * FROM agent_memory WHERE id = ?", [id]);
|
|
609
|
-
if (!row)
|
|
610
|
-
return null;
|
|
611
|
-
await db.run("UPDATE agent_memory SET accessedAt = ?, accessCount = accessCount + 1 WHERE id = ?", [new Date().toISOString(), id]);
|
|
612
|
-
return rowToAgentMemory(row);
|
|
613
|
-
}
|
|
614
|
-
async peek(id) {
|
|
615
|
-
const row = await getDbClient().get("SELECT * FROM agent_memory WHERE id = ?", [
|
|
616
|
-
id
|
|
617
|
-
]);
|
|
618
|
-
if (!row)
|
|
619
|
-
return null;
|
|
620
|
-
return rowToAgentMemory(row);
|
|
621
|
-
}
|
|
622
|
-
async search(embedding, agentId, options = {}) {
|
|
623
|
-
const { scope = "all", limit = 10, source, isLead = false, includeExpired = false } = options;
|
|
624
|
-
const health = this.getHealth();
|
|
625
|
-
if (isHybridSearchEnabled() && options.queryText && this.ftsInitialized && await this.getFtsTableSchema() && health.retrievalMode === "vec" && embedding.length === EMBEDDING_DIMENSIONS) {
|
|
626
|
-
console.log(`[memory-search] retrieval_path=hybrid scope=${scope} limit=${limit} vec_rows=${health.counts.memoryVec} searchable=${health.counts.searchable}`);
|
|
627
|
-
return this.searchHybrid(embedding, options.queryText, agentId, {
|
|
628
|
-
scope,
|
|
629
|
-
limit,
|
|
630
|
-
source,
|
|
631
|
-
isLead,
|
|
632
|
-
includeExpired
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
if (health.retrievalMode === "vec" && embedding.length === EMBEDDING_DIMENSIONS) {
|
|
636
|
-
console.log(`[memory-search] retrieval_path=vec scope=${scope} limit=${limit} vec_rows=${health.counts.memoryVec} searchable=${health.counts.searchable}`);
|
|
637
|
-
return this.searchWithVec(embedding, agentId, {
|
|
638
|
-
scope,
|
|
639
|
-
limit,
|
|
640
|
-
source,
|
|
641
|
-
isLead,
|
|
642
|
-
includeExpired
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
if (options.queryText && this.ftsInitialized && await this.getFtsTableSchema()) {
|
|
646
|
-
console.log(`[memory-search] retrieval_path=fts scope=${scope} limit=${limit} reason=${embedding.length !== EMBEDDING_DIMENSIONS ? "query_dimension_mismatch" : health.reasons.join("|") || "vec_unavailable"}`);
|
|
647
|
-
return this.searchFts(options.queryText, agentId, {
|
|
648
|
-
scope,
|
|
649
|
-
limit,
|
|
650
|
-
source,
|
|
651
|
-
isLead,
|
|
652
|
-
includeExpired
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
console.log(`[memory-search] retrieval_path=fallback scope=${scope} limit=${limit} reason=${embedding.length !== EMBEDDING_DIMENSIONS ? "query_dimension_mismatch" : health.reasons.join("|") || "vec_unavailable"}`);
|
|
656
|
-
return this.searchBruteForce(embedding, agentId, {
|
|
657
|
-
scope,
|
|
658
|
-
limit,
|
|
659
|
-
source,
|
|
660
|
-
isLead,
|
|
661
|
-
includeExpired
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
async searchHybrid(queryEmbedding, queryText, agentId, options) {
|
|
665
|
-
const overfetchLimit = Math.min(Math.max(options.limit * 4, options.limit), 100);
|
|
666
|
-
const vectorCandidates = await this.searchWithVec(queryEmbedding, agentId, {
|
|
667
|
-
...options,
|
|
668
|
-
limit: overfetchLimit
|
|
669
|
-
});
|
|
670
|
-
const ftsCandidates = await this.searchFts(queryText, agentId, {
|
|
671
|
-
...options,
|
|
672
|
-
limit: overfetchLimit
|
|
673
|
-
});
|
|
674
|
-
const byId = new Map;
|
|
675
|
-
const rrf = new Map;
|
|
676
|
-
const cosines = new Map;
|
|
677
|
-
const sources = new Map;
|
|
678
|
-
const add = (candidate, rank) => {
|
|
679
|
-
byId.set(candidate.id, byId.get(candidate.id) ?? candidate);
|
|
680
|
-
const retrievalSource = candidate.retrievalSource === "fts" ? "fts" : "vec";
|
|
681
|
-
const candidateSources = sources.get(candidate.id) ?? new Set;
|
|
682
|
-
candidateSources.add(retrievalSource);
|
|
683
|
-
sources.set(candidate.id, candidateSources);
|
|
684
|
-
if (retrievalSource === "vec")
|
|
685
|
-
cosines.set(candidate.id, candidate.similarity);
|
|
686
|
-
rrf.set(candidate.id, (rrf.get(candidate.id) ?? 0) + computeRrfScore(rank, 1));
|
|
687
|
-
};
|
|
688
|
-
vectorCandidates.forEach(add);
|
|
689
|
-
ftsCandidates.forEach(add);
|
|
690
|
-
await this.fillCosines(queryEmbedding, [...byId.keys()], cosines);
|
|
691
|
-
return [...byId.values()].map((candidate) => {
|
|
692
|
-
const similarity = fusedSimilarity(cosines.get(candidate.id) ?? 0, rrf.get(candidate.id) ?? 0);
|
|
693
|
-
return {
|
|
694
|
-
...candidate,
|
|
695
|
-
rawSimilarity: similarity,
|
|
696
|
-
similarity,
|
|
697
|
-
retrievalSource: retrievalSourceFor(sources.get(candidate.id) ?? new Set),
|
|
698
|
-
recencyDecayApplied: false
|
|
699
|
-
};
|
|
700
|
-
}).sort((a, b) => b.similarity - a.similarity).slice(0, options.limit);
|
|
701
|
-
}
|
|
702
|
-
async fillCosines(queryEmbedding, ids, cosines) {
|
|
703
|
-
const missing = ids.filter((id) => !cosines.has(id));
|
|
704
|
-
if (missing.length === 0)
|
|
705
|
-
return;
|
|
706
|
-
const rows = await getDbClient().query(`SELECT id, embedding FROM agent_memory WHERE id IN (${missing.map(() => "?").join(",")})`, missing);
|
|
707
|
-
for (const row of rows) {
|
|
708
|
-
if (!row.embedding)
|
|
709
|
-
continue;
|
|
710
|
-
const embedding = deserializeEmbedding(row.embedding);
|
|
711
|
-
if (embedding.length !== queryEmbedding.length)
|
|
712
|
-
continue;
|
|
713
|
-
cosines.set(row.id, Math.max(0, cosineSimilarity(queryEmbedding, embedding)));
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
async searchFts(queryText, agentId, options) {
|
|
717
|
-
const match = this.buildFtsMatch(queryText);
|
|
718
|
-
if (!match)
|
|
719
|
-
return [];
|
|
720
|
-
const { scope, limit, source, isLead, includeExpired } = options;
|
|
721
|
-
const conditions = ["memory_fts MATCH ?"];
|
|
722
|
-
const params = [match];
|
|
723
|
-
this.addScopeConditions(conditions, params, agentId, scope, isLead, "m");
|
|
724
|
-
if (source) {
|
|
725
|
-
conditions.push("m.source = ?");
|
|
726
|
-
params.push(source);
|
|
727
|
-
}
|
|
728
|
-
if (!includeExpired) {
|
|
729
|
-
conditions.push("(m.expiresAt IS NULL OR m.expiresAt > datetime('now'))");
|
|
730
|
-
}
|
|
731
|
-
try {
|
|
732
|
-
const sqlLimit = Math.min(Math.max(limit * 4, limit), 100);
|
|
733
|
-
const rows = await getDbClient().query(`SELECT m.*, bm25(memory_fts) AS rank
|
|
734
|
-
FROM memory_fts
|
|
735
|
-
JOIN agent_memory m ON m.id = memory_fts.memory_id
|
|
736
|
-
WHERE ${conditions.join(" AND ")}
|
|
737
|
-
ORDER BY rank
|
|
738
|
-
LIMIT ?`, [...params, sqlLimit]);
|
|
739
|
-
const now = new Date;
|
|
740
|
-
return rows.map((row, index) => {
|
|
741
|
-
const rawSimilarity = 1 / (index + 1);
|
|
742
|
-
return {
|
|
743
|
-
...rowToCandidate(row, rawSimilarity * recencyDecay(row.createdAt, now, row.source)),
|
|
744
|
-
rawSimilarity,
|
|
745
|
-
retrievalSource: "fts",
|
|
746
|
-
recencyDecayApplied: true
|
|
747
|
-
};
|
|
748
|
-
}).sort((a, b) => b.similarity - a.similarity).slice(0, limit);
|
|
749
|
-
} catch (err) {
|
|
750
|
-
console.warn("[memory-fts] query failed:", err.message);
|
|
751
|
-
return [];
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
buildFtsMatch(queryText) {
|
|
755
|
-
const terms = queryText.trim().split(/[^\p{L}\p{N}_-]+/u).map((term) => term.trim()).filter((term) => term.length > 0).slice(0, 12);
|
|
756
|
-
if (terms.length === 0)
|
|
757
|
-
return null;
|
|
758
|
-
return terms.map((term) => `"${term.replaceAll('"', '""')}"`).join(" OR ");
|
|
759
|
-
}
|
|
760
|
-
async searchWithVec(queryEmbedding, agentId, options) {
|
|
761
|
-
const { scope, limit, source, isLead, includeExpired } = options;
|
|
762
|
-
const embeddingBuffer = serializeEmbedding(queryEmbedding);
|
|
763
|
-
const knnLimit = Math.min(Math.max(limit, this.getVecCount()), 4096);
|
|
764
|
-
const conditions = ["v.embedding MATCH ?"];
|
|
765
|
-
const params = [embeddingBuffer];
|
|
766
|
-
this.addScopeConditions(conditions, params, agentId, scope, isLead, "m");
|
|
767
|
-
if (source) {
|
|
768
|
-
conditions.push("m.source = ?");
|
|
769
|
-
params.push(source);
|
|
770
|
-
}
|
|
771
|
-
if (!includeExpired) {
|
|
772
|
-
conditions.push("(m.expiresAt IS NULL OR m.expiresAt > datetime('now'))");
|
|
773
|
-
}
|
|
774
|
-
conditions.push("v.k = ?");
|
|
775
|
-
params.push(knnLimit);
|
|
776
|
-
const rows = await getDbClient().query(`SELECT m.*, v.distance
|
|
777
|
-
FROM memory_vec v
|
|
778
|
-
JOIN agent_memory m ON m.id = v.memory_id
|
|
779
|
-
WHERE ${conditions.join(" AND ")}
|
|
780
|
-
ORDER BY v.distance
|
|
781
|
-
LIMIT ?`, [...params, limit]);
|
|
782
|
-
const candidates = [];
|
|
783
|
-
for (const row of rows) {
|
|
784
|
-
const similarity = 1 - row.distance;
|
|
785
|
-
if (similarity < minSimilarity())
|
|
786
|
-
continue;
|
|
787
|
-
candidates.push({ ...rowToCandidate(row, similarity), retrievalSource: "vec" });
|
|
788
|
-
}
|
|
789
|
-
return candidates;
|
|
790
|
-
}
|
|
791
|
-
async searchBruteForce(queryEmbedding, agentId, options) {
|
|
792
|
-
const { scope, limit, source, isLead, includeExpired } = options;
|
|
793
|
-
const conditions = ["embedding IS NOT NULL"];
|
|
794
|
-
const params = [];
|
|
795
|
-
this.addScopeConditions(conditions, params, agentId, scope, isLead);
|
|
796
|
-
if (source) {
|
|
797
|
-
conditions.push("source = ?");
|
|
798
|
-
params.push(source);
|
|
799
|
-
}
|
|
800
|
-
if (!includeExpired) {
|
|
801
|
-
conditions.push("(expiresAt IS NULL OR expiresAt > datetime('now'))");
|
|
802
|
-
}
|
|
803
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
804
|
-
const rows = await getDbClient().query(`SELECT * FROM agent_memory ${whereClause}`, params);
|
|
805
|
-
const candidates = [];
|
|
806
|
-
for (const row of rows) {
|
|
807
|
-
if (!row.embedding)
|
|
808
|
-
continue;
|
|
809
|
-
const emb = deserializeEmbedding(row.embedding);
|
|
810
|
-
if (emb.length !== queryEmbedding.length)
|
|
811
|
-
continue;
|
|
812
|
-
const similarity = cosineSimilarity(queryEmbedding, emb);
|
|
813
|
-
if (similarity < minSimilarity())
|
|
814
|
-
continue;
|
|
815
|
-
candidates.push({ ...rowToCandidate(row, similarity), retrievalSource: "fallback" });
|
|
816
|
-
}
|
|
817
|
-
candidates.sort((a, b) => b.similarity - a.similarity);
|
|
818
|
-
return candidates.slice(0, limit);
|
|
819
|
-
}
|
|
820
|
-
addScopeConditions(conditions, params, agentId, scope, isLead, tableAlias = "") {
|
|
821
|
-
const col = (name) => tableAlias ? `${tableAlias}.${name}` : name;
|
|
822
|
-
if (!isLead) {
|
|
823
|
-
if (scope === "agent") {
|
|
824
|
-
conditions.push(`${col("agentId")} = ? AND ${col("scope")} = 'agent'`);
|
|
825
|
-
params.push(agentId);
|
|
826
|
-
} else if (scope === "swarm") {
|
|
827
|
-
conditions.push(`${col("scope")} = 'swarm'`);
|
|
828
|
-
} else {
|
|
829
|
-
conditions.push(`(${col("agentId")} = ? OR ${col("scope")} = 'swarm')`);
|
|
830
|
-
params.push(agentId);
|
|
831
|
-
}
|
|
832
|
-
} else {
|
|
833
|
-
if (scope === "agent") {
|
|
834
|
-
conditions.push(`${col("scope")} = 'agent'`);
|
|
835
|
-
} else if (scope === "swarm") {
|
|
836
|
-
conditions.push(`${col("scope")} = 'swarm'`);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
buildListWhereClause(agentId, options) {
|
|
841
|
-
const { scope = "all", isLead = false, ownerAgentId, source, sourcePath } = options;
|
|
842
|
-
const conditions = [];
|
|
843
|
-
const params = [];
|
|
844
|
-
this.addScopeConditions(conditions, params, agentId, scope, isLead);
|
|
845
|
-
if (ownerAgentId) {
|
|
846
|
-
conditions.push("agentId = ?");
|
|
847
|
-
params.push(ownerAgentId);
|
|
848
|
-
}
|
|
849
|
-
if (source) {
|
|
850
|
-
conditions.push("source = ?");
|
|
851
|
-
params.push(source);
|
|
852
|
-
}
|
|
853
|
-
const sourcePathNeedle = sourcePath?.trim().toLowerCase();
|
|
854
|
-
if (sourcePathNeedle) {
|
|
855
|
-
conditions.push("instr(lower(coalesce(sourcePath, '')), ?) > 0");
|
|
856
|
-
params.push(sourcePathNeedle);
|
|
857
|
-
}
|
|
858
|
-
return {
|
|
859
|
-
whereClause: conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "",
|
|
860
|
-
params
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
async list(agentId, options = {}) {
|
|
864
|
-
const { limit = 20, offset = 0 } = options;
|
|
865
|
-
const { whereClause, params } = this.buildListWhereClause(agentId, options);
|
|
866
|
-
const queryParams = [...params, limit, offset];
|
|
867
|
-
const rows = await getDbClient().query(`SELECT * FROM agent_memory ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`, queryParams);
|
|
868
|
-
return rows.map(rowToAgentMemory);
|
|
869
|
-
}
|
|
870
|
-
async count(agentId, options = {}) {
|
|
871
|
-
const { whereClause, params } = this.buildListWhereClause(agentId, options);
|
|
872
|
-
const row = await getDbClient().get(`SELECT COUNT(*) AS count FROM agent_memory ${whereClause}`, params);
|
|
873
|
-
return row?.count ?? 0;
|
|
874
|
-
}
|
|
875
|
-
isSourceProtected(source) {
|
|
876
|
-
return PROTECTED_SOURCES.has(source);
|
|
877
|
-
}
|
|
878
|
-
async edit(input) {
|
|
879
|
-
if (!input.id && !(input.key && input.scope)) {
|
|
880
|
-
throw new Error("memory edit requires either id or key+scope");
|
|
881
|
-
}
|
|
882
|
-
const { result, ftsContent } = await getDbClient().transaction(async (tx) => {
|
|
883
|
-
const row = input.id ? await tx.get("SELECT * FROM agent_memory WHERE id = ?", [input.id]) : await tx.get(`SELECT * FROM agent_memory
|
|
884
|
-
WHERE key = ? AND scope = ? AND coalesce(agentId, '') = coalesce(?, '')
|
|
885
|
-
ORDER BY chunkIndex ASC
|
|
886
|
-
LIMIT 1`, [input.key, input.scope, input.agentId ?? null]);
|
|
887
|
-
if (!row)
|
|
888
|
-
throw new Error("memory not found");
|
|
889
|
-
if ((row.totalChunks ?? 1) !== 1)
|
|
890
|
-
throw new Error("memory edit only supports single-chunk rows");
|
|
891
|
-
if (input.expectedVersion && input.expectedVersion !== (row.version ?? 1)) {
|
|
892
|
-
throw new Error("memory version conflict");
|
|
893
|
-
}
|
|
894
|
-
const previousVersion = row.version ?? 1;
|
|
895
|
-
const nextContent = applyEditMode(input.mode, row.content, {
|
|
896
|
-
content: input.content,
|
|
897
|
-
oldString: input.oldString,
|
|
898
|
-
newString: input.newString
|
|
899
|
-
});
|
|
900
|
-
const nextHash = contentSha256(nextContent);
|
|
901
|
-
if (nextHash === row.contentHash) {
|
|
902
|
-
return {
|
|
903
|
-
result: {
|
|
904
|
-
memory: rowToAgentMemory(row),
|
|
905
|
-
changed: false,
|
|
906
|
-
previousVersion,
|
|
907
|
-
version: previousVersion,
|
|
908
|
-
contentHash: nextHash
|
|
909
|
-
},
|
|
910
|
-
ftsContent: null
|
|
911
|
-
};
|
|
912
|
-
}
|
|
913
|
-
const nextVersion = previousVersion + 1;
|
|
914
|
-
const now = new Date().toISOString();
|
|
915
|
-
await tx.run(`INSERT INTO agent_memory_version (id, memory_id, version, content, contentHash, intent, operation, changedByAgentId, createdAt, updatedAt, created_by, updated_by)
|
|
916
|
-
VALUES (?, ?, ?, ?, ?, ?, 'edit', ?, ?, ?, ?, ?)`, [
|
|
917
|
-
crypto.randomUUID(),
|
|
918
|
-
row.id,
|
|
919
|
-
nextVersion,
|
|
920
|
-
nextContent,
|
|
921
|
-
nextHash,
|
|
922
|
-
input.intent,
|
|
923
|
-
input.changedByAgentId ?? null,
|
|
924
|
-
now,
|
|
925
|
-
now,
|
|
926
|
-
input.changedByAgentId ?? null,
|
|
927
|
-
input.changedByAgentId ?? null
|
|
928
|
-
]);
|
|
929
|
-
await tx.run(`UPDATE agent_memory
|
|
930
|
-
SET content = ?, contentHash = ?, version = ?, updatedAt = ?
|
|
931
|
-
WHERE id = ?`, [nextContent, nextHash, nextVersion, now, row.id]);
|
|
932
|
-
return {
|
|
933
|
-
result: {
|
|
934
|
-
memory: rowToAgentMemory({
|
|
935
|
-
...row,
|
|
936
|
-
content: nextContent,
|
|
937
|
-
contentHash: nextHash,
|
|
938
|
-
version: nextVersion,
|
|
939
|
-
updatedAt: now
|
|
940
|
-
}),
|
|
941
|
-
changed: true,
|
|
942
|
-
previousVersion,
|
|
943
|
-
version: nextVersion,
|
|
944
|
-
contentHash: nextHash
|
|
945
|
-
},
|
|
946
|
-
ftsContent: nextContent
|
|
947
|
-
};
|
|
948
|
-
});
|
|
949
|
-
if (ftsContent !== null) {
|
|
950
|
-
await this.syncFtsRow(result.memory.id, result.memory.name, ftsContent);
|
|
951
|
-
}
|
|
952
|
-
return result;
|
|
953
|
-
}
|
|
954
|
-
async listForCuration(agentId) {
|
|
955
|
-
const db = getDbClient();
|
|
956
|
-
const protectedList = [...PROTECTED_SOURCES].map((s) => `'${s}'`).join(",");
|
|
957
|
-
if (agentId) {
|
|
958
|
-
return db.query(`SELECT id, source, name, createdAt FROM agent_memory
|
|
959
|
-
WHERE agentId = ? AND source NOT IN (${protectedList})`, [agentId]);
|
|
960
|
-
}
|
|
961
|
-
return db.query(`SELECT id, source, name, createdAt FROM agent_memory
|
|
962
|
-
WHERE source NOT IN (${protectedList})`);
|
|
963
|
-
}
|
|
964
|
-
async listForReembedding(options) {
|
|
965
|
-
const db = getDbClient();
|
|
966
|
-
if (options?.agentId) {
|
|
967
|
-
return db.query("SELECT id, content FROM agent_memory WHERE agentId = ?", [options.agentId]);
|
|
968
|
-
}
|
|
969
|
-
return db.query("SELECT id, content FROM agent_memory");
|
|
970
|
-
}
|
|
971
|
-
async purgeByIds(ids) {
|
|
972
|
-
if (ids.length === 0)
|
|
973
|
-
return;
|
|
974
|
-
if (this.vecInitialized && this.getVecTableSchema()) {
|
|
975
|
-
const placeholders = ids.map(() => "?").join(",");
|
|
976
|
-
await getDbClient().run(`DELETE FROM memory_vec WHERE memory_id IN (${placeholders})`, ids);
|
|
977
|
-
}
|
|
978
|
-
await this.deleteFtsRows(ids);
|
|
979
|
-
}
|
|
980
|
-
async delete(id) {
|
|
981
|
-
await this.purgeByIds([id]);
|
|
982
|
-
const result = await getDbClient().run("DELETE FROM agent_memory WHERE id = ?", [id]);
|
|
983
|
-
return result.changes > 0;
|
|
984
|
-
}
|
|
985
|
-
async deleteBySourcePath(sourcePath, agentId) {
|
|
986
|
-
const db = getDbClient();
|
|
987
|
-
const ids = await db.query("SELECT id FROM agent_memory WHERE sourcePath = ? AND agentId = ?", [sourcePath, agentId]);
|
|
988
|
-
await this.purgeByIds(ids.map((r) => r.id));
|
|
989
|
-
const result = await db.run("DELETE FROM agent_memory WHERE sourcePath = ? AND agentId = ?", [
|
|
990
|
-
sourcePath,
|
|
991
|
-
agentId
|
|
992
|
-
]);
|
|
993
|
-
return ids.length || result.changes;
|
|
994
|
-
}
|
|
995
|
-
async purgeExpired() {
|
|
996
|
-
const db = getDbClient();
|
|
997
|
-
const expiredIds = await db.query("SELECT id FROM agent_memory WHERE expiresAt IS NOT NULL AND expiresAt <= datetime('now')");
|
|
998
|
-
if (expiredIds.length === 0)
|
|
999
|
-
return 0;
|
|
1000
|
-
const batchSize = 500;
|
|
1001
|
-
for (let i = 0;i < expiredIds.length; i += batchSize) {
|
|
1002
|
-
await this.purgeByIds(expiredIds.slice(i, i + batchSize).map((r) => r.id));
|
|
1003
|
-
}
|
|
1004
|
-
const result = await db.run("DELETE FROM agent_memory WHERE expiresAt IS NOT NULL AND expiresAt <= datetime('now')");
|
|
1005
|
-
console.log(`[memory] Purged ${result.changes} expired memory row(s) (vec cleanup: ${expiredIds.length} id(s))`);
|
|
1006
|
-
return result.changes;
|
|
1007
|
-
}
|
|
1008
|
-
async updateEmbedding(id, embedding, model) {
|
|
1009
|
-
const db = getDbClient();
|
|
1010
|
-
const buffer = serializeEmbedding(embedding);
|
|
1011
|
-
await db.run("UPDATE agent_memory SET embedding = ?, embeddingModel = ? WHERE id = ?", [
|
|
1012
|
-
buffer,
|
|
1013
|
-
model,
|
|
1014
|
-
id
|
|
1015
|
-
]);
|
|
1016
|
-
if (this.vecInitialized && this.getVecTableSchema()) {
|
|
1017
|
-
const vecBuffer = this.toVecBuffer(embedding);
|
|
1018
|
-
if (!vecBuffer) {
|
|
1019
|
-
console.warn(`[memory-vec] update skipped memory_id=${id} reason=invalid_dimensions dimensions=${embedding.length} expected=${EMBEDDING_DIMENSIONS}`);
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
try {
|
|
1023
|
-
await db.run("DELETE FROM memory_vec WHERE memory_id = ?", [id]);
|
|
1024
|
-
await db.run("INSERT INTO memory_vec(memory_id, embedding) VALUES (?, ?)", [id, vecBuffer]);
|
|
1025
|
-
} catch (err) {
|
|
1026
|
-
console.error(`[memory-vec] update failed memory_id=${id}: ${err.message}`);
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
async getStats(agentId) {
|
|
1031
|
-
const db = getDbClient();
|
|
1032
|
-
const total = await db.get("SELECT COUNT(*) as count FROM agent_memory WHERE agentId = ?", [agentId]);
|
|
1033
|
-
const bySourceRows = await db.query("SELECT source, COUNT(*) as count FROM agent_memory WHERE agentId = ? GROUP BY source", [agentId]);
|
|
1034
|
-
const byScopeRows = await db.query("SELECT scope, COUNT(*) as count FROM agent_memory WHERE agentId = ? GROUP BY scope", [agentId]);
|
|
1035
|
-
const withEmbeddings = await db.get("SELECT COUNT(*) as count FROM agent_memory WHERE agentId = ? AND embedding IS NOT NULL", [agentId]);
|
|
1036
|
-
const expired = await db.get("SELECT COUNT(*) as count FROM agent_memory WHERE agentId = ? AND expiresAt IS NOT NULL AND expiresAt <= datetime('now')", [agentId]);
|
|
1037
|
-
const bySource = {};
|
|
1038
|
-
for (const row of bySourceRows)
|
|
1039
|
-
bySource[row.source] = row.count;
|
|
1040
|
-
const byScope = {};
|
|
1041
|
-
for (const row of byScopeRows)
|
|
1042
|
-
byScope[row.scope] = row.count;
|
|
1043
|
-
return {
|
|
1044
|
-
total: total?.count ?? 0,
|
|
1045
|
-
bySource,
|
|
1046
|
-
byScope,
|
|
1047
|
-
withEmbeddings: withEmbeddings?.count ?? 0,
|
|
1048
|
-
expired: expired?.count ?? 0
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
getHealthCounts() {
|
|
1052
|
-
const db = getDb();
|
|
1053
|
-
const tableExists = this.getVecTableSchema() !== null;
|
|
1054
|
-
const tableUsable = tableExists && isSqliteVecAvailable();
|
|
1055
|
-
const count = (sql) => db.prepare(sql).get()?.count ?? 0;
|
|
1056
|
-
return {
|
|
1057
|
-
total: count("SELECT COUNT(*) as count FROM agent_memory"),
|
|
1058
|
-
withEmbedding: count("SELECT COUNT(*) as count FROM agent_memory WHERE embedding IS NOT NULL"),
|
|
1059
|
-
validEmbedding: count(`SELECT COUNT(*) as count FROM agent_memory WHERE embedding IS NOT NULL AND length(embedding) = ${VECTOR_BYTES}`),
|
|
1060
|
-
invalidEmbedding: count(`SELECT COUNT(*) as count FROM agent_memory WHERE embedding IS NOT NULL AND length(embedding) != ${VECTOR_BYTES}`),
|
|
1061
|
-
searchable: count(`SELECT COUNT(*) as count FROM agent_memory
|
|
1062
|
-
WHERE embedding IS NOT NULL
|
|
1063
|
-
AND length(embedding) = ${VECTOR_BYTES}
|
|
1064
|
-
AND (expiresAt IS NULL OR expiresAt > datetime('now'))`),
|
|
1065
|
-
memoryVec: tableUsable ? count("SELECT COUNT(*) as count FROM memory_vec") : 0,
|
|
1066
|
-
missingFromVec: tableUsable ? count(`SELECT COUNT(*) as count
|
|
1067
|
-
FROM agent_memory m
|
|
1068
|
-
LEFT JOIN memory_vec v ON v.memory_id = m.id
|
|
1069
|
-
WHERE m.embedding IS NOT NULL
|
|
1070
|
-
AND length(m.embedding) = ${VECTOR_BYTES}
|
|
1071
|
-
AND v.memory_id IS NULL`) : count(`SELECT COUNT(*) as count FROM agent_memory WHERE embedding IS NOT NULL AND length(embedding) = ${VECTOR_BYTES}`),
|
|
1072
|
-
extraInVec: tableUsable ? count(`SELECT COUNT(*) as count
|
|
1073
|
-
FROM memory_vec v
|
|
1074
|
-
LEFT JOIN agent_memory m ON m.id = v.memory_id
|
|
1075
|
-
WHERE m.id IS NULL`) : 0
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
getHealth() {
|
|
1079
|
-
const schema = this.getVecTableSchema();
|
|
1080
|
-
const counts = this.getHealthCounts();
|
|
1081
|
-
const reasons = [];
|
|
1082
|
-
if (!isSqliteVecAvailable())
|
|
1083
|
-
reasons.push("sqlite_vec_extension_unavailable");
|
|
1084
|
-
if (!schema)
|
|
1085
|
-
reasons.push("memory_vec_table_missing");
|
|
1086
|
-
if (!this.vecInitialized)
|
|
1087
|
-
reasons.push("memory_vec_not_initialized");
|
|
1088
|
-
if (counts.memoryVec === 0)
|
|
1089
|
-
reasons.push("memory_vec_empty");
|
|
1090
|
-
if (counts.missingFromVec > 0)
|
|
1091
|
-
reasons.push("memory_vec_missing_embeddings");
|
|
1092
|
-
if (counts.extraInVec > 0)
|
|
1093
|
-
reasons.push("memory_vec_extra_rows");
|
|
1094
|
-
return {
|
|
1095
|
-
sqliteVec: {
|
|
1096
|
-
extensionLoaded: isSqliteVecAvailable(),
|
|
1097
|
-
tableExists: schema !== null,
|
|
1098
|
-
initialized: this.vecInitialized,
|
|
1099
|
-
vectorDimensions: EMBEDDING_DIMENSIONS,
|
|
1100
|
-
distanceMetric: "cosine",
|
|
1101
|
-
schema,
|
|
1102
|
-
lastPopulate: this.lastPopulate
|
|
1103
|
-
},
|
|
1104
|
-
counts,
|
|
1105
|
-
retrievalMode: reasons.length === 0 ? "vec" : "fallback",
|
|
1106
|
-
reasons
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
var VECTOR_BYTES;
|
|
1111
|
-
var init_sqlite_store = __esm(() => {
|
|
1112
|
-
init_db();
|
|
1113
|
-
init_embedding();
|
|
1114
|
-
init_profile_sync();
|
|
1115
|
-
init_constants();
|
|
1116
|
-
init_reranker();
|
|
1117
|
-
VECTOR_BYTES = EMBEDDING_DIMENSIONS * Float32Array.BYTES_PER_ELEMENT;
|
|
1118
|
-
});
|
|
1119
|
-
|
|
1120
|
-
// src/be/memory/index.ts
|
|
1121
|
-
var embeddingProvider = null;
|
|
1122
|
-
var memoryStore = null;
|
|
1123
|
-
function getEmbeddingProvider() {
|
|
1124
|
-
if (!embeddingProvider) {
|
|
1125
|
-
const { OpenAIEmbeddingProvider: OpenAIEmbeddingProvider2 } = (init_openai_embedding(), __toCommonJS(exports_openai_embedding));
|
|
1126
|
-
embeddingProvider = new OpenAIEmbeddingProvider2;
|
|
1127
|
-
}
|
|
1128
|
-
return embeddingProvider;
|
|
1129
|
-
}
|
|
1130
|
-
function resetEmbeddingProvider() {
|
|
1131
|
-
embeddingProvider = null;
|
|
1132
|
-
}
|
|
1133
|
-
function getMemoryStore() {
|
|
1134
|
-
if (!memoryStore) {
|
|
1135
|
-
const { SqliteMemoryStore: SqliteMemoryStore2 } = (init_sqlite_store(), __toCommonJS(exports_sqlite_store));
|
|
1136
|
-
memoryStore = new SqliteMemoryStore2;
|
|
1137
|
-
}
|
|
1138
|
-
return memoryStore;
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
export { cosineSimilarity, serializeEmbedding, deserializeEmbedding, init_embedding, CANDIDATE_SET_MULTIPLIER, isGraphExpansionEnabled, EMBEDDING_DIMENSIONS, init_constants, computeScore, rerank, init_reranker, rowToCandidate, init_sqlite_store, getEmbeddingProvider, resetEmbeddingProvider, getMemoryStore };
|