mirror of
https://github.com/rohitg00/agentmemory.git
synced 2026-09-14 20:16:33 +08:00
dependabot/github_actions/actions/setup-node-7
476 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
db99ca5358 |
ci(deps)(deps): bump actions/setup-node from 6 to 7
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
9c82d2aa7d |
chore(release): v0.9.29 with project-scope parity across capture surfaces (#1141)
* chore(release): v0.9.29 with project-scope parity across surfaces Version trio + plugin manifests + supportedVersions + ExportData union bumped to 0.9.29; CHANGELOG entry covering everything since v0.9.28 with upgrade notes for the four visible behavior changes. Fixes the endpoint-count drift on main (130 registered routes vs docs saying 129 after #1132 landed in parallel with #1136). Project-scope parity: OpenCode plugin, Hermes plugin, Pi extension, and JSONL replay now resolve project the same way the hooks do (env override, git toplevel basename, cwd basename) instead of sending raw filesystem paths, closing #903 and #1135 and pre-empting the same bug in pi. The filesystem watcher accepts AGENTMEMORY_PROJECT_NAME with the old AGENTMEMORY_PROJECT kept as a deprecated alias, replay handles Windows-recorded paths, and OpenCode file enrichment matches the agent's lowercase tool names (the capitalized set never matched). Tests: opencode fallback expectations updated to basenames per the canonicalization, git-toplevel resolution covered with a fixture repo, new project-scope-parity suite for replay and fs-watcher. * fix(release): review findings, git-toplevel parity, doc counts - skills generator dedupes routes on method plus path, so the REST reference lists all 130 registered routes instead of hiding the second method on ten dual-method paths (header said 119) - fs-watcher trims AGENTMEMORY_PROJECT_NAME and the deprecated alias, treating whitespace as unset, and derives the git toplevel basename when watching a subdirectory - replay resolves the git toplevel basename when the recorded cwd still exists locally (memoized per cwd), keeping the basename fallback for historical or cross-platform paths; no env override here since a bulk import spans many projects - parity tests for replay git-root resolution, watcher git-root and trim behavior - stat-tests badge updated from 1428+ to 1550+ passing * fix(cli): refuse second-instance boot over a live daemon Closes the class behind issue 1140: agentmemory consolidate (or any unrecognized word) fell through the command table into the full server boot, registering a duplicate worker on the running engine; on iii 0.11.2 the second instance's shutdown tears down the daemon's HTTP trigger routing until a full engine restart. Unknown subcommands now error with the supported list, and main() probes livez on the resolved port and refuses to boot over a live daemon, so multi-instance setups on other ports are unaffected. Verified behaviorally against the built CLI: both paths refuse with exit 1. Also from review: the watcher stamps each event with its own root's project via a per-root map (an explicit config.project still overrides for every root), and replay only accepts a non-empty string cwd from parsed JSONL so malformed entries cannot reach the filesystem probe. * test(watcher): two-repository flush events scope to their own project * chore(release): bump packages/mcp, guard it, refresh CONTRIBUTING packages/mcp was still 0.9.28 after the release bump because nothing guarded it; a consistency test now pins it to package.json. CONTRIBUTING release list corrected to the files a bump actually touches (no tracked lockfile, the two extra plugin manifests, the export test derives from VERSION now), and the subsystems table gains src/cli, integrations/pi, and the generated-manifest note. * fix(export): refuse over-frame export instead of dropping the worker Closes the availability bug in issue 1142: GET /agentmemory/export assembles the full store and returns it through sdk.trigger, so a store whose serialized export passes the engine's 16 MiB WebSocket frame (tungstenite max_frame_size, not raisable under the 0.11.2 pin) dies on the worker->engine hop, drops the worker, and 404s every endpoint for ~1s. The session collections page on maxSessions/offset but ~18 others do not, so a large store hits this at any parameter combination. A shared frame-guard measures the serialized size before returning: mem::export returns a small oversized error instead of the giant object, and api::mesh-export returns 413 (same dead-end as #890). Either way the over-frame payload never crosses the boundary, so the daemon stays up and the failure is one clean request with a hint to narrow the range. Full pagination of the non-session collections is a follow-up. Layer 1 of the fix; verified with a synthetic oversized export returning the error object (tiny) rather than the payload. * ci: collapse to a single npm install to fix Node 24/26 CI The two-step install (npm install --package-lock-only then npm ci) failed only on the Node 24/26 matrix rows: their stricter npm rejects rolldown's optional platform bindings (@rolldown/binding-android-arm64) that a --package-lock-only pass does not fully enumerate. Lockfiles are gitignored, so npm ci re-validation buys no reproducibility here. A single lenient npm install resolves and installs in one pass. * fix(mesh): scope exported memories by project like actions api::mesh-export filtered actions by ?project but returned every project's memories. On a mesh instance federating one project to a peer, the peer pulled other projects' memories (cross-project leak), and those extras could push the payload past the 16 MiB transport frame into a 413 even when the requested project's own slice fit. Memories carry the same optional project field as actions, so filter both before the frame-size guard runs. Adds a regression test asserting a project-scoped export excludes other projects' memories and that an oversized memory in another project no longer 413s the scoped request. * chore(release): credit the Antigravity native hooks adapter in 0.9.29 notes * chore(release): sweep stale 0.9.28 refs for 0.9.29 Deploy Dockerfiles/compose/render pins, AGENTS.md stats header, opencode plugin manifest, website meta snapshot, test-count claims (1,428 -> 1,596) in README/AGENTS/stat SVGs, and the missing 0.9.29 CHANGELOG compare link. * chore(release): sync stat-tests badge to 1596+ and commit bridge exec bit * refactor: trim frame-guard comments and drop issue refs from code |
||
|
|
d60652a705 |
feat(cli): native hooks adapter for Antigravity CLI (agy) (#1146)
* feat(cli): native hooks adapter for Antigravity CLI (agy) Antigravity ships two products with unrelated configuration: the IDE, already wired by `connect antigravity`, and the `agy` CLI, which reads its customizations out of ~/.gemini/ and until now was not wired at all. This adds `connect antigravity-cli` for the latter — MCP via ~/.gemini/config/mcp_config.json, plus optional native auto-capture hooks behind --with-hooks. Unlike Droid (#1130), the Codex merge engine could not be reused. The Antigravity hooks contract differs in three ways: * hooks.json is a map of *named* hook bundles at the root, not the `{ hooks: { <Event>: [...] } }` envelope, so antigravity-hooks.ts implements a merge that owns top-level keys instead of per-event entries. User-authored bundles are preserved; a re-install replaces only the bundle whose commands point under the bundled plugin dir. * only five events exist (PreToolUse, PostToolUse, PreInvocation, PostInvocation, Stop) — no SessionStart/SessionEnd/UserPromptSubmit, so the session lifecycle is synthesized from the first PreInvocation and from Stop. PostInvocation is left unwired to avoid double-capture. * the stdin payload is camelCase and nested (`toolCall.args` with PascalCase keys, `conversationId`, `workspacePaths`), and stdout must be a JSON object — `pre-tool-use.mjs` writes raw prose when context injection is on. plugin/scripts/antigravity-bridge.mjs bridges all three: it normalizes the payload onto the shape the bundled hooks already accept, maps Cascade tool names (view_file, replace_file_content, …) onto the read/edit/write/grep vocabulary the capture heuristics use, pipes to the right script, discards child stdout and always answers `{}` so Antigravity's own permission decisions are never overridden. Event names, tool names and arg keys were verified against the shipped agy binary rather than docs alone (docs disagree on the global hooks path); the customization dir is ~/.gemini/config/, matching where agy already keeps mcp_config.json and plugins/. Signed-off-by: Bertho Joris <bertho_joris@yahoo.co.id> * fix(cli): keep $-bearing plugin paths literal when resolving hook commands resolveBundle() expanded ${CLAUDE_PLUGIN_ROOT} via String.prototype.replace with a string argument, so a plugin root containing `$$`, `$&`, "$`" or `$'` was read as a replacement pattern and rewritten: C:/plug$&in -> C:/plug${CLAUDE_PLUGIN_ROOT}in/scripts/... C:/plug$$in -> C:/plug$in/scripts/... `$1` and `$<name>` are unaffected — the regex has no capture groups. Switching to a replacer function keeps the path verbatim. The failure mode this closes is silent: the hook installs with a broken command and auto-capture simply never fires. Regression test builds the manifest against a temp plugin root named `plug$&$$in` and asserts the resolved command contains it literally. Reported by CodeRabbit on #1146. Signed-off-by: Bertho Joris <bertho_joris@yahoo.co.id> * fix(antigravity): emit an explicit allow decision from the PreToolUse hook Antigravity documents `decision` as a required field of PreToolUse hook output, and agy treats a response that omits it as a denial: the bare `{}` the bridge used to write made the agent refuse every matched tool call (reported against agy 1.0.5 in cmux#5358) instead of passively capturing it. `responseFor` now answers PreToolUse with `{"decision":"allow"}` and leaves every other event on `{}`, so no event that carries no permission decision starts overriding the user's own settings. The response is written from the `finally` block, so a failed capture or an unparseable payload still produces the contract rather than empty stdout, which PreToolUse would read the same way as `{}`. Tests cover both the pure contract and the built bundled script running end to end with no server listening. Also extends the ARG_KEY_MAP test to every mapped key and pins that an explicit canonical key wins over a PascalCase alias. * fix(antigravity): match agy's real hooks.json schema, verified against 1.0.15 Three defects found by probing a live agy 1.0.15 with an instrumented hook, each of which stopped the adapter from capturing anything at all. Lifecycle events take a flat handler list, not the tool-event wrapper. agy parses `PreToolUse`/`PostToolUse` as `[{matcher, hooks: [...]}]` but `PreInvocation`/`PostInvocation`/`Stop` as a bare `[{type, command}]`, since there is no tool name to match on. Wrapping a lifecycle event makes agy read the wrapper itself as a handler and reject the *whole file* with `invalid hook "agentmemory": command hook must specify 'command'` — so the mis-shaped Stop entry disabled every hook in the bundle, and would have disabled hooks other tools had written to the same file. `command` is not run through a shell and quotes are not stripped, so the quoted path resolved to a module name that literally began with a double quote: `Cannot find module 'C:\Users\…\.gemini\config\"C:\…\bridge.mjs"'`. Commands are now bare. That also means a path containing spaces cannot be expressed at all — quoted and unquoted both fail — so the installer refuses with an explanation instead of writing hooks that can only fail at tool time. The merge engine reads both shapes when deciding which bundles agentmemory owns, so a re-install over the old wrapped layout still replaces it rather than leaving a second copy behind. Tests pin both event shapes, the absence of quotes, the space check, and normalization of a payload captured verbatim from the live run — which also confirms `conversationId`, PascalCase `toolCall.args`, and that agy sends no `cwd` key at all. * refactor(antigravity): cut comment volume to match the sibling adapters The bundled script carried 24 comment lines where every other script in plugin/scripts has three. The bundler strips `//` comments but preserves JSDoc blocks, so the fix is to document the bridge's exported helpers with line comments: the explanations stay in source and the generated artifact comes out as clean as its siblings. The connect adapter and merge engine restated the same facts in a file header and again in a per-function block. Kept one statement of each, dropped the repetition, and left the verified agy behaviour in place since that is the part not derivable from the code. --------- Signed-off-by: Bertho Joris <bertho_joris@yahoo.co.id> |
||
|
|
5023cf3ccb |
fix(memory): honest memory_forget reporting + lesson delete path (#1132)
* fix(memory): guard mem::forget delete/count on record existence
Calling mem::forget with a lesson id (lsn_*) deleted a nonexistent key
from the memories keyspace, counted it, and reported success. Guard the
delete, index cleanup, and counter on the kv.get result, matching the
mem::governance-delete pattern, so nonexistent ids return
{ success: true, deleted: 0 } with no audit row. Closes #1120.
* feat(lessons): add mem::lesson-delete soft-delete function
Register mem::lesson-delete to set deleted: true on a lesson, mirroring
the lesson-strengthen existence guard and audit pattern. Read paths
already filter !l.deleted, and re-saving deleted content creates a fresh
lesson. Adds lesson_delete to the audit operation union.
* feat(mcp): expose memory_lesson_delete tool and REST endpoint
Wire mem::lesson-delete through the MCP tool registry and dispatch
case (memory_lesson_delete) and a POST /agentmemory/lessons/delete REST
route with 400 for a missing lessonId and 404 for a nonexistent lesson.
* chore(consistency): bump tool/endpoint counts to 54/129
Adds memory_lesson_delete to the registry, so update every count surface:
tool-count test, README badge and prose, AGENTS.md stats,
INSTALL_FOR_AGENTS.md, plugin manifests and docs, and the two code
comments this change makes stale. REST endpoint count goes 128 to 129
for the new /agentmemory/lessons/delete route.
* refactor(lessons): simplify 404 mapping and restore decay-delta test
Cast the lesson-delete trigger result once instead of twice inline, and
restore the lastDecayedAt incremental-delta decay test that was dropped
when the lesson-delete describe block was added.
* fix(review): align 404 error shape and regenerate skill references
Review fixes: the lesson-delete REST route now returns the repo-standard
{ error: 'lesson not found' } body on 404 instead of the function-shaped
{ success: false } payload, matching api::memory-by-id. Regenerated the
autogen MCP and REST skill references so memory_lesson_delete and the
lessons/delete route appear in the tables with accurate counts.
* fix(lessons): normalize lessonId at entry points and harden no-op test
Address CodeRabbit review: trim lessonId once at both the MCP dispatch
and REST route before triggering mem::lesson-delete (whitespace-padded
ids previously 404'd or looked up raw), and extend the nonexistent-
memoryId regression test to assert the no-op path performs no kv.delete
and no search-index cleanup.
---------
Co-authored-by: Rohit Ghumare <48523873+rohitg00@users.noreply.github.com>
|
||
|
|
9be3d2743a |
feat(opencode): allow AGENTMEMORY_PROJECT_NAME to override project name (#1125)
* feat(opencode): allow AGENTMEMORY_PROJECT_NAME to override project name The OpenCode capture plugin resolved the project as ctx.worktree || ctx.project?.id || process.cwd() (a full path) with no way to override it. It now consults AGENTMEMORY_PROJECT_NAME first — the same env var the Claude hooks use in resolveProject() — falling back to the existing resolution when unset. This lets users pin a stable project name and aligns the two plugin ecosystems so OpenCode and Claude sessions can share memory when the env var is set to the same value. Signed-off-by: Abdullah Alaqeel <abdullah.t.aqeel@gmail.com> * test(opencode): replace source-text assertions with runtime project-name tests Execute the plugin end-to-end, fire a session.created event, and assert the /session/start payload's project field. Covers whitespace trimming of AGENTMEMORY_PROJECT_NAME and the documented fallback chain (ctx.worktree -> ctx.project.id -> process.cwd). Signed-off-by: Abdullah Alaqeel <abdullah.t.aqeel@gmail.com> --------- Signed-off-by: Abdullah Alaqeel <abdullah.t.aqeel@gmail.com> |
||
|
|
d219763ccb |
feat(cli): native hooks adapter for Droid (Factory.ai) (#1130)
* feat(cli): add native hooks adapter for Droid (Factory.ai) Droid ships a first-party hooks system (~/.factory/hooks.json) using the same manifest shape as Codex/Claude Code. `agentmemory connect droid --with-hooks` now merges the bundled hooks.droid.json into it, covering SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, and SessionEnd via the existing buildMergedHooks merge/re-install logic. Also fixes a Windows test-isolation bug in connect-new-agents.test.ts where os.homedir() reads USERPROFILE (not HOME), which was silently letting adapter tests write into the real user home directory. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs: changelog entry + regenerate agentmemory-agents skill reference for Droid hooks CHANGELOG [Unreleased] entry for the new --with-hooks path, and npm run skills:gen to sync the auto-generated agents table (pulled from src/cli/connect/index.ts protocolNote) so it reflects Droid's updated hooks capability. tools/rest/config/hooks reference docs showed as stale too but diffed empty (pre-existing line-ending-only drift on this checkout) so left untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(cli): surface skipped hook results in dry-run branch, tighten Droid hook tests - json-mcp-adapter: capture and warn on skipped hook results in the dry-run branch (consistent with already-wired and post-install branches) - droid-connect-hooks test: assert exact five-event set instead of presence-only plus selective absence checks - droid-connect-hooks test: use deep equality (toEqual) for idempotency check instead of per-event length comparison Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix(cli): use Droid Create tool in PreToolUse matcher Droid has no Write tool — file creation is Create — so the Claude-era matcher blocked enrichment on new files. Align the manifest and test. Co-authored-by: Cursor <cursoragent@cursor.com> * docs: drop changelog entry and trim Droid README row Per review: remove the Unreleased CHANGELOG addition, and keep the Droid agents-table row short with a brief --with-hooks mention. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
52d229d517 |
fix(claude-bridge): restore memory/ subdir in MEMORY.md path (#1134)
The #625 fix removed the memory/ subdirectory and pointed the bridge at ~/.claude/projects/<slug>/MEMORY.md. Claude Code 2.x actually stores project memory at ~/.claude/projects/<slug>/memory/MEMORY.md (the index) plus per-topic .md files in the same memory/ dir — verified against the Claude Code 2.1.141 bundle (constants memory/MEMORY.md) and an existing project memory dir on disk. With the current path the bridge writes a MEMORY.md that Claude Code never reads, so CLAUDE_MEMORY_BRIDGE is silently broken on every platform. Restore the memory/ segment while keeping the leading-dash slug fix from #625, and update the path tests accordingly. Co-authored-by: txtsamu <txtsamu@users.noreply.github.com> |
||
|
|
a7114c7b68 |
test(hooks): decouple resolveProject tests from checkout directory name (#1138)
* test(hooks): decouple resolveProject tests from checkout directory name test/hook-project.test.ts asserted that resolveProject() returns the literal "agentmemory", which only holds when the repository is cloned into a directory named exactly "agentmemory". Any fork, worktree or rename made five tests fail on a clean checkout, blocking the "npm test must pass" gate in CONTRIBUTING.md. Run the git-toplevel cases against a throwaway repo created under tmpdir() with a name the test controls, and stub process.cwd() with vi.spyOn for the cwd-fallback cases. The assertions still exercise the real behaviour: dropping the git-toplevel branch or the process.cwd() default from resolveProject() still fails the suite. Signed-off-by: Dmitrii Zhukov <dmitry0983@gmail.com> * test(hooks): ceiling the git search in the non-repo fallback case The fallback case created its fixture under os.tmpdir() and assumed that is outside any repository. It is not guaranteed: with TMPDIR pointing at a working directory, git rev-parse walks up from the fixture, finds the enclosing repo and returns its basename, so the fallback under test never runs. Verified by pointing TMPDIR at a checkout — the assertion failed with the repository's name in place of the fixture's. Set GIT_CEILING_DIRECTORIES to the fixture's parent for the duration of the assertion and restore it afterwards. The path is resolved first because git compares ceiling entries after resolving symlinks, and os.tmpdir() is a symlink on macOS. Signed-off-by: Dmitrii Zhukov <dmitry0983@gmail.com> --------- Signed-off-by: Dmitrii Zhukov <dmitry0983@gmail.com> |
||
|
|
6cc9b9f0fe |
fix: env hydration, indexing, consolidation lifecycle, connector activation, hardening (#1136)
* fix: env hydration, indexing, consolidation, connectors, hardening - config: hydrate ~/.agentmemory/.env into process.env at boot so all modules see it - search: shared indexRecords() so export-import and replay populate BM25 and vector (#1072) - snapshot: wire the periodic timer (#1006), clamp non-positive intervals, add a reentrancy guard - schema: CJK-aware jaccard dedup plus exact-match fallback for short memories - embeddings: shared resolveDimensions() so openrouter stops hardcoding 1536 (#1002) - viewer: buffer request bodies before decoding to fix multibyte corruption (#930) - providers: retry 429/503 with Retry-After under a total-elapsed budget cap - consolidation: fire on session stop (#1087), gate keyless installs, debounce the per-turn stop hook, drop the client-side double-fire - evict: bound stale-session recovery to one consolidation pass - api/patterns: bound session fan-out (#1100) - connect: write a memory-usage guideline into each hook-less agent's native rules file (12 agents, doc-verified paths, --no-guidelines opt-out) - graph: import graphify's graph.json via mem::graph::import-graphify + POST /agentmemory/graph/import-graphify; shared persistGraphDelta with endpoint remap so merged nodes never leave dangling or duplicate edges - fs-watcher: stat roots before fs.watch so missing roots fail deterministically on Node 24+ - test: regression tests for every fix * fix: address review findings on import, debounce, and connect paths - guidelines: refuse to touch files with a lone or reversed marker pair - export-import/replay: indexing after committed writes is best-effort, logged instead of failing the import; flatten the nested runChunked so replace-mode deletes stay bounded to one chunk - graph: persist the snapshot when merge-only batches mutate cached topNodes/topEdges entries - graph-import: async fs, typeof validation on path/cwd; REST handler whitelists the payload and 400s non-string values - fetch: cancel discarded response bodies before retrying - events: serialize the consolidation cooldown check so concurrent stops cannot both pass the read-check-write window - evict: gate recovered-session consolidation on isConsolidationEnabled and mirror the stop path's force flag - search: rebuild indexes per session chunk to bound peak memory - test: regression coverage for each (malformed markers, concurrent stops, snapshot persistence, AMBIGUOUS/default mappings, env isolation) |
||
|
|
8c90741c63 |
chore(deps): migrate @xenova/transformers to @huggingface/transformers v4 (#1096)
* chore(deps): migrate @xenova/transformers to @huggingface/transformers v4 @xenova/transformers@2.x is deprecated and silently broken on Node 22+ (see #479). The project was renamed to @huggingface/transformers; same Apache-2.0 license, same code. v4 ships onnxruntime-node/web and sharp as hard deps, so they're dropped from our optionalDependencies. Pipeline / RawImage.fromBlob / tolist / text-classification output shape all unchanged. Three behavior-preserving adjustments needed: - All 4 pipeline call sites pass { dtype: "q8" }. v4's default on Node is fp32 (DEFAULT_DEVICE_DTYPE = "fp32"); v2 defaulted to quantized=true. Without explicit dtype, all 4 sites silently regress (~3.5x larger download, slower inference). dtype "q8" maps to model_quantized.onnx per v4's DEFAULT_DTYPE_SUFFIX_MAPPING; file exists in all 3 Xenova models. This was the regression that prompted the test additions below. - src/providers/embedding/local.ts: split import try/catch from pipeline() call so model-load errors (network, missing q8 variant, disk) propagate with their actual message, not masked as "Install @huggingface/transformers...". - src/providers/embedding/{local,clip}.ts: type module from typeof import("@huggingface/transformers") so PretrainedModelOptions flows through; drop hand-rolled aliases and @ts-ignore. Cast at assignment sites (pipeline return union isn't structurally assignable to our narrow FeatureExtractor / ClipPipeline shapes). Tests added where coverage was zero (would have caught the dtype regression): - test/local-embedding-provider.test.ts (3 tests): unavailable-path install hint; pipeline called with dtype:q8 + extractor options + mapped Float32Array result; embedBatch shape. - test/clip-embedding-provider.test.ts (5 tests): unavailable-path; text pipeline dtype:q8 + result; embedBatch; embedImage with data: URL decode; custom model ID propagation. - test/reranker.test.ts: positive-path using vi.doMock + resetModules. Other: - src/huggingface.d.ts deleted (package ships its own types). - src/xenova.d.ts removed. - src/providers/embedding/clip.ts: inline single-use DIMENSIONS constant. - tsdown.config.ts: trim neverBundle list and comment. - README.md L1267: BGE-small -> Xenova/all-MiniLM-L6-v2 (was always wrong). - 16 docs: install commands + prose mentions across main README, 11 translations, SECURITY.md, 2 benchmark docs, benchmark script. - Model IDs (Xenova/all-MiniLM-L6-v2, Xenova/clip-vit-base-patch32, Xenova/ms-marco-MiniLM-L-6-v2) kept — HF Hub repo names, still valid. Closes #1095. Fixes #479. Verified: 1424/1424 tests pass, build clean, tsc clean on migrated files. * test(embedding): add v4 smoke test, harden import errors, expand CI matrix Review follow-ups for #1096: - env-guarded non-mocked smoke test (RUN_HF_SMOKE=1) loading real Xenova/all-MiniLM-L6-v2, asserts 384 finite dims; skipped by default - selective ERR_MODULE_NOT_FOUND handling in local/clip providers so real init errors propagate (checks err.code and err.cause.code to handle vitest mock-factory wrapping) - CLIP install hint made embedding-agnostic (loader serves text + image) - afterEach mock cleanup in doMock-based provider/reranker tests - CI Node matrix: [20, 22] -> [20, 22, 24, 26] across ubuntu/macos * refactor(embedding): drop err.cause check, use manual mock for missing-module tests The .cause branch in the ERR_MODULE_NOT_FOUND check existed only to accommodate vitest's mock-factory wrapping, not a real Node loader behavior. Replace it with a manual mock fixture (__mocks__/@huggingface/ transformers.ts) that throws a Node-shaped error at module top-level, bypassing vitest's factory wrapper so the import rejects with err.code set directly. Production code now checks only err.code === 'ERR_MODULE_NOT_FOUND', matching real Node behavior. Tests verify the same public contract without coupling production code to the test framework. |
||
|
|
d8b5267c36 |
fix(cli): add --data-dir flag + AGENTMEMORY_DATA_DIR so engine state lives outside repos (#314)
* fix(cli): add --data-dir flag + AGENTMEMORY_DATA_DIR so engine state lives outside repos (#303) Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> * feat(cli): adopt legacy ./data stores before platform-default data dir Before falling back to the new platform default, detect an existing ./data (prior default) store and keep using it so existing users do not boot into an empty store. Covers both paths with tests. * docs(skills): regenerate REFERENCE.md to include AGENTMEMORY_DATA_DIR The autogen env block in the agentmemory-config skill reference was stale after adding the --data-dir flag; regenerated via npm run skills:gen so AGENTMEMORY_DATA_DIR is listed (34 -> 35 recognized variables). Fixes the failing skills-reference drift check. * docs: fix the local-models anchor in the provider table Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> * fix: narrow legacy data adoption, XDG relocation, and env export Addresses the three blocking review items. 1. resolveDataDir only adopts a cwd-local data/ directory when it is actually ours, keyed on data/state_store.db or data/iii-config.yaml existing. Before, any data/ folder was adopted, so running the CLI in an unrelated repo that happens to have one (common in ML projects) would start writing our stores into it. 2. cli.ts only exports AGENTMEMORY_DATA_DIR when the user actually supplied a --data-dir flag or env value. Exporting it for the default too meant ${AGENTMEMORY_DATA_DIR:-iii-data} in docker-compose never fell back to the named volume, so existing docker users booted against an empty bind-mounted platform dir with their memories stranded in the volume. 3. The XDG relocation now requires the XDG path to actually live under the git root, rather than firing whenever cwd is inside any repo with XDG_DATA_HOME set. Previously XDG_DATA_HOME=/mnt/data run from a normal repo was ignored with a warning claiming it was inside a git worktree when it was not. The two smaller items you flagged as fine-as-follow-ups (IMAGES_DIR not moving with --data-dir, and renderIiiConfig rewriting file_path by exact string match) are untouched here. --------- Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> |
||
|
|
a8e7d19a81 |
fix(viewer): surface health status from non-2xx /agentmemory/health responses (#1046)
* fix(viewer): surface health status from non-2xx /agentmemory/health responses The health endpoint intentionally returns HTTP 503 when status is "critical" (src/triggers/api.ts), with a valid JSON body describing the degraded state. The viewer's shared api() fetch helper treated any non-ok response as a hard failure and discarded the body, returning null - so renderDashboard's `h.status || 'unknown'` fallback always showed "unknown" for a critical backend instead of "critical". Parse and return the JSON body on non-ok responses too (falling back to null only if the body isn't valid JSON), so the dashboard reflects the real health status regardless of the HTTP status code used to carry it. Fixes #1019 Signed-off-by: Vidit Gujrathi <223816573+viditchess64@users.noreply.github.com> * fix(viewer): log non-2xx JSON parse failures at debug level Address CodeRabbit review: don't silently swallow the parse error when a non-2xx response body isn't valid JSON - log it at debug level to help diagnose unexpected content types. Signed-off-by: Vidit Gujrathi <223816573+viditchess64@users.noreply.github.com> * fix(viewer): restore non-2xx -> null contract in api(), special-case health Per review: changing the shared api() helper to return parsed bodies on non-2xx responses broke callers that treat null as "request failed" (e.g. loadGraph's disabled/error state). Restore the null contract as the default and let only the health call opt in via readErrorBody, since /agentmemory/health intentionally responds 503 with a valid JSON body when status is critical (#1019). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Signed-off-by: Vidit Gujrathi <223816573+viditchess64@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
6761a99ba1 |
fix: guard hooks against null payload (#1074)
#1047: JSON.parse("null") returns null without throwing, so every hook's parse guard passed it through and the first data.xxx access threw a TypeError. Bare main() turned that into an unhandled rejection -> exit 1 -> host reported 'hook failed' on every affected tool call. All 13 hook entrypoints now guard non-object payloads before dereferencing and wrap main() in .catch() to fail closed (silent exit 0). #1057: mem::context and api::context filtered candidate sessions by project only, leaking cross-agent observations/summaries under AGENTMEMORY_AGENT_SCOPE=isolated. Now applies the same agent-scope filter as mem::search (#817); api::context, api::session::start, and event::session::started forward agentId. Also: bump 0.9.28 across manifests/deploy/export-import set; refresh stale README/AGENTS stats (files/LOC/functions/KV; AGENTS tests 950+ -> 1,428+) and regenerate the website meta snapshot to 0.9.28; CHANGELOG 0.9.28 section; remove the rate-limited star-history chart from README and all 11 translations.v0.9.28 |
||
|
|
93ae9bc04f |
CLI onboarding UX, colored output, build + docs cleanup (#984)
* fix(cli): onboarding UX, colored output, build + docs cleanup - Clearer, actionable first-run guidance with an honest recovery path when startup can't proceed automatically. - Quieter boot: collapse the no-provider-key notice to a single dim line. - Colored CLI output across boot, status, demo, connect, and errors (adds picocolors to deps; panels stay aligned; color auto-disables on non-TTY). - Build warnings: migrate tsdown deps config, silence per-entry plugin-timings, make dynamic imports consistent. Only the node20 deprecation remains. - Docs: correct install instructions and pin install paths to the supported version across README and 11 translations; bump deploy Dockerfile version to 0.9.27. * docs: drop --instance from engine-conflict note Align the README recovery note with the CLI: stop the other engine, then run agentmemory (installs the pinned engine). --instance does not relocate the engine's config-bound port, so it was misleading. * docs(agents): latest global install, drop --instance, add conflict step Install runbook now uses npm install -g @agentmemory/agentmemory@latest (agent + future sessions get newest), removes the misleading --instance/--port port suggestions (neither relocates the config-bound engine), and adds an engine-conflict troubleshooting step (stop the other engine, re-run, pinned engine installs into ~/.agentmemory/bin). * docs(agents): drop multi-instance --instance note from runbook * fix: fail-closed engine adopt, image rollback, deploy version pins - cli.ts: only adopt a running engine when its version positively matches the pin; treat unknown/unverifiable as incompatible (fail closed) instead of adopting and risking a reconnect loop. Message handles the unknown-version case. - observe.ts: on a failed observation write, roll back via decrementImageRef (deletes the file only when no other observation references it) instead of deleteImage, so a failed write can't orphan a deduped image or leave a stale ref. - deploy: bump AGENTMEMORY_VERSION to 0.9.27 in the Coolify compose override and the Render blueprint to match the Dockerfile default. * fix: preserve write error on rollback, install/docs tidy - observe.ts: wrap the image-ref rollback in try/catch and log on failure so the original observation-write error is still thrown (not masked by a rollback error). - INSTALL_FOR_AGENTS: port-conflict bullet now lists 3112/3113 too (matches prerequisites). - README: engine-conflict note uses npx -y ...@latest (matches the runbook). - Drop redundant @latest from npm install -g (global install already resolves latest); keep @latest only on npx, which caches per version. * docs: drop issue-number refs from CLI output and READMEs Issue/PR numbers belong in the CHANGELOG, not in CLI output, README, or comments a reader can't act on. Strip our own #NNN refs (keeping the reasoning) from the no-key/boot notices, README prose + env-example comments, comments in the files this PR touches, and all 11 translated READMEs. Kept external upstream links (openai/codex#16430) and CHANGELOG anchors. * docs: remove npm downloads badge from README and translations |
||
|
|
f6f9e3cb13 |
fix(website): portal mobile nav sheet to body and close breakpoint gap (#670)
* fix(website): portal mobile nav sheet to body so it covers the viewport The mobile nav sheet was rendered inside the <header> element, which uses backdrop-filter: blur(10px). Per CSS spec, backdrop-filter establishes a containing block for fixed-positioned descendants, so the sheet's `position: fixed; inset: 0` was clipped to the header strip instead of covering the viewport. The dark overlay only painted across the top bar, leaving the hero fully visible behind the menu and the panel items squeezed into a strip too short to render. Render the sheet through createPortal to document.body so it escapes the nav's containing block. Drop backdrop-filter on the sheet (no longer needed once the bg is fully opaque) and switch to var(--abyss) so hero content can't bleed through even if a browser ignores 0.94 opacity. Closes #669 * fix(website): close mobile nav gaps on top of the portal fix Show the hamburger at the same 860px breakpoint that hides the section links; previously the links disappeared at 860px but the hamburger only appeared at 720px, leaving tablet widths with no navigation at all. Make the closed sheet inert so keyboard focus cannot tab into hidden links, return focus to the toggle when the sheet closes, and add noreferrer to the external links. --------- Co-authored-by: Rohit Ghumare <ghumare64@gmail.com> |
||
|
|
f3dc7f8ece |
fix: CLI help and docs papercuts (#897)
* fix: CLI help and docs papercuts - derive the connect agent list in --help from the adapter registry (18 agents, was a stale 9-name list) - warn on unknown --tools values instead of silently treating them as all - point claude-code, hermes, and openclaw protocol notes at real doc URLs (the docs/*.md paths never existed) - correct skill count to 15 in the connect summary and AGENTS.md - name the real 8 core tools in INSTALL_FOR_AGENTS.md - extend the tool-count consistency test to guard skill counts and the core tool sentence * chore: regenerate skill reference docs |
||
|
|
25e7701460 |
Adapters declare their own category (finish #872 DRY) (#887)
Onboarding still kept a hardcoded NATIVE_AGENTS set to label the picker (native plugin vs MCP server), decoupled from the adapters and prone to the same drift as the agent list (#872) — it had even mislabeled openclaw. Add a category field to ConnectAdapter (default mcp; factory sets mcp; the 7 native plugin/hook adapters set native), derive the onboarding hint from a.category, and assert every adapter declares one so a future adapter without a category fails CI. |
||
|
|
a842adecea |
Add Docs link to website navbar (#886)
Now that /docs proxies to the Mintlify-hosted docs via the Vercel rewrite, surface a DOCS entry in the site navbar (desktop + mobile, since SECTIONS feeds both). |
||
|
|
c2d836d623 |
Add opencode connect adapter, single-source the onboarding picker (#883)
* fix(connect): add opencode adapter and derive onboarding picker from ADAPTERS The onboarding agent picker and the connect interactive menu maintained two separate hardcoded lists that had drifted: onboarding offered opencode/goose/kilo/aider/claude-desktop/windsurf/roo (which connect had no adapter for, so picking them hit 'no adapter available, skipped'), while connect had antigravity/kiro/warp/continue/zed/droid/qwen that onboarding never offered. Add a real opencode adapter writing OpenCode's documented mcp schema (top-level mcp key, command as array, type/enabled), and make onboarding's buildAgentOptions derive from connect's ADAPTERS so the two can never diverge again. Connect-only adapters now appear in onboarding; opencode is now wireable; phantom agents with no adapter no longer appear. Fixes #872. * fix(connect): harden opencode adapter mcp merge and drop unexpanded env block Guard the mcp spread against a non-object value in an existing opencode.json, and remove the environment block whose shell-style ${VAR:-default} values OpenCode does not expand (writing them literally would clobber a user's real shell AGENTMEMORY_URL). The stdio child inherits the shell env and the shim defaults unset vars. Also drop a redundant inline comment in onboarding. * docs(skills): regenerate agents reference for opencode adapter Adding the opencode adapter changed ADAPTERS (17 -> 18); regenerate the auto-derived agents table so npm run skills:check passes in CI. |
||
|
|
1a7b5ca181 |
Serve docs at agent-memory.dev/docs via Mintlify rewrite (#885)
Per Mintlify's Vercel deploy guide, serve the docs at agent-memory.dev/docs by rewriting /docs and /docs/:match* to the Mintlify-hosted subdomain. Subdomain assumed to be agentmemory.mintlify.dev (matches the dashboard project name); change that one host if the actual Mintlify subdomain differs. |
||
|
|
a688e50538 |
Join session summaries into GET /agentmemory/sessions (#882)
api::sessions returned sessions straight from KV.sessions and never read KV.summaries, so the REST response and the viewer #sessions tab never showed summaries even after a successful /summarize. Join KV.summaries onto the returned (post-filter) sessions, mirroring the existing mem::context join. Fixes #879. |
||
|
|
a76224f098 | Update README.md | ||
|
|
c504b79633 |
Add oracleagentmemory to the comparison (#861)
Add oracleagentmemory (Oracle AI Database memory engine, Python-only, v26.4.0) to the README and COMPARISON.md comparisons. Its 94.4% LongMemEval is vendor self-reported, scored with GPT-5.5 at xhigh reasoning and requires an Oracle Database; agentmemory's 95.2% uses free local embeddings with no API key. Marked self-reported and unreproduced alongside MemPalace and supermemory. |
||
|
|
749c2806e0 |
Refresh competitor star counts and add benchmark caveat (#855)
* docs: refresh competitor star counts and add benchmark methodology note Update mem0 (58K), Letta (23K), Khoj (35K), and claude-mem (81K) star counts in the README comparison table and benchmark/COMPARISON.md, and add a note that agentmemory's R@5 is LongMemEval-S while the mem0/Letta figures are LoCoMo (different dataset, ballpark only). Verified the iii-engine v0.11.2 pin and per-agent hook counts are current, so they are left unchanged. * docs: replace claude-mem with supermemory in comparison * docs: add MemPalace column and mark competitor benchmarks as self-reported Add MemPalace to the comparison (54K-star vector-only OSS) and label its ~96.6% R@5 as a vendor-published claim we have not reproduced. Clarify that only agentmemory's 95.2% is our own measured, reproducible result and every other number is a vendor claim on a different benchmark or harness, so the figures are ballpark not head-to-head. * docs: expand README comparison table to full competitor set Add Khoj, supermemory, MemPalace, and Hippo columns to the README comparison table (previously only mem0, Letta, and Built-in), matching benchmark/COMPARISON.md. Mark MemPalace and supermemory retrieval as vendor self-reported and unreproduced; only agentmemory's R@5 is our own measured result. |
||
|
|
45de643cd2 |
Detailed, self-updating skills covering the whole system (#854)
* feat(skills): detailed tiered skills covering the whole system Restructure the 8 action skills into the tiered format (SKILL.md under 100 lines, EXAMPLES.md, shared troubleshooting, anti-patterns, cross-refs) and add 7 reference skills covering MCP tools, REST API, config, connect adapters, hooks, architecture, and skill authoring. Reference data tables are generated from source by scripts/skills/generate.ts and guarded against drift by npm run skills:check in CI, so the docs stay current as the repo changes. * docs(skills): fix recall REST mapping and label code fences Correct the recap/handoff REST fallback in _shared/TROUBLESHOOTING.md to POST /agentmemory/smart-search (/agentmemory/recall is not a registered route), and add language identifiers to all opening code fences across the skill docs to satisfy markdownlint MD040. |
||
|
|
7d01d057cd |
Agent-driven install path, Windows clarity, onboarding cost+inject prompts (#853)
* fix(mcp): correct tool-count drift and add count-consistency guard Correct stale 51-tool references to the registry truth of 53 across the standalone shim comments and the registry comment, derive the visible tool count from the registry, update README test-count stats to 1,423+, add the demo --serve README line, and add a vitest guard that fails CI if the registry count, README, or CLI help drift out of sync. * fix(cli): relax engine version gate and add demo --serve one-shot Convert the hard process.exit on a PATH iii version mismatch into a non-fatal warning so a mismatched iii on PATH no longer blocks first run; agentmemory continues with its own pinned engine in ~/.agentmemory/bin and honors AGENTMEMORY_III_VERSION. Add a --serve flag to the demo subcommand that boots the server, waits for health, runs the seed and query demo, and tears the server down on exit, all in one invocation. Derive the --tools help counts from the registry. * fix(cli): exit cleanly after demo --serve so in-process worker stops * feat: agent install runbook, windows clarity, onboarding prompts Add INSTALL_FOR_AGENTS.md, a runbook a coding agent can execute end to end: global install, start server, demo --serve, connect MCP, install skills, and a save/recall verification round-trip. README Install section now leads with the agent one-liner path and a short Windows callout (WSL2 is the fast path, native setup is manual and connect is unsupported there). Onboarding gains a context-injection step that asks whether to enable AGENTMEMORY_INJECT_CONTEXT with a token-cost note, persists the choice as a never-nag prefs flag, and writes the env var when enabled. Provider selection prints a one-line rough cost hint. * docs: expand agent install runbook with prereqs, REST shapes, troubleshooting |
||
|
|
cc28d61fc4 |
Fix onboarding stat drift, relax engine gate, one-command demo (#852)
* fix(mcp): correct tool-count drift and add count-consistency guard Correct stale 51-tool references to the registry truth of 53 across the standalone shim comments and the registry comment, derive the visible tool count from the registry, update README test-count stats to 1,423+, add the demo --serve README line, and add a vitest guard that fails CI if the registry count, README, or CLI help drift out of sync. * fix(cli): relax engine version gate and add demo --serve one-shot Convert the hard process.exit on a PATH iii version mismatch into a non-fatal warning so a mismatched iii on PATH no longer blocks first run; agentmemory continues with its own pinned engine in ~/.agentmemory/bin and honors AGENTMEMORY_III_VERSION. Add a --serve flag to the demo subcommand that boots the server, waits for health, runs the seed and query demo, and tears the server down on exit, all in one invocation. Derive the --tools help counts from the registry. * fix(cli): exit cleanly after demo --serve so in-process worker stops |
||
|
|
25158519d5 | chore(release): collapse v0.9.28 into v0.9.27 (not tagged yet) (#851) v0.9.27 | ||
|
|
a6e22fa9c7 |
fix(release): v0.9.28 hotfix wave (#843, #825, #817, iii pin) (#849)
* fix(release): v0.9.28 hotfix wave (data loss, graph reset, agent leak, iii pin) * fix(graph,search): cr followups for #843/#825/#817 wave * docs(readme): warn import-jsonl users about Claude Code 30-day cleanup |
||
|
|
a323fb0b96 | chore(release): v0.9.27 (#827) | ||
|
|
2a581408a7 |
fix(graph): top-degree snapshot cache for /graph/query + /graph/stats (#814) (#816)
* fix(graph): top-degree snapshot cache for query + stats * fix(consistency): bump REST endpoint count 126 -> 127 for snapshot-rebuild * fix(graph): incremental indexes + snapshot-only hot path + reset endpoint * fix(graph): sync degree comparator + composite-key reset + batch backfill * test(graph): edge-key wipe assertion + worker-death rejection path |
||
|
|
334e5ad81a | fix(cli): auto-derive streams + engine ports from REST anchor (#750) (#815) | ||
|
|
3e90110961 |
docs(readme): refresh stats + v0.9.26 callout + corrected P@5 (#807)
* docs(readme): refresh stats + v0.9.26 callout + corrected P@5 * docs(website): regen generated-meta.json (v0.9.26) |
||
|
|
e5c9dc68e4 |
docs(benchmarks): correct coding-agent-life-v1 P@5 numbers (#796) (#805)
* docs(benchmarks): correct coding-agent-life-v1 P@5 numbers * Update 2026-05-20-coding-agent-life-v1.md |
||
|
|
7446bded53 |
fix(release): v0.9.26 hotfix for first-run load crash (#797) (#804)
First boot after upgrading to v0.9.25 threw
'TypeError: Cannot read properties of undefined (reading v)' from
IndexPersistence.loadManifestData. Some iii-state adapters return
undefined (not null) for a missing key. The load path checked
manifest.value !== null and forwarded undefined to loadManifestData,
which then read undefined.v.
Self-healing: the next debounced save rebuilt a fresh manifest, so
operation continued after the warning — but the error scared every
fresh upgrader.
Fix: treat null + undefined + non-object values as 'no manifest' and
fall through to the legacy load path. Two regression tests:
- kv.get returning undefined surfaces as a clean { bm25: null,
vector: null } load, no throw.
- kv.get returning a string (wrong shape) does not crash the
manifest path.
Files bumped (9):
package.json, plugin/.claude-plugin/plugin.json, plugin/plugin.json,
plugin/.codex-plugin/plugin.json, src/version.ts, src/types.ts,
src/functions/export-import.ts, test/export-import.test.ts,
packages/mcp/package.json
125 test files / 1381 tests pass.
Closes #797.
v0.9.26
|
||
|
|
d442fee78f |
chore(release): v0.9.25 (#792)
Bump to 0.9.25 across 9 files + CHANGELOG. Closes #778 #775 #783 (PR #791), #758 #726 (PR #773), #759 (PR #772), #752 (PR #774), #729 (PR #780), #781 (PR #782), #753 (PR #789), #771 (PR #786), #762 (PR #764). Files bumped: - package.json - packages/mcp/package.json - plugin/.claude-plugin/plugin.json - plugin/.codex-plugin/plugin.json - plugin/plugin.json - src/version.ts - src/types.ts (ExportData.version union) - src/functions/export-import.ts (supportedVersions Set) - test/export-import.test.ts (assertion) 125 test files / 1379 tests pass. npm audit (root + website): 0 vulns.v0.9.25 |
||
|
|
1ab0a7d294 |
Merge pull request #791 from rohitg00/fix/778-fallback-model-resolution
fix: close three v0.9.24 regressions before v0.9.25 (#778 #775 #783) |
||
|
|
aa386b7884 |
fix(release): close three v0.9.24 regressions before v0.9.25
Bundles three independent bug fixes that block the v0.9.25 release. fix(providers): fallback chain resolves its OWN model, not the primary's (#778) createFallbackProvider copied config.model from the primary into every fallback. With OpenAI primary + Gemini fallback, Gemini was called with 'gpt-4o-mini' and 404'd on every call, which tripped the circuit breaker and blocked downstream LLM ops entirely. New defaultModelFor() resolves each provider's env-driven default (OPENAI_MODEL, GEMINI_MODEL, ANTHROPIC_MODEL, MINIMAX_MODEL, OPENROUTER_MODEL) so a fallback config now does what it advertises. fix(replay): import-jsonl re-keys on parsed.sessionId, not existing.id (#775) The existing-session branch wrote kv.set(KV.sessions, existing.id, ...) but legacy session rows can be missing the 'id' field. existing.id was then undefined, JSON.stringify dropped the key from the state::set payload, and the engine rejected the call with 'missing field key'. The rejection aborted the whole handler — a single legacy row killed the entire import. Now re-keys on parsed.sessionId (always populated by parseJsonlText's three-level fallback) and backfills missing existing.id so future reads stay clean. fix(summarize): strip markdown fences + retry final parse on failure (#783) parseSummaryXml passed the raw LLM payload to the tag regex. DeepSeek, GPT, and others wrap XML in fences or prepend a conversational sentence, which caused a silent parse_failed and the session was never summarized. Added stripXmlWrappers() that peels markdown fences and a surrounding pre/postamble before the regex runs. Also extended the chunk-level 2-attempt retry pattern to the final summarize merge so a flaky wrap on the first attempt doesn't drop the summary. Tests (10 new total) test/fallback-model-resolution.test.ts (5): primary/fallback model separation across OpenAI/Gemini/Anthropic/Minimax/OpenRouter, env override precedence, duplicate-provider skip. test/replay-import-key.test.ts (2): id-less existing row no longer aborts the import; fresh import path unchanged. test/summarize.test.ts (3): markdown-fenced XML parses cleanly, final retry recovers on first-attempt parse failure, double-fail still surfaces parse_failed. Full suite: 125 files / 1379 tests pass. Closes #778, closes #775, closes #783. |
||
|
|
3e806c9167 |
Merge pull request #789 from rohitg00/fix/753-graph-pagination
fix(viewer): graph tab blank on large graphs (graph/query pagination) |
||
|
|
a3fb7aeab5 |
Merge pull request #764 from Rokurolize/fix/762-shard-index-persistence
fix(index-persistence): shard persisted search snapshots |
||
|
|
436c86c86d |
fix(viewer): graph tab blank on large graphs (graph/query pagination)
Closes #753. POST /agentmemory/graph/query with an unbounded body ({}) on a corpus with 11k+ nodes returned HTTP 500 'Invocation stopped' because the response payload exceeded the iii state response channel ceiling. Viewer's api() returns null on non-2xx so the failure surfaced as '0 nodes / 0 edges' on a blank canvas with no signal — same shape as #544, never covered for graph/query. Backend (src/functions/graph.ts) - mem::graph-query accepts limit + offset on every branch and applies a default cap (DEFAULT_GRAPH_QUERY_LIMIT = 500, MAX = 5000) so an empty {} body never materializes the whole graph in one invocation. - Empty-body / nodeType-only branch ranks nodes by incident-edge degree before paginating so the truncated page surfaces the densest subgraph rather than an arbitrary KV scan order. - Edges in the page response are restricted to edges with BOTH endpoints in the page; cross-page edges are dropped so the viewer doesn't render dangling lines. - Response shape extended: totalNodes, totalEdges, truncated, limit, offset. totalNodes / totalEdges reflect the unbounded result for the given filter so callers can render 'showing X of Y' without re-querying. API (src/triggers/api.ts) - api::graph-query whitelists payload fields (startNodeId, nodeType, maxDepth, query, limit, offset) per AGENTS.md security rule. No more raw req.body spread. Viewer (src/viewer/index.html) - Initial /graph/query sends an explicit limit (500) rather than {}. - Distinguishes apiPost === null (server error) from { nodes: [], edges: [] } (truly empty corpus). Failure now renders a visible 'Graph query failed' banner with a Retry button instead of the 'No graph data yet. Building...' empty state. - Truncation banner above the search box: 'Showing N of M nodes (most-connected first). The full graph is too large to render at once.' so operators know the page is bounded. - state.graph gains queryError, truncated, totalNodes, totalEdges fields. Tests (test/graph.test.ts) - Unbounded {} body caps to 500 with truncated=true on a 1,200-node seed; high-degree nodes land on page 1. - limit + offset paginate without overlap on a 50-node seed. - limit clamped above MAX returns at most MAX. - Cross-page edges excluded from page response; totalEdges still counts unbounded. 121 test files / 1330 tests pass. |
||
|
|
9a4bdbe0e8 |
feat(diag): smart-search followup-rate proxy + reader-failure counter (#786)
* feat(diag): smart-search followup-rate proxy + reader-failure counter Closes #771. Disambiguates 'retrieval bug' vs 'reader bug'. Today every retrieval regression looks identical to a reader-behavior regression because we have no signal for 'the right memory was in the context but the agent didn't use it'. This change ships the live-use proxy half end-to-end plus the placeholder counter the benchmark scorer will increment. Live-use proxy -------------- mem::smart-search now detects follow-up searches: when an agent-initiated call (sessionId present, source !== 'viewer') is followed by another search from the same session inside AGENTMEMORY_FOLLOWUP_WINDOW_SECONDS (default 30s) AND the new result set has zero overlap with the prior one AND the queries differ, we count it as a directional signal that the first results didn't satisfy the agent. - New KV scope mem:recent-searches holds the most recent search per session: { sessionId, query, resultIds, at }. sessionId is duplicated into the row so the sweep can delete by it (StateKV.list returns values only). - mem::diagnostic::recent-searches-sweep runs hourly, drops rows older than 24h. - OTEL counter agentmemory.smart_search.followup_within_window_total fires on every detected follow-up. Mirrored to a module-scope counter for read-back without an OTEL collector. - mem::diagnostic::followup-stats exposes the live counts; GET /agentmemory/diagnostics/followup returns them with a 'directional / may overcount on refinement' caveat. - agentmemory status surfaces 'Followup rate: H/T (P%) within Ws'. Benchmark-mode counter ---------------------- Counter agentmemory.reader_failure_with_evidence_total is registered but never incremented by core in live use. The benchmark scorer (separate from this PR) increments it when judge_correct === false AND the gold-evidence IDs are a subset of the retrieved-context IDs. Exclusions ---------- - Viewer-originated searches (X-Agentmemory-Source: viewer or body field source: 'viewer') skip the diagnostic entirely. The API trigger reads the header and threads it into the smart-search payload; the body field wins if both are set. - Direct sdk.trigger callers without a sessionId are skipped. - Identical re-query is a retry, not a follow-up (skipped). - Result-set overlap means the prior results were used (skipped). Edge cases covered by tests --------------------------- - First call records but never flags. - Disjoint results within window: flagged. - Overlapping results within window: not flagged. - Identical query (retry): not flagged. - Prior search outside window: not flagged. - Viewer source: skipped, scope unchanged so subsequent agent calls don't see the viewer search as 'prior'. - No sessionId: skipped. - Sweep deletes stale (>24h) rows, keeps fresh ones. Endpoint count bumped 125 -> 126 across README / AGENTS / index banner. * fix(diag): whitelist smart-search payload, off-critical-path followup detection Address CodeRabbit review on PR #786. src/triggers/api.ts: api::smart-search was spreading req.body into the sdk.trigger payload, which violates the AGENTS.md security rule that REST endpoints must whitelist fields. Replace the spread with an explicit field list (query, expandIds, limit, project, includeLessons, agentId, sessionId, source). Unknown fields from the client are now dropped before reaching mem::smart-search. src/functions/smart-search.ts: followup detection used to await kv.get + kv.set on the critical response path, adding one round-trip of state I/O per agent-initiated search. Move detection into a withKeyedLock('recent-searches:<sessionId>', ...) call that runs fire-and-forget. The lock serializes per session so the next call's detectFollowup still reads the prior call's row (lock body queues behind), and other sessions run in parallel. flushPendingFollowups() exposes a drain hook for tests and graceful-shutdown paths. Empty-result calls now skip detection entirely. A search that returns zero hits is a retrieval failure, not a reader-failure signal — the old code treated the empty-vs-prior comparison as 'disjoint' and would inflate the rate every time search returned nothing. src/state/schema.ts unchanged. RecentSearch now exported from smart-search.ts and consumed by recent-searches-sweep.ts so the row shape stays consistent across producer and sweeper. Audit logging on every recent-searches write was suggested but skipped — high-frequency diagnostic writes don't belong in the audit log (analogous flows like recordAccessBatch and dedup don't audit either; it would 10x audit volume without operator-actionable value). Tests: added an empty-results case (sub-point b) that asserts the empty-skip guard. Mock sdk.trigger now flushes pending followups after each smart-search call so test assertions see consistent state. 122 test files / 1336 tests pass. |
||
|
|
fc068bee66 |
fix(agent-sdk): scope recursion guard to AsyncLocalStorage, memoize SDK import (#782)
* fix(agent-sdk): scope recursion guard to AsyncLocalStorage, memoize SDK import Reported in #781. mem::summarize fails with 'too_many_chunks_skipped: 4/4 chunks failed to parse after retry' on the agent-sdk provider whenever a session is large enough to split into >=2 chunks. Root cause: the recursion guard from #181 mutated process.env globally: if (process.env.AGENTMEMORY_SDK_CHILD === '1') return '' process.env.AGENTMEMORY_SDK_CHILD = '1' // ...await SDK... // restore #472 then introduced chunked summarize that runs chunks concurrently via Promise.all (SUMMARIZE_CHUNK_CONCURRENCY=6 by default). The first chunk in a batch flips the env synchronously before its first await; siblings in the same batch then enter query(), see the flag, and return ''. An empty string has no <title>, parseSummaryXml returns null, the chunk is counted as skipped, >50% skip ratio throws too_many_chunks_skipped. The guard (cross-process) and the chunk concurrency (in-process) were never reconciled. Each needs the right primitive: - In-process recursion guard: AsyncLocalStorage. Scoped to the async call tree of the SDK query, so concurrent siblings have separate ALS frames and no longer see each other's marker. The check now reads sdkChildContext.getStore() instead of process.env. - Cross-process recursion guard for hook scripts: still process.env.AGENTMEMORY_SDK_CHILD = '1' around the SDK call. Subprocesses spawned by @anthropic-ai/claude-agent-sdk inherit process.env at spawn time; hook scripts (separate processes) still read the env to short-circuit their REST callbacks. The race on the env between concurrent in-process siblings is benign because every sibling wants the same value '1' during its own SDK call. Also memoize the dynamic import of @anthropic-ai/claude-agent-sdk on the provider instance. Production benefit: concurrent callers share one module resolution. Test benefit: vi.mock factories apply uniformly across concurrent imports instead of racing between mock and real module references. Tests cover: - 4 concurrent summarize calls each return the real SDK result (no empty siblings) — the direct #781 regression - Mixed concurrent summarize + compress on the same provider - AGENTMEMORY_SDK_CHILD is set to '1' during the SDK call (child process inheritance) and restored to the prior value on exit - Genuine re-entry inside the same async tree still degrades to '' so the #149 / #181 stop-hook -> summarize recursion stays broken Closes #781. * fix(agent-sdk): refcount env marker + vi.hoisted mock state Address CodeRabbit findings on PR #782. agent-sdk.ts: the per-call prev snapshot races across overlapping calls. Trace: - A enters: prev=undef, env='1' - B enters: prev='1' (already set by A), env='1' - A finishes first, restores prev=undef -> env=undef WHILE B is still mid-flight; any child process B spawns now won't inherit the marker - B finishes, restores prev='1' -> env='1' leaks past the last caller Replace with module-level reference counting: let sdkActiveCount = 0 let sdkOriginalEnv: string | undefined if (sdkActiveCount === 0) { sdkOriginalEnv = process.env.AGENTMEMORY_SDK_CHILD process.env.AGENTMEMORY_SDK_CHILD = '1' } sdkActiveCount++ try { ...SDK call... } finally { sdkActiveCount-- if (sdkActiveCount === 0) { // restore sdkOriginalEnv (delete if undefined) } } Only the first entrant snapshots the original; only the last exit restores it. Env stays '1' for the full overlap. test/agent-sdk-provider.test.ts: vi.mock factories are hoisted above const/let bindings, so the closure can't safely reference non-hoisted module-scope state. Move queryCalls and mockResult into a vi.hoisted state object so the mock factory and tests share an explicitly hoisted reference instead of a closure capture. Also fixed a test-mock bug while adding the overlap regression: the mock yielded the unwrapped Promise from a function-shaped mockResult. The gen now awaits the mockResult inside the generator so async mock results are unwrapped before yielding. New regression test: 3 concurrent calls each observe AGENTMEMORY_SDK_CHILD='1' at enter and exit phases for the full overlap, and env is restored to undefined only after the last call finishes. 122 test files / 1332 tests pass. |
||
|
|
de95403e42 |
fix(obsidian-export): harden against null fields + never throw to HTTP layer (#780)
Reported in #729. mem::obsidian-export crashed with HTTP 500 '{"error":"[object Object]"}' when any session/memory/lesson/crystal in the store was missing its id, and zero files were written. The filename was built via sanitize(X.id) before the per-record try block, so undefined.replace(...) escaped the handler entirely. The same unguarded pattern applied to memories, lessons, crystals, and sessions; relatedIds/concepts/tags/keyOutcomes/etc. assumed non-null arrays; title/content/narrative assumed non-empty strings; sessions sorted on new Date(undefined).getTime() = NaN. This change applies four layers of defense: 1. hasExportId() typeguard filters every per-tier loop, so id-less records are skipped instead of crashing. The filter runs before sanitize() so the cast to string is safe. 2. safeArray / safeString / safeTimestamp helpers normalize optional record fields. Missing arrays render as omitted sections instead of crashing on .map / .length / .slice. Missing titles / content / narrative fall back to the record id so the export file is still useful. 3. An outer try/catch wraps the whole export body and returns {success: false, error: <string>} on any unexpected throw. The HTTP layer's serializer never sees an Error object now, killing the '[object Object]' symptom even if a future refactor reintroduces an unguarded path. 4. Sessions sort with safeTimestamp(), so malformed startedAt values (undefined / not-a-date) yield 0 instead of NaN and don't poison the ordering. Tests cover each null path: - id-less records across all four tiers - malformed startedAt timestamps - null optional array fields (concepts/files/relatedIds/supersedes) - missing title / content / narrative (id fallback in heading) - engine-level throws turn into {success: false, error: <string>} 121 test files / 1331 tests pass. Closes #729. |
||
|
|
91c78e759f |
chore(deps): bundle Dependabot bumps + clear all 8 audit vulns (#779)
Bundles the two open Dependabot PRs (#497 root, #760 website), drops the iii-sdk bump per current support window, and pins transitive overrides so 'npm audit' is clean. Root (package.json) - @anthropic-ai/sdk ^0.93.0 -> ^0.100.1 (minor) - tsdown ^0.20.3 -> ^0.21.10 (minor; 0.22.x bumps node engine to >=22.18 which would break our engines.node = >=20) - @types/node ^25.7.0 -> ^25.9.1 (dev, patch) - iii-sdk 0.11.2 unchanged (engine pin stays per current support; 0.17.x removes triggerVoid + has API drift) Transitive vuln pins (npm overrides) - qs >=6.15.2 (GHSA-q8mj-m7cp-5q26, DoS) - ws >=8.21.0 (GHSA-58qx-3vcg-4xpx, uninitialized memory disclosure) - protobufjs >=7.5.8 (GHSA-xq3m-2v4x-88gg + 8 others — pulled in transitively via the optional @xenova/transformers/onnxruntime-web/onnx-proto chain; 'npm audit fix --force' would downgrade @xenova/transformers to 2.0.1, the override avoids that) Website (website/package.json) - next ^16.2.6 -> ^16.2.7 (patch) - react / react-dom ^19.2.6 -> ^19.2.7 (patch) - @types/node ^25.7.0 -> ^25.9.1 (dev) - @types/react ^19.2.14 -> ^19.2.16 (dev) Lockfile hygiene - Untrack website/package-lock.json (was the only tracked lockfile; conflicts with the repo's existing no-lockfile rule in the root .gitignore) - Add package-lock.json / pnpm-lock.yaml / yarn.lock to website/.gitignore to match the root convention Results - 'npm audit' (root): 8 -> 0 vulnerabilities - 'npm audit' (website): 0 -> 0 vulnerabilities - 'npm install': no EBADENGINE warnings on node 20.x - 'npm run build': clean - 'npm test': 120 files / 1293 tests passing - 'cd website && npm run build': static generation completes for all 5 routes |
||
|
|
f40631e492 |
fix(viewer): allow non-loopback bind via AGENTMEMORY_VIEWER_HOST (#442)
* fix(viewer): allow non-loopback bind via AGENTMEMORY_VIEWER_HOST `startViewerServer` hardcoded `server.listen(port, "127.0.0.1")`, so Fly's `fly-local-6pn` traffic was RST and `fly proxy 3113:3113` reported "Recv failure: Connection reset by peer" (#434). Bind host is now read from `AGENTMEMORY_VIEWER_HOST` (default `127.0.0.1`). To keep the viewer's bearer-authorized proxy from silently exposing itself on non-loopback binds, the same code path now: - drops loopback hostnames from the default Host allowlist when bind is non-loopback; - refuses to start unless both `VIEWER_ALLOWED_HOSTS` and `AGENTMEMORY_SECRET` are set in that mode; - requires `Authorization: Bearer $AGENTMEMORY_SECRET` (timing-safe compare from src/auth.ts) on every /agentmemory/* request; HTML and the favicon stay open. `deploy/fly/Dockerfile` no longer bakes `AGENTMEMORY_VIEWER_HOST=::`. The entrypoint exports it (and a `VIEWER_ALLOWED_HOSTS` default scoped to the Host headers `fly proxy` emits) only when `FLY_APP_NAME` or `FLY_ALLOC_ID` is present, so a `docker run -p 3113:3113` of the image elsewhere stays loopback-only. Tests: 29 cases in test/viewer-host.test.ts. * fix(viewer): secure Fly proxy access * fix(viewer): address CodeRabbit review on PR #442 - Add aria-label + aria-describedby to the unlock input so screen readers announce 'AGENTMEMORY_SECRET' instead of relying on placeholder text alone (major a11y gap on the only path through the auth gate). - Call fetchFlags() after a successful unlock so footer version + flag banners refresh without a full page reload; bootstrap-only fetch left the UI stale when config/flags was the first request that 401'd. - Log the actual bound port in the listening handler. server.listen(0) callers (tests + ephemeral-port users) saw 'http://localhost:0' in the startup log because currentPort stayed 0 while server.address() already exposed the real port. - Cache the favicon buffer at module load instead of doing a synchronous readFileSync on every /favicon.svg request. - README: 'preseeds' -> 'pre-seeds'. - Clarify the VIEWER_ALLOWED_HOSTS cache comment: readAllowedHostsOverride is called per request, but buildAllowedHosts caches the result for the lifetime of the listener — production env changes after the first request require a restart. --------- Co-authored-by: Rohit Ghumare <ghumare64@gmail.com> |
||
|
|
4b593e04d4 |
fix(cli): install pinned iii to private dir, fall back on PATH mismatch (#774)
* fix(cli): install pinned iii to private dir, fall back on PATH mismatch Reported in #752: fresh global install of @agentmemory/agentmemory on a box that already has iii-engine v0.16.1 on PATH refuses to boot because agentmemory hard-pins v0.11.2. The downgrade hint tells the user to overwrite their global iii install with v0.11.2, but the v0.11.2 release ships only the 'iii' binary so any consumer of 'iii-init' / 'iii-worker' breaks. AGENTMEMORY_III_VERSION=0.16.1 boots cleanly per the reporter, but agentmemory's pin doesn't follow. This change isolates agentmemory's pinned engine from the user's PATH. Install location: ~/.agentmemory/bin/iii (was: ~/.local/bin/iii) Fallback order: private -> PATH -> ~/.local/bin -> /usr/local/bin PATH mismatch: auto-install pinned to private (was: exit 1) Help / prompts: point at the new private path Behavior: - pickCompatibleIii walks candidate iii paths and returns the first one whose --version matches IIPINNED_VERSION, or null if none match. - When PATH iii mismatches the pin AND the private install doesn't exist, startEngine auto-installs to ~/.agentmemory/bin without prompting (the user's existing iii stays untouched). - The 'attach to running engine' code still hard-fails if the running engine reports a wrong version — we can't reinstall under it without killing it. The error message now points at the private-install fix path instead of a manual curl. - agentmemory remove now plans cleanup of BOTH ~/.agentmemory/bin/iii (always safe, agentmemory owns it) and ~/.local/bin/iii (legacy, version-gated so a user-managed install isn't deleted). iii-sdk and engine version pins both stay at 0.11.2. Closes #752. * fix(cli): address review findings on iii pin resolution - Attach path: read launched iii bin from engine-state.json before falling back to whichBinary/fallbackIiiPaths. Persist binPath in writeEngineState. Avoids false failures when PATH iii changed after the engine was started. - resolveCompatibleIii: a failed iiiBinVersion probe (null) was treated as compatible. Require a positive match (detected === IIPINNED_VERSION) so unreadable / chmod-broken / crashed binaries route through the fallback + reinstall path instead of being silently trusted. - remove-plan path helpers: append .exe on Windows. legacyLocalBinIii and privateIiiBin hard-coded 'iii' so existsSync probes missed the install on win32. - probeLocalBinIiiVersion now reads legacyLocalBinIii(home) instead of the alias localBinIii (= privateIiiBin). The 'legacy version matches pin?' gate in buildRemovePlan was checking the private bin (which agentmemory always installs at the pin), so the legacy entry would render with the wrong description / alwaysAsk flag. |
||
|
|
a35de80d65 |
fix(sdk): replace removed triggerVoid with trigger + TriggerAction.Void (#773)
iii-sdk 0.11.2 removed sdk.triggerVoid in favor of
sdk.trigger({ function_id, payload, action: TriggerAction.Void() }).
Nine call sites were still using the removed API:
src/triggers/api.ts 1 site (try/catch wrapped)
src/triggers/events.ts 2 sites (try/catch wrapped)
src/functions/observe.ts 3 sites
src/functions/image-quota-cleanup.ts 1 site
src/functions/image-refs.ts 1 site
src/functions/disk-size-manager.ts 1 site
Six of the nine ran without try/catch and threw TypeError on every
call, breaking image lifecycle, disk-quota cleanup, vision embeddings,
and observation write paths.
Tests updated to spy on sdk.trigger with TriggerAction.Void payloads
instead of the removed triggerVoid spy.
|
||
|
|
52e5f3f5f1 |
fix(pi): send tool_input/tool_output instead of input/output (#772)
pi integration emitted post_tool_use observations with fields named 'input' and 'output', but observe.ts reads 'tool_input'/'tool_output'. The field mismatch caused observe to record undefined inputs/outputs, so the compress pipeline produced empty observations rendered as 'No content provided' in the viewer. |
||
|
|
b7d6b68b2a |
perf: write index shards in parallel
Signed-off-by: Rokurolize <1701388+Rokurolize@users.noreply.github.com> |