mirror of
https://github.com/rohitg00/agentmemory.git
synced 2026-09-14 20:16:33 +08:00
v0.9.19
131 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bb259acedc |
chore(release): v0.9.19 — route around upstream iii-console installer bug
`install.iii.dev/console/main/install.sh` filters releases with
`startswith("v")` while `iii-hq/iii` tags as `iii/v0.12.0` —
slash-prefixed. Every stable release is filtered out and the script
bails with "no stable iii release found", breaking first-run console
install for every fresh agentmemory user.
Switch our prompt to pass `--next` through the installer. The next-
release lookup uses a regex on `-next.` without the buggy
`startswith` constraint, so it succeeds against the same tag set and
installs the most recent prerelease. Inline comment documents the
upstream tag-prefix bug + the revert condition for when the
installer is patched.
Files bumped (9):
- package.json
- packages/mcp/package.json
- plugin/.claude-plugin/plugin.json
- plugin/.codex-plugin/plugin.json
- src/version.ts
- src/types.ts
- src/functions/export-import.ts
- test/export-import.test.ts
- CHANGELOG.md
1007/1007 tests pass.
|
||
|
|
caa9f5223e |
fix(plugin): inherit AGENTMEMORY_URL/SECRET via env passthrough (#375) (#460)
The bundled plugin .mcp.json + the `agentmemory connect <agent>` wiring
both hardcoded the MCP server to localhost:3111 with no env-var seam.
Users running agentmemory remotely (k8s cluster-internal DNS, reverse
proxy with API key auth) hit a permanent /doctor warning because they
had to add a second entry with the right AGENTMEMORY_URL + SECRET, and
both entries fought for the "agentmemory" name.
Both files now use ${AGENTMEMORY_URL} / ${AGENTMEMORY_SECRET}
expansion. Host (Claude Code, Cursor, etc.) substitutes the shell
value at MCP-server launch; when vars are unset the host passes empty
string, which the standalone shim treats as missing and falls back to
http://localhost:3111. One wired entry now covers both local and
remote without duplicates.
- plugin/.mcp.json: env block added with ${VAR} expansion
- src/cli/connect/util.ts: AGENTMEMORY_MCP_BLOCK env updated; comment
documents the empty-string fallback contract
- test/cli-connect.test.ts: regression test asserting the env block
shape after install() — protects against future regressions where
the literal hardcoded URL leaks back in
1008/1008 tests pass.
|
||
|
|
abeec1d0b7 |
fix: end Codex sessions on Stop hook (#495)
Signed-off-by: Nanami <Rex57@users.noreply.github.com> Co-authored-by: Nanami <Rex57@users.noreply.github.com> |
||
|
|
06248fd199 |
refactor(providers): extract OpenAI transport into shared module (#371) (#462)
* refactor(providers): extract OpenAI transport into shared module (#371) OpenAIProvider (LLM) and OpenAIEmbeddingProvider (embedding) both speak the OpenAI wire shape over POST /v1/chat/completions and POST /v1/embeddings — and both need to swap to the Azure shape when the base URL lands at *.openai.azure.com (api-key header instead of Authorization: Bearer, api-version query param, no /v1/ prefix because the deployment is already in the URL). Until now only the LLM provider knew about Azure. The embedding provider hardcoded /v1/embeddings + Bearer auth, so any user pointing OPENAI_BASE_URL at an Azure resource saw their LLM calls route correctly while embedding calls 404'd / 401'd. New src/providers/_openai-shared.ts owns: detectAzure(baseUrl) buildChatUrl(baseUrl, isAzure, apiVersion) buildEmbeddingUrl(baseUrl, isAzure, apiVersion) buildAuthHeaders(apiKey, isAzure) normalizeBaseUrl(raw) Both classes import these helpers. The embedding provider gains Azure auto-detection via the same OPENAI_BASE_URL hostname check the LLM provider already uses, plus the same OPENAI_API_VERSION knob (default 2024-08-01-preview). Closes the gap users hit when mirroring an Azure deployment on both surfaces. Net delta: ~40 LOC of duplicated transport collapsed into one shared file; both provider files are smaller and focused on their surface's unique concerns (LLM owns max_tokens / reasoning_effort / timeout precedence; embedding owns the MODEL_DIMENSIONS table). Approach diverges from the issue body (single class implementing both interfaces): the existing factory contracts at src/providers/index.ts dispatch LLM and embedding independently (detectProvider vs detectEmbeddingProvider), and merging into one class would force constructors to coordinate detection across two config surfaces. Shared transport keeps factories clean while delivering the DRY win and the Azure-on-embeddings parity gain. - src/providers/_openai-shared.ts: new shared transport module - src/providers/openai.ts: import shared helpers; drop local detectAzure + private buildUrl/buildHeaders - src/providers/embedding/openai.ts: import shared helpers; Azure auto-detection via OPENAI_BASE_URL host check, OPENAI_API_VERSION query param when Azure-shaped - test/openai-shared.test.ts: 17 cases covering all four helpers plus end-to-end embedding Azure shape (asserts URL path + api-key header substitution via mocked fetch) 1023/1023 tests pass. * fix(providers): use URL API for Azure path composition (review) Inline review on #462 surfaced an edge-case bug in the azureUrl helper: it tested baseUrl for an existing query string to pick the ?/& separator, then string-concatenated the route path BEFORE that separator. If a baseUrl arrived with pre-existing query params (corporate proxy carrying a tenant token, diagnostics endpoint, etc.), the route path got interpolated into the query string, producing a malformed URL like: baseUrl: https://proxy.example.com/openai/deployments/d?tenant=acme result: https://proxy.example.com/openai/deployments/d?tenant=acme/chat/completions&api-version=2024-08-01-preview ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in the query, not the path Switch to the URL API: parse baseUrl, normalise the existing pathname (strip trailing slashes), join the route path with a single boundary slash, set api-version via url.searchParams.set (handles encoding without a manual encodeURIComponent), then toString. Existing query params are preserved. Test header normalisation pass: the OpenAIEmbeddingProvider Azure-detection tests captured RequestInit.headers as Record<string,string>. buildAuthHeaders happens to return a plain record today, but a future contributor migrating to HeadersInit/Headers would have these tests silently pass-but-stale because Record-style access would no longer match. Wrap the capture in new Headers(init?.headers) so the assertions go through .get() and tolerate any of the three valid HeadersInit shapes. Skipped — replacing DEFAULT_AZURE_API_VERSION with /openai/v1: the v1 Azure pattern is a recent addition and many deployments still require the api-version query param. Switching the default would break those deployments. Separate opt-in flag (e.g. OPENAI_AZURE_API_STYLE=v1) is the right shape but out of scope for this minimal-change review pass. - src/providers/_openai-shared.ts: azureUrl rewritten on URL API - test/openai-shared.test.ts: two new cases (query-param preservation, trailing-slash normalisation); embedding Azure tests switched to Headers API + .get() / .toBeNull() 1025/1025 tests pass. * feat(providers): support Azure OpenAI v1 URL pattern (review) Azure shipped a v1 GA endpoint pattern that mirrors the OpenAI wire shape one-for-one: POST https://<resource>.openai.azure.com/openai/v1/chat/completions No api-version query param, no /deployments/<dep> path segment — deployment name is passed in the request body as `model`. Existing OpenAI clients work as drop-in. The legacy URL pattern (/openai/deployments/<dep>/chat/completions?api-version=<date>) is still supported by Azure indefinitely for back-compat. Verification of the inline review's "remove DEFAULT_AZURE_API_VERSION and migrate all Azure calls to /openai/v1" instruction: removing it flat would break legacy users mid-flight (their existing OPENAI_BASE_URL=https://r.openai.azure.com/openai/deployments/d configs would still route through the legacy path and 400 without api-version). Hybrid auto-detect off the URL shape is the right shape: - baseUrl pathname contains `/openai/deployments/` -> legacy style, api-version still appended (DEFAULT_AZURE_API_VERSION fallback) - otherwise (bare resource host, /openai prefix, /openai/v1 prefix) -> v1 style, no api-version, route through /openai/v1/<path> Users migrate by stripping the /openai/deployments/<dep> suffix from their OPENAI_BASE_URL — no env-var changes required. azureStyleOf(baseUrl) is the new detection helper. v1AzureUrl preserves any pre-existing query params on the base URL (corporate proxy diagnostics tokens) but never appends api-version. The /openai/v1 prefix is normalised so configurations like OPENAI_BASE_URL=https://r.openai.azure.com/openai or .../openai/v1 don't double-prefix. - src/providers/_openai-shared.ts: azureStyleOf + v1AzureUrl; legacyAzureUrl (renamed from azureUrl) is unchanged; module header comment documents both styles and the migration path - test/openai-shared.test.ts: 3 new cases — v1 routing on bare host, /openai or /openai/v1 prefix normalisation, v1 on the embedding surface as well; legacy assertion updated to say "Azure legacy" so the contrast with v1 is explicit 1028/1028 tests pass. |
||
|
|
249d8ff032 |
feat(commits): link agent sessions to git commits (#498)
* feat(commits): link agent sessions to git commits
Adds a commit-link layer so memories captured during an agent session
can be traced back to the git commit they produced, and vice versa.
What ships:
- KV.commits namespace storing CommitLink records keyed by full SHA.
- Session.commitShas[] back-reference for fast forward lookup.
- POST /agentmemory/session/commit upserts a link (sha, branch, repo,
message, author, authoredAt, files, sessionId); merges sessionIds on
re-link and preserves linkedAt.
- GET /agentmemory/session/by-commit?sha= hydrates the link plus its
sessions.
- GET /agentmemory/commits?branch=&repo=&limit= lists recent links,
filtered and sorted desc by linkedAt, limit clamp 1..500 (default 100).
- src/hooks/post-commit.ts shells git rev-parse/log/diff-tree and POSTs
to /agentmemory/session/commit. Honors AGENTMEMORY_URL,
AGENTMEMORY_SECRET, AGENTMEMORY_CWD, AGENTMEMORY_SESSION_ID,
AGENTMEMORY_COMMIT_SHA. Best-effort, 1500 ms timeout. Wired into
tsdown hookEntries so it bundles to dist/hooks/ and plugin/scripts/.
- Two MCP tools: memory_commit_lookup, memory_commits.
- Four user-invocable plugin skills:
- commit-context: traces a file/function/line via git blame plus
memory_commit_lookup plus memory_recall.
- commit-history: lists agent-linked commits; parses branch=/repo=/
limit= from $ARGUMENTS.
- handoff: resumes the most recent session for cwd; surfaces an
unanswered user-facing question before the brief.
- recap: summarizes the last N sessions for cwd, grouped by date;
parses last <n> / today / this week.
Wire-up to capture from a real repo:
ln -sf "$(realpath node_modules/agentmemory/dist/hooks/post-commit.mjs)" .git/hooks/post-commit
chmod +x .git/hooks/post-commit
No new dependencies. Additive only.
* fix(commits): address CI + review findings on commit-link feature
- Update mcp-standalone.test.ts to expect 14 CORE_TOOLS (was 12) now that
memory_commit_lookup and memory_commits are registered. Restores CI on
Node 20 and 22.
- Wrap CommitLink upsert and session.commitShas mutation in withKeyedLock
to prevent lost updates when concurrent post-commit hooks race against
each other or against api::session::commit callers. Locks keyed by
`commit:<sha>` and `session:<sessionId>` so unrelated writers do not
serialize.
- Parallelize hydration of linked sessions via Promise.all in both
api::session::by-commit and the memory_commit_lookup MCP handler;
serial kv.get loops dragged response time for commits with many
sessions.
- Sharpen handoff SKILL.md project-path matching: resolve relative
$ARGUMENTS to an absolute normalized path and require directory-
boundary equality, not raw string prefix, so sibling repos sharing a
prefix do not collide.
- Sharpen commit-history SKILL.md HTTP fallback: build the
/agentmemory/commits URL with URL-encoded query values so branch and
repo values containing `?`, `&`, or `#` cannot corrupt the request.
* chore(consistency): bump documented tool and endpoint counts
Bring README.md, AGENTS.md, and the boot log in src/index.ts in line
with the live tool registry (53 MCP tools, +2 from this branch) and
the live REST registration (124 endpoints, +3 from this branch).
The Consistency test in test/consistency.test.ts derives both counts
from source and asserts the markdown matches; this commit only updates
the literal numbers, no behavior change.
|
||
|
|
4db303b6b9 |
perf(graph): swap BFS for Dijkstra over edge-weighted graph (#328) (#463)
* perf(graph): swap BFS for Dijkstra over edge-weighted graph (#328) Memory graph edges carry weights from 0.1 to 1.0 (encoding relation strength). BFS visits nodes in edge-count order regardless of weight, so a one-hop weak edge ranked the same as a two-hop chain of strong edges to the same node — which is the wrong semantics for the weighted graph the retrieval surface actually queries. Replace bfsTraversal with dijkstraTraversal: cost(edge) = 1 / max(edge.weight, 0.01) Higher-weight edges cost less, so Dijkstra prefers strong-edge paths. The clamp floor at 0.01 protects against malformed edges with weight=0 dividing by zero (the documented weight floor is 0.1, but imports / older snapshots may carry zeros). Same swap tightens the perf profile: - Adjacency built once in O(V+E) (was: filter allEdges per visited node, O(V·E) overall). - Min-heap dequeue is O(log V) per pop (was: Array.shift() which is O(n) — the dominant cost on graphs above ~200 nodes per the contributor's benchmark linked in #328). Min-heap is a 50-LOC inline class so the file doesn't take a new dependency for the perf-critical inner loop. Semantics preserved: - maxDepth still bounds edge-count, not accumulated cost. So a deep chain of strong edges still terminates at maxDepth hops. - One path per reachable node within depth (matches what the existing scoring loop consumes). - score = avgWeight × (1/pathLength) formula unchanged. - src/functions/graph-retrieval.ts: dijkstraTraversal + MinHeap - test/graph-retrieval.test.ts: 4 new cases — weight-optimal path selection (the core Dijkstra promise), disconnected-node safety, near-zero-weight clamp, maxDepth bound preserved 1011/1011 tests pass. * fix(graph): exclude startNode from Dijkstra returned paths (review) Inline review on #463 surfaced a pre-existing bug carried forward through the BFS-to-Dijkstra refactor: the returned path list included the startNode's own length-1 path, so the generic path-scoring loop in searchByEntities ran first — pathLength=1 with no edges → avgWeight fell to 0.5 (empty-edgeWeights fallback) → score=0.5 → startNode obs marked visited. The dedicated score=1.0 fallback loop for startNode obs was then skipped by the visitedObs guard, making it dead code. So startNode observations were silently scoring 0.5 instead of the intended 1.0. Drop the startNode entry from pathTo before returning. The caller's fallback loop now fires as designed. - src/functions/graph-retrieval.ts: pathTo.delete(startNode.id) immediately before Array.from(pathTo.values()); inline comment explains why the fallback would otherwise be dead. - test/graph-retrieval.test.ts: regression asserts obs_root carries score=1.0 and pathLength=0 when its node is the matched start. 1012/1012 tests pass. |
||
|
|
9061da56d5 |
chore(release): v0.9.18 (#459)
Hardening + DX wave. Bundles the four follow-up issues filed during the v0.9.17 audit (#446 timeout env unify, #447 viewer CSP tighten, #448 fs-watcher PEM/JWT redaction, #449 mcp-standalone probe DI flake) plus the lessons-auto-inject gap surfaced in discussion #381 / fixed in #457. 1007/1007 tests pass. Files bumped: - package.json - packages/mcp/package.json - plugin/.claude-plugin/plugin.json - plugin/.codex-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) - CHANGELOG.md (new section) |
||
|
|
a557e34f39 |
fix(context): auto-inject lessons into mem::context payload (#457) (#458)
Lessons were generated (via mem::lesson-save / mem::reflect) and stored in the lessons KV scope, but never folded into the auto-inject payload the session-start hook prepends to the first turn. Agents only saw lessons if they explicitly called memory_lesson_recall — which they rarely did unprompted. Half the loop: lessons learned, lessons never reused. mem::context now reads lessons KV alongside slots + profile, filters out tombstoned entries + cross-project leakage, ranks project-scoped lessons above globals (1.5x boost) then by confidence, caps at 10, and emits a "## Lessons Learned" block sized like every other context block so the outer token-budget loop drops it cleanly when tight. Surfaced in #381 (@rockarxiv asked where lessons get used — answer was "nowhere, automatically"). Filed as #457, fixed here. - src/functions/context.ts: parallel-fetch lessons with slots + profile, render block after profile and before sessions - test/context-lessons.test.ts: 8 cases covering inclusion, empty state, project ranking, cross-project isolation, deleted-skip, top-N cap, confidence rendering, optional context-string append 1007/1007 tests pass. |
||
|
|
49377db0db |
fix(providers): unify OPENAI_TIMEOUT_MS + AGENTMEMORY_LLM_TIMEOUT_MS (#446) (#453)
* fix(providers): unify OPENAI_TIMEOUT_MS + AGENTMEMORY_LLM_TIMEOUT_MS (#446) v0.9.17 shipped OPENAI_TIMEOUT_MS scoped to the OpenAI LLM provider (inline AbortController, default 60s). PR #379 then shipped AGENTMEMORY_LLM_TIMEOUT_MS in the shared src/providers/_fetch.ts helper used by every other raw-fetch provider (Gemini, OpenRouter, MiniMax, OpenAI/Cohere/Voyage/OpenRouter embedding). Two env vars, same value, different names — ops confusion. Unify on the global name while keeping back-compat: 1. OPENAI_TIMEOUT_MS — OpenAI-scoped alias, takes precedence 2. AGENTMEMORY_LLM_TIMEOUT_MS — global fall-back across providers 3. 60 000 ms default The OpenAI LLM provider now routes through the shared fetchWithTimeout helper, dropping ~30 lines of duplicate AbortController + clearTimeout plumbing. Existing users with OPENAI_TIMEOUT_MS set keep the exact v0.9.17 behaviour; new users setting AGENTMEMORY_LLM_TIMEOUT_MS get the OpenAI LLM path covered too. README + .env.example now document AGENTMEMORY_LLM_TIMEOUT_MS as the canonical name and note OPENAI_TIMEOUT_MS as the OpenAI-scoped alias. 4 new precedence tests in test/fetch-timeout.test.ts cover all four env-var combinations. * test(providers): strict parse for OPENAI_TIMEOUT_MS env (CodeRabbit) CodeRabbit caught parseInt("30ms", 10) silently returning 30 in the timeout-resolve path. Real users hitting this would think they bound the call to 30ms when the regex would have rejected it. parsePositiveInt now rejects anything that isn't pure digits via /^\d+$/ (after trim). parseInt's lenience on trailing units / underscores / signs is gone — those fall back to the 60s default instead of masquerading as an aggressive bound. New regression test covers "30ms", "1_000", "60s", "30abc", "-30", "0". Whitespace padding (e.g. " 30 ") is still accepted — that's normal env-var handling. 992/992 tests pass on the worktree. |
||
|
|
00df540c87 |
fix(fs-watcher): redact PEM private-key blocks and standalone JWTs in previews (#448) (#450)
PR #332 covered KEY=value and Bearer-prefixed tokens. This extends the filesystem watcher's preview pipeline to also collapse multi-line and inline PEM private-key blocks (RSA / DSA / EC / OPENSSH / ENCRYPTED / generic) into a single REDACTED line bracketed by the BEGIN/END markers, and to redact standalone JWT-looking strings outside Bearer context. JWT redaction requires a three-segment dot pattern AND total length >= 100 chars to avoid matching incidental base64-shaped words. Closes #448 |
||
|
|
92e5c885fc |
fix(mcp): inject livez probe to kill mcp-standalone test flake (#449) (#451)
The MCP standalone shim's REST proxy probed `:3111/agentmemory/livez` via a hard-coded `fetch()` with a 2s AbortController timeout. In CI, vitest's mock setup raced the in-flight probe — tests randomly failed on call counts and response-shape assertions depending on whether the probe completed before each test's own deadline. This file has been the source of every "10-11 pre-existing failures unrelated to this PR" footnote for the last five releases. Add a `setLivezProbe(fn)` module-level seam in `src/mcp/rest-proxy.ts` (option A in the issue). Default stays the real fetch-based probe — wire behaviour is unchanged in production. `resetHandleForTests()` also resets the probe so tests can't accidentally leak a stub across files. In `test/mcp-standalone.test.ts`, install an instant `ok:false` probe in `beforeEach` so the shim always takes the deterministic InMemoryKV fallback path. Replace `globalThis.fetch` with a trap that throws if hit — any regression that bypasses the seam fails loudly instead of timing out silently and re-introducing the flake. Validation: 10/10 consecutive runs green (~185ms each, down from 2.2s); proxy test file unaffected (14/14); full suite 988/988 stable across 3 runs. |
||
|
|
57c785f4db |
fix(viewer): self-host SVG favicon + restore tight img-src CSP (#447) (#452)
#313 added `data:` to the viewer img-src so an inline-SVG favicon encoded as `data:image/svg+xml,...` could load. Bare `data:` allows arbitrary data-URI image types (data:image/png;base64,..., etc), which the viewer does not need. Self-host the favicon at `/favicon.svg` and revert the CSP to `img-src 'self'`. Same artwork (dark rounded tile + green "AM"), served from a real URL with `Content-Type: image/svg+xml` and `Cache-Control: public, max-age=3600`. - new file: src/viewer/favicon.svg (extracted from the URL-decoded inline blob) - src/auth.ts: img-src reverted to 'self' - src/viewer/index.html: favicon link now href="/favicon.svg" - src/viewer/server.ts: new GET /favicon.svg route, candidate-path loader mirrors loadViewerTemplate() so it resolves both from source (vitest) and from dist/ (npm run start) - package.json: build script copies favicon.svg into dist/viewer/ alongside index.html - test/viewer-security.test.ts: CSP assertion now requires bare `img-src 'self'` and rejects any `data:` allowance; new e2e test hits /favicon.svg and verifies SVG payload + headers |
||
|
|
c93c715043 |
fix(test): stub history + removeAttribute in viewer sandbox (#445)
The viewer-session-id sandbox (added in #366) ran fine until #313 landed hash-routing in `switchTab` — `updateTabRoute` now calls `history.replaceState`, and the aria-selected toggle calls `removeAttribute` on the non-active tab buttons. Neither was stubbed in the vm sandbox, so the combined main produced 2 failures: TypeError: b.removeAttribute is not a function ReferenceError: history is not defined Both PRs (#366, #313) passed CI in isolation. The combined failure only surfaced after merge. Stub both globals. 987/987 tests now pass on main. |
||
|
|
63a0742447 |
fix: stabilize viewer navigation and timeline layout (#313)
* fix: stabilize viewer navigation and timeline layout * fix: address viewer review nits |
||
|
|
e2f2217f59 |
fix: redact filesystem watcher previews before transport (#332)
* fix: redact filesystem watcher previews * fix: redact quoted sensitive preview keys --------- Co-authored-by: 이민재 <19909783+honor2030@users.noreply.github.com> |
||
|
|
e9f447c80e |
fix: recover stale sessions before eviction (#387)
* fix: recover stale sessions before eviction Constraint: preserve recoverable session insights during stale cleanup Confidence: high Scope-risk: narrow Tested: npm test -- test/evict.test.ts --reporter=dot; npx tsdown; git diff --check Tested: npm test -- --reporter=dot (fails on existing Windows path-sensitive compress-file and obsidian-export tests) Signed-off-by: laplace young <yangqk12@whu.edu.cn> * fix: refine stale session recovery guard Constraint: preserve raw-only stale sessions until a compression recovery path exists Confidence: high Scope-risk: narrow Tested: npm test -- test/evict.test.ts --reporter=dot; npx tsdown; git diff --check Signed-off-by: laplace young <yangqk12@whu.edu.cn> --------- Signed-off-by: laplace young <yangqk12@whu.edu.cn> Co-authored-by: laplace young <yangqk12@whu.edu.cn> |
||
|
|
0ee3e6510e |
fix(viewer): handle sessions missing ids (#366)
* fix(viewer): handle sessions missing ids * fix(viewer): address session id review feedback --------- Co-authored-by: honor2030 <19909783+honor2030@users.noreply.github.com> |
||
|
|
97ae69a3e4 |
fix: add AbortController timeout to all raw-fetch providers (#379)
* fix: add AbortController timeout to all raw-fetch providers (closes #373) Signed-off-by: Satyam Pratibhan <142714564+SATYAM-PRATIBHAN@users.noreply.github.com.> * fix: validate timeout ms and compose upstream AbortSignal in fetchWithTimeout Signed-off-by: Satyam Pratibhan <142714564+SATYAM-PRATIBHAN@users.noreply.github.com.> --------- Signed-off-by: Satyam Pratibhan <142714564+SATYAM-PRATIBHAN@users.noreply.github.com.> |
||
|
|
3e7b61efd0 |
fix(security): reject non-loopback Host headers in viewer server (DNS rebinding) (#294)
The viewer (default :3113) binds to 127.0.0.1 and proxies every request to the local REST API with the AGENTMEMORY_SECRET attached as Bearer auth. The Origin allowlist defends against a plain cross-origin attacker page, but it does not defend against DNS rebinding: a victim visiting attacker.com whose authoritative DNS short-TTL-rebinds to 127.0.0.1 ends up issuing same-origin fetches to the viewer (origin and target both http://attacker.com:3113 from the browser's perspective), no preflight fires, and the proxy adds the secret server-side. The attacker page then reads every memory the user has stored. Fix: derive a Host-header allowlist from VIEWER_ALLOWED_ORIGINS plus the viewer's actual listen port (resolved post-listen so port=0 unit tests still work) and reject any other Host with 403 before the route logic runs. An explicit VIEWER_ALLOWED_HOSTS env var is honoured for reverse- proxy setups. Tests cover the predicate plus an end-to-end http.request that confirms an attacker-controlled Host header is rejected before the HTML / proxy paths. Detected by Aeon + manual review. Severity: high CWE-350 (Reliance on Reverse DNS Resolution for a Security-Critical Action) Co-authored-by: aeonframework <aeon@aaronjmars.com> |
||
|
|
3a3f866ab5 |
chore(release): v0.9.17 — OpenAI provider + telemetry id + Compare polish (#432)
Patch bump per the established rule: additive surface only. OpenAI provider is a new optional surface that activates only when OPENAI_API_KEY is set, gated by OPENAI_API_KEY_FOR_LLM. Telemetry project_name pin is pure observability metadata. Compare polish is docs/website only. PRs included since v0.9.16: #307 — OpenAI-compatible LLM provider (universal adapter for OpenAI, Azure OpenAI auto-detected by hostname, DeepSeek, SiliconFlow, vLLM, LM Studio, Ollama). Plus the maintainer- pushed Azure detection + fetch timeout + README scope hint follow-ups. Closes #185, #232, #312, supersedes #240. #426 — pin worker telemetry project_name #427 — Compare section polish (title + native plugins cell + grid) Files bumped (9): package.json, packages/mcp/package.json, plugin/.claude-plugin/plugin.json, plugin/.codex-plugin/plugin.json, src/version.ts, src/types.ts, src/functions/export-import.ts, test/export-import.test.ts, CHANGELOG.md |
||
|
|
705321be34 |
docs: refresh current project stats (#330)
* docs: refresh current project stats * docs: update REST endpoint badge count * docs: refresh stats for v0.9.16 --------- Co-authored-by: 이민재 <19909783+honor2030@users.noreply.github.com> |
||
|
|
b48176cacd |
fix(mcp): use DELETE for governance memory deletion proxy (#423)
Co-authored-by: 이민재 <19909783+honor2030@users.noreply.github.com> |
||
|
|
b7e1240d06 |
chore(release): v0.9.16 — DevEx polish + website refresh (#417)
Patch bump per the established rule: additive surface only, no breaks to MemoryProvider trait, exported types, or default behaviour. New top-level subcommands (`--reset` already shipped 0.9.15, no new commands here) are opt-in. PRs included since v0.9.15: #408 — onboarding wires selected agents inline + memory-share callout #409 — clarify MCP is opt-in (REST primary) #410 — 5-port ready panel, iii console install, global-install prompt #411 — splash banner rerender + README install hoist + npx caveat #415 — agent-memory.dev refresh (FeaturedIn bar + Agents/Compare/ CommandCenter/Hero updates) Files bumped (9): package.json, packages/mcp/package.json, plugin/.claude-plugin/plugin.json, plugin/.codex-plugin/plugin.json, src/version.ts, src/types.ts, src/functions/export-import.ts, test/export-import.test.ts, CHANGELOG.md |
||
|
|
c3a613aed8 |
chore(release): v0.9.15 — DevEx overhaul (splash, connect, doctor v2, remove, silent killers) (#407)
Patch bump per the established rule: all changes additive, no breaks to MemoryProvider trait or exported types or default behaviour. New top-level subcommands (connect, remove, --reset, --force) are opt-in. PRs included since v0.9.14: #405 — silent killers: viewer port auto-bump, engine version-match warning, stop --force, adopt-on-attach, npx PATH hint #402 — agentmemory connect — automate native-plugin install for 8 agents (claude-code, codex, cursor, gemini-cli, openclaw end-to-end; hermes/pi/openhuman stubbed) #406 — interactive doctor v2 + agentmemory remove (destruction plan + two-confirmation flow) #403 — splash banner + agent grid + provider picker + smart-defaults preferences + bootLog shim (30+ lines of log spam → 10) Files bumped (9): package.json, packages/mcp/package.json, plugin/.claude-plugin/plugin.json, plugin/.codex-plugin/plugin.json, src/version.ts, src/types.ts, src/functions/export-import.ts, test/export-import.test.ts, CHANGELOG.md |
||
|
|
659d0c3e1a |
feat(cli): splash banner, agent grid, provider picker, smart defaults (#403)
Replaces the 25-line `[agentmemory] X` log spam on first invocation
with a SkillKit-grade onboarding flow:
* `src/cli/splash.ts` — terminal-width-aware ASCII banner with three
tiers (>=120 / 80-119 / <80 cols), brand-accent orange via ANSI
256-colour, NO_COLOR-aware, no new deps.
* `src/cli/preferences.ts` — JSON-backed prefs at
`~/.agentmemory/preferences.json`, atomic write via tmp+fsync+rename,
graceful defaults on corrupt/missing file, `isFirstRun()` /
`resetPrefs()` helpers. 7 unit tests added.
* `src/cli/onboarding.ts` — first-run multi-select for agents (native
plugin + MCP-server rows from the README), single-select for
provider (anthropic / openai / gemini / openrouter / minimax / skip
-> BM25-only), seeds `~/.agentmemory/.env`, prints the next-step
hint.
* `src/logger.ts` — adds `bootLog` / `setBootVerbose`. The 18 startup
`console.log` calls in `src/index.ts` route through this shim;
default is muted, `--verbose` (or AGENTMEMORY_VERBOSE=1) restores
the old chatty output. Warnings, errors, and shutdown logs stay
loud.
* `src/cli.ts` — splash before clack intro, run onboarding when
`isFirstRun()` or `--reset`, propagate verbosity to the worker
logger, wait for `/agentmemory/livez` after `import("./index.js")`
and print a single-line ready hint. Adds `--reset` flag.
First-run output drops from 30+ lines to ~10. Subsequent runs are a
single ready line. `--verbose` restores the old verbose surface for
debugging. Build passes; the 10 pre-existing failures in
`test/mcp-standalone.test.ts` are unchanged (903 -> 910, all 7 new
passes are `test/preferences.test.ts`).
|
||
|
|
29e8865286 |
feat(cli): interactive doctor v2 + agentmemory remove (#406)
* feat(cli): interactive doctor v2 with inline fixes Replace passive doctor with an interactive [F]ix/[S]kip/[?]more/[Q]uit prompt per diagnostic. Each check now ships a one-line problem, cause, fix-preview, and inline apply() — and we re-check only the affected diagnostic after a fix, not the whole suite. New flags: doctor --all apply every fix without prompting (CI) doctor --dry-run show what each fix would do, run nothing Catalog (src/cli/doctor-diagnostics.ts): - env-missing → runInit() - no-llm-provider-key → open .env in $EDITOR - engine-version-mismatch → runIiiInstaller + restart - viewer-unreachable → stop + restart engine - stale-pidfile → clear pid + state, restart - env-placeholder-keys → open .env in $EDITOR - iii-on-path-not-local-bin → manual (suggest installer) The catalog is exported as a pure data structure so tests can assert each entry has check/fix/message without booting clack. Passive server checks (graph populated, Claude Code hooks, flags) still run first — they need a live engine and have no one-shot inline fix. * feat(cli): add agentmemory remove for clean uninstall New command tears down everything agentmemory installs: - ~/.agentmemory/iii.pid + engine-state.json (after stopping engine) - ~/.agentmemory/.env (asks separately — holds API keys) - ~/.agentmemory/preferences.json - ~/.agentmemory/backups/ (connect-PR manifest + backups) - ~/.local/bin/iii (only when iii --version matches our pin) - Any agent symlinks from the connect-manifest - ~/.agentmemory/data/ (asks separately, default keep) Surface: agentmemory remove # interactive, double-confirms agentmemory remove --force # skip generic confirms agentmemory remove --keep-data # remove binaries + symlinks only Plan building lives in src/cli/remove-plan.ts as a pure fs-inspecting function so tests sandbox a fake $HOME under tmpdir() and assert on plan shape without touching the real home directory. Always-ask items (.env, memory data, mismatched-version local-bin iii) still get per-item confirmation even with --force. |
||
|
|
b36a6d7c73 |
feat(cli): add agentmemory connect to automate native-plugin install for 8 agents (#402)
agentmemory connect <agent> wires agentmemory into each supported coding agent: detect install, back up existing config to ~/.agentmemory/backups/, merge agentmemory MCP entry, verify, idempotent on re-run. Working: claude-code, codex, cursor, gemini-cli, openclaw. Stubbed with manual-install hints: hermes (YAML), pi (TS extension copy), openhuman (no integrations folder). Windows prints manual-install message and exits. Argv: agentmemory connect [agent] [--all] [--dry-run] [--force] |
||
|
|
372c6a6bbb |
chore(release): v0.9.14 — CLI installer first + agentmemory stop + Docker-aware teardown (#401)
Patch bump. No breaking changes to the public API or the existing CLI
subcommands. CLI bootstrap flow changes are additive; existing Docker
users keep working via the new AGENTMEMORY_USE_DOCKER=1 opt-in.
Files bumped (9):
- package.json
- packages/mcp/package.json
- plugin/.claude-plugin/plugin.json
- plugin/.codex-plugin/plugin.json
- src/version.ts
- src/types.ts (ExportData.version union — also adds 0.9.13
which was missed in the previous release)
- src/functions/export-import.ts (supportedVersions Set)
- test/export-import.test.ts
- CHANGELOG.md
PRs included since v0.9.13:
#396 — feat(cli): install iii-engine binary first, Docker opt-in;
agentmemory stop with Docker-aware teardown; LISTEN-only
lsof filter so the CLI no longer signals its own parent.
#397 — docs(readme): move OpenHuman after pi in agents grid.
|
||
|
|
8b98432853 |
chore(release): v0.9.13 — env-example + init + CJK + load bench + deploy templates + Gemini GA bumps (#383)
* chore(release): v0.9.13 — env-example discovery + CJK tokenizer + load harness + deploy templates + Gemini GA bumps + 14 advisories closed Six PRs landed since v0.9.12: - #372 .env.example discovery (this commit) — repo-root template + `init` CLI command + CI sync-checker - #362 CJK BM25 tokenizer (`@node-rs/jieba` + tiny-segmenter + Hangul) - #363 `benchmark/load-100k.ts` harness with p50/p90/p99 + per-release results dir - #361 one-click deploy templates for fly.io / Railway / Render / Coolify (multi-stage Dockerfile, `iiidev/iii` base, `gosu` privilege drop, first-boot HMAC, verified end-to-end on fly.io) - #364 Python ecosystem via `iii-sdk` example (replaces closed PR #360) - #370 Gemini GA bumps (LLM default → gemini-2.5-flash, embedding → gemini-embedding-001 + L2-norm + 768 dims) Plus 14 open Dependabot advisories closed in PR #348 via Next.js → 16.2.6 and PostCSS → 8.5.10 overrides. Bumped: - src/version.ts: VERSION 0.9.12 → 0.9.13 - package.json: 0.9.12 → 0.9.13, files += ".env.example", build script copies .env.example into dist/ - packages/mcp/package.json: 0.9.12 → 0.9.13 (lockstep with main) - plugin/.claude-plugin/plugin.json, plugin/.codex-plugin/plugin.json: 0.9.12 → 0.9.13 - src/types.ts: ExportData.version union extended with "0.9.13" - src/functions/export-import.ts: supportedVersions Set extended - test/export-import.test.ts: expected version updated New surface: - .env.example at repo root — every env var read by src/ documented in one place, grouped by surface (LLM, embedding, auth, search tuning, behaviour flags, CLI runtime, ports, iii engine pin, Claude Code bridge, Obsidian export). Every line commented out by default so the file is a template. - agentmemory init — copies bundled .env.example to ~/.agentmemory/.env if absent, refuses to overwrite, prints a diff command. Wired into CLI dispatch + help block. - scripts/check-env-example.mjs — walks src/ for env-read patterns, fails CI on drift in either direction. Plugged into ci.yml after npm test. Initial bootstrap: 60 keys in sync. Verified: npm test 903/903, npm run build clean, init smoke pass (creates ~/.agentmemory/.env on first run, refuses overwrite on second). * fix(init): atomic copy via COPYFILE_EXCL; address CodeRabbit review Two valid findings from the CodeRabbit pass on PR #383. 1. `runInit` race between existsSync(target) + copyFile(template, target). A parallel `agentmemory init` (or any other process touching ~/.agentmemory/.env between the two calls) would silently overwrite the config the operator just wrote. Switch to a single atomic `copyFile(template, target, fsConstants.COPYFILE_EXCL)` and treat the EEXIST error as the "already configured" signal — same warning + diff hint as before, but the check + copy now happen in one syscall so they cannot race. Other failure paths still surface as process exit 1. 2. Comment on `scripts/check-env-example.mjs::walk` claimed it matched ".ts / .mts / .mjs" but the regex also matched ".js". Rewrote the comment to match the regex (".ts / .mts / .mjs / .js"). Same comment pass: noted that test/ never enters because the walk is rooted at src/, not because of an explicit skip. Skipped findings: - WHAT-style comment on `findEnvExample` — kept a one-liner explaining the package-vs-source priority since both paths are real; reduced the block from 4 lines to 2 instead of removing it entirely. - "Add trailing newline to .env.example" — file already ends with `\n` (verified `tail -c 5` shows `tion\n`). Verified locally: - `npm run build` clean. - `npm test` 903 / 903 pass. - First `agentmemory init` against a clean HOME creates the file. - Second init against the same HOME hits EEXIST and prints the "leaving it untouched" warning + diff hint without overwriting. - `node scripts/check-env-example.mjs` — in sync (60 keys). |
||
|
|
8c3418cc61 |
feat(search): CJK tokenizer for BM25 (#362)
* feat(search): CJK tokenizer for BM25 (closes #344) BM25 previously tokenized Chinese, Japanese, and Korean memories as a single sentence-long token because CJK scripts don't put spaces between words, leaving the keyword leg of hybrid search as dead weight for CJK users. Greek, Cyrillic, accented Latin already work since v0.9.12 (#327) because they split cleanly on whitespace. Detect CJK by Unicode block, then route per-run: - Han: @node-rs/jieba (native, no model download), Jieba.cut(text, hmm) - Kana (with mixed kanji): tiny-segmenter (pure JS, ~25KB) - Hangul: rule-based, each [가-]+ run is one token Both segmenters declared in optionalDependencies so the bundle stays small for users who don't need them. If the dep isn't installed the segmenter soft-falls to whole-run tokenization (the pre-fix behavior) and prints a one-time stderr hint per language. Non-CJK input goes through the existing tokenize path unchanged — zero behavior change for the Latin/Greek/Cyrillic/Hebrew/Arabic paths. New fixtures in test/search-index.test.ts cover Chinese (项目记忆存储), Japanese (プロジェクト記憶), and Korean (프로젝트 메모리 저장소). Existing Greek + ASCII tests stay green. Suite grew 899 -> 902 tests. * fix(cjk): preserve source order across mixed CJK + non-CJK runs The previous segmentCjk walked the input three times — once for Han/kana runs, once for Hangul runs, once for non-CJK pieces — and concatenated the results. For a mixed string like "abc 메모리 def 项目 ghi" that emitted ["메모리","项目","abc","def","ghi"], dropping the original positional information BM25 phrase queries and any downstream consumer keyed on token order rely on. Replace with a single left-to-right pass over CJK_RUN_RE: emit any intervening non-CJK substring (trimmed), then route the matched CJK run to segmentHangul / segmentKana / segmentHan based on which script it contains, and finally emit any trailing non-CJK piece. Added a regression test asserting source order across four mixed-script strings. Drops the now-unused HAN_KANA_RUN_RE / HANGUL_RUN_RE regex constants. |
||
|
|
87fae50b79 |
chore(release): v0.9.12 — BM25 unicode + vector live-write + viewer hardening (#337)
Three landed PRs since v0.9.11: - #327 (#295) — BM25 tokenizer now accepts non-ASCII (Greek, accented Latin, Hebrew, Arabic, Cyrillic), VectorIndex.add now actually called at runtime via vectorIndexAddGuarded helper with dim guard + input clip, migrateVectorIndex for dim migrations. - #326 (#277) — RetentionScore type no longer declares source twice; JSDoc back-compat note no longer shadowed. - #335 (#323) — viewer drops Google Fonts <link> (CSP-blocked), loadDashboard now surfaces load errors inline instead of sticking on "Loading dashboard…". Bumping 0.9.11 -> 0.9.12 across the 9 standard files: - package.json - packages/mcp/package.json - plugin/.claude-plugin/plugin.json - plugin/.codex-plugin/plugin.json - src/version.ts - src/types.ts (ExportData.version literal) - src/functions/export-import.ts (supportedVersions) - test/export-import.test.ts (round-trip expectation) - CHANGELOG.md (new 0.9.12 entry) 886 / 886 tests pass. Build clean. |
||
|
|
1a706ca2d6 |
fix(integrations): warn when bearer auth crosses plaintext HTTP to non-loopback (#315)
* fix(integrations): warn when bearer auth crosses plaintext HTTP to non-loopback (#275) Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> * test(integrations/315): edge cases for IPv6 loopback + LAN IPs + lookalike hostnames + docs @mvanhorn's PR #315 lands the right plaintext-bearer-auth warn-once pattern across three runtimes (hermes/python, openclaw/mjs, pi/typescript). Reviewed cleanly. Adding test cases + docs before merge so the contract is locked: Test additions (test/integration-plaintext-http.test.ts pi suite, +4 cases, 13 total in suite): - IPv6 loopback (http://[::1]:3111) — URL parser strips brackets, hostname comes back as `::1`, the LOOPBACK_HOSTS set already covers this. Test pins the parsing semantic so a future refactor can't silently break it. - Private LAN IPs (RFC1918 ranges 192.168.x.y, 10.x.x.x) — NOT loopback, guard MUST warn. RFC1918 was a deliberate design choice (loopback set stays minimal: localhost / 127.0.0.1 / ::1). LAN deployment behind a tunnel still leaks the bearer over the wire. - No-secret short-circuit (guard with secret="" or undefined) — guard never fires when a bearer wouldn't actually be sent. Mirrors the usesPlaintextBearerAuth() early-return. - Lookalike hostname (http://localhost.evil.com:3111) — `parsed.hostname` returns the full `localhost.evil.com` string, which is NOT in LOOPBACK_HOSTS. Guard warns. This pins the exact-match (vs prefix) loopback semantic. Docs additions: - integrations/pi/README.md and integrations/hermes/README.md now document AGENTMEMORY_REQUIRE_HTTPS in the env-vars table — same row shape as AGENTMEMORY_SECRET, names the env value (=1), describes the warn-vs-throw semantic, and lists the loopback hostnames the guard recognises. No code changes to the guard itself — it's already correct. Pre-merge hardening only. 899 / 899 tests pass on rebased branch. Build clean. --------- Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Rohit Ghumare <ghumare64@gmail.com> |
||
|
|
24f6c5fb24 |
fix(search): BM25 unicode + vector index live-write — improved (closes #295, supersedes #296) (#327)
* fix #2: BM25 tokenizer now preserves Cyrillic/Unicode via \p{L}\p{N} regex Signed-off-by: Nikita Nosov <20nik.nosov21@gmail.com> * fix #1: add getVectorIndex/setVectorIndex + vector embedding in rebuildIndex Signed-off-by: Nikita Nosov <20nik.nosov21@gmail.com> * fix #1+#3: vectorIndex.add in remember/observe/compress + setVectorIndex in index.ts Signed-off-by: Nikita Nosov <20nik.nosov21@gmail.com> * fix: add migration script, Cyrillic BM25 tests, vector-populate tests, rebuildIndex vector test Adds migrateVectorIndex utility for re-embedding when embedding provider dimensions change. Adds comprehensive test coverage for Cyrillic tokenization, vector index population on remember/rebuildIndex, and migration edge cases. Signed-off-by: Nikita Nosov <20nik.nosov21@gmail.com> * chore: remove internal research spec from PR branch Signed-off-by: Nikita Nosov <20nik.nosov21@gmail.com> * refactor(search): centralize vector-index writes via vectorIndexAddGuarded Pre-merge hardening on top of @nik1t7n's PR #296. Original PR had vectorIndex.add() inlined at four sites (remember, observe, compress, rebuildIndex) with the same try/catch shape repeated each time. That open-codes three sharp edges per site: 1. Missing dimension guard at the write boundary. Per #248, persistence load already refuses to start when persisted vectors mismatch the active provider's dimensions — but the live-write path had no symmetric check. A mis-configured embedding provider returning a different-length Float32Array would silently corrupt the in-memory index until next restart cleared it. 2. No input clipping. Memory.content can be arbitrarily large (the MemorySlot path even allows 20KB content per slot). Most embedding providers cap input around 8k tokens and 400 on overflow. A single oversize memory could throw at the provider and lose the vector entry; worse, on local-Ollama-style providers it can cost real inference time. 3. Inconsistent error logging (rebuildIndex had `catch {}` swallowing the error entirely, the other three sites logged via logger.warn with slightly different shape). Consolidates into a single exported `vectorIndexAddGuarded()` in src/functions/search.ts: - Reads vectorIndex + currentEmbeddingProvider from module-level state (same singletons the PR added). - Clips embed input to 16k chars (EMBED_MAX_CHARS) — well under every provider's documented limit, leaves headroom for chars-per-token variance across languages. - Validates embedding.length === provider.dimensions before calling vi.add(); logs structured warning and skips on mismatch. - Single logger.warn on embed failure with provider name + error message + indexed-item kind ("memory" | "observation" | "synthetic") + id. - Returns boolean (true on success) so callers can branch if they want, but every existing call site fire-and-forgets — the contract is "soft-fail: index entry skipped, upstream save still succeeds". Rewrites the four call sites (remember.ts, observe.ts synthetic branch, compress.ts post-LLM, search.ts rebuildIndex memories + obs loops) to call vectorIndexAddGuarded() instead of the inline try/catch. Side effect: cleans up the lingering `catch {}` in rebuildIndex that was swallowing rebuild-time embed failures silently — every failure now surfaces in logs at a consistent shape. Tests: - test/multimodal.test.ts mocked `../src/functions/search.js` and exposed only getSearchIndex; needed vectorIndexAddGuarded added to the mock surface or test fails with "No vectorIndexAddGuarded export is defined on the search.js mock". Added the export to the mock. - All other tests (search-index Cyrillic, search rebuildIndex vector, vector-index-populate, migrate-vector-index) green on the helper refactor — no behavioural change beyond the three guards above. 886 / 886 tests pass. Build clean. Out-of-scope (deferred to follow-ups): - Blocking await on every save. The embed call is on the request hot path. For local Ollama / on-device providers this is fine; for network providers it adds 200ms-2s latency per remember/observe. Should be async / fire-and-forget with a write-behind queue — separate perf PR after this lands. - Periodic persistence flush of the vector index. Today the index is serialized at process exit (per IndexPersistence). Runtime adds between flushes survive in memory but die on crash. Acceptable for this PR; future PR can add a debounced flush. - Batching in rebuildIndex. Today the rebuild path embeds one doc at a time. embedBatch() exists on every provider and would be ~5-10x faster for cold restarts on large corpora. Defer. * fix(search): tighter migrate guard + rebuild vector clear + test hygiene Five reviewer findings applied. Five skipped with reason: APPLIED: 1. migrate-vector-index.ts filter now requires m.content (and rejects empty/whitespace). Without this, undefined m.content would concat to "title undefined" and poison the embed input. Memory.content is declared string but #277-era data shapes had cases where it could be missing — defensive filter. 2. migrate-vector-index.ts success expression now derives from the failed counter (success: failed === 0). Previously hardcoded true so callers couldn't rely on the boolean alone. 3. search.ts rebuildIndex now calls vectorIndex?.clear() before the repopulation loops. Symmetric to the BM25 idx.clear() that was already there. Without this, memories deleted between runs leave orphan embeddings in the vector store after every rebuild. 4. test/vector-index-dimensions.test.ts assertion tightened from toBeGreaterThan(0) to toBe(1) for both totalProcessed and vectorSize. The seeded fixture has exactly one observation; loose assertion would hide regressions where the migration produced extra/duplicate vectors. 5. test/vector-index-populate.test.ts switched to beforeEach/afterEach for the setVectorIndex / setEmbeddingProvider lifecycle. Manual cleanup in each test body was easy to drift on additions. PLUS: 6. compress.ts BM25 add now wrapped in try/catch matching the remember.ts pattern for parity. Helps cross-file symmetry even though SearchIndex.add doesn't throw on valid input. SKIPPED (with reason): - Wrapping vectorIndexAddGuarded calls in additional try/catch at remember.ts / observe.ts / compress.ts: the helper has internal try/catch (src/functions/search.ts:67-89). It NEVER throws. Adding outer try/catch is dead code. The reviewer missed the helper's contract. - vector-index-dimensions.test.ts mockKV.delete -> real deletion: the tests in that file don't exercise deletion semantics, and migrateVectorIndex itself never calls kv.delete. Touching it would add noise without effect on any assertion. - recordAudit + Promise.allSettled in remember.ts: out of scope. recordAudit is already called earlier in the function. Parallelizing cascade-update with vector-add changes failure semantics (cascade- update is currently fire-and-forget) — separate perf PR if wanted. - validateDimensions(N) assertion in vector-index-dimensions.test.ts: vectorSize is the COUNT of stored vectors, not their dimensions. Adding a validateDimensions check on the result would require exposing the new index. Out of scope for assertion tightening. 886 / 886 tests pass. Build clean. * fix(migrate): per-session isolation + dim guard + drop unused oldIndex param Four reviewer findings applied. One skipped with reason: APPLIED: 1. Per-session isolation in migrateVectorIndex observations phase. The previous shape wrapped the entire sessions for-loop in one try/catch — a single bad session (kv.list throws, embedBatch rejects, etc.) aborted every subsequent session and silently truncated the migration. The loop now has per-session try/catch; failures increment `failed`, append the session id to a new `failedSessions` array on the result, and the loop continues. The kv.list<sessions> call itself is now guarded separately — if THAT fails the whole migration aborts (no sessions to iterate), but with `failed` recorded and `failedSessions` empty so the caller can distinguish. 2. Drop `oldIndex` parameter from migrateVectorIndex signature. It was dead — no read path inside the function. Three test call sites updated to match the new signature migrateVectorIndex(kv, newProvider). Confused the API contract; if we ever DO need a previous-index argument it can come back with a real use case. 3. Dim guard on every embedding result inside migrateVectorIndex, in both the memories phase and the per-session observations phase. Extracted into a local isValidEmbedding() helper that mirrors the shape of search.ts::vectorIndexAddGuarded's guard: log structured warn + skip on mismatch. Without this, a misconfigured provider returning the wrong-length Float32Array would corrupt the rebuilt index, defeating the #248 persistence-load guard. 4. Rename `vi` -> `vectorIndex` in test/vector-index-populate.test.ts so the let binding doesn't shadow the vitest `vi` import. Practical impact: future tests in that file can use `vi.fn()` / `vi.spyOn()` without renaming this binding first. Cosmetic but cheap. SKIPPED (with reason): - Use embedBatch in rebuildIndex (search.ts): perf optimization, not correctness. Already called out as deferred in the previous review's commit message. Cold-restart speed matters but the fix here is bigger refactor (collect-into-batches with metadata, validate each, add only successful) and worth its own PR. Will pick up when we also do the write-behind queue for live-write batching. Result envelope: Added `failedSessions: string[]` to MigrateVectorIndexResult so the caller can decide whether to retry per-session or just abort. Existing fields (success, totalProcessed, failed, vectorSize) unchanged in shape. 886 / 886 tests pass. Build clean. * fix(migrate): attribute batch-level failures to correct count + distinguish list-fail Two new reviewer findings on migrate-vector-index. Six earlier findings re-flagged in the same review block had already been addressed in prior commits on this branch (verified inline): - compress.ts BM25 try/catch (commit |
||
|
|
25dddc4379 |
chore(release): v0.9.11 — Codex plugin platform + OpenClaw slot fix + website star button (#317)
Three landed PRs since v0.9.10: - #311 — Codex plugin support (manifest + marketplace + Codex-shaped hooks subset, sharing the same scripts as the Claude Code plugin via CLAUDE_PLUGIN_ROOT OOTB-compat injection on Codex's hook engine) - #310 — OpenClaw plugins.slots.memory now actually claimed via api.registerMemoryCapability({ promptBuilder }); older builds without the capability API still load via the existing hook-only path - #316 — Star on GitHub button in the website hero CTA row, live stargazer count via api.github.com, 30-minute localStorage cache, graceful degrade on API failure Bumping 0.9.10 -> 0.9.11 across the 8 standard files: - package.json - packages/mcp/package.json - plugin/.claude-plugin/plugin.json - plugin/.codex-plugin/plugin.json - src/version.ts - src/types.ts (ExportData.version literal) - src/functions/export-import.ts (supportedVersions) - test/export-import.test.ts (round-trip expectation) - CHANGELOG.md (new 0.9.11 entry) 877 / 877 tests pass. Build clean. |
||
|
|
48b11e8351 |
fix(openclaw): claim plugins.slots.memory via registerMemoryCapability (#310)
* fix(openclaw): claim plugins.slots.memory via registerMemoryCapability
The OpenClaw integration declared kind: "memory" in its manifest and
wired before_agent_start / agent_end hooks, but never called
api.registerMemoryCapability(...). On OpenClaw 2026.4.9+ that
left plugins.slots.memory = "agentmemory" reporting unavailable
even though the hooks worked end-to-end and the REST API was
healthy.
Per OpenClaw's plugin SDK declarations
(plugin-sdk/src/plugins/types.d.ts), api.registerMemoryCapability
takes a MemoryPluginCapability and is the canonical way to occupy
the memory slot. All capability fields are optional. The
promptBuilder field has signature:
(params: { availableTools: Set<string>; citationsMode? }) => string[]
This commit:
- Calls api.registerMemoryCapability({ promptBuilder }) at register
time when the host exposes it. promptBuilder returns three lines
describing agentmemory as the active memory provider, including
the configured base_url so the agent knows where recall is
coming from.
- Guards on `typeof api.registerMemoryCapability === "function"`
so older OpenClaw builds (pre-capability API) still load via
the existing hook-only path. Net behaviour on older hosts is
unchanged.
- Does NOT register a runtime adapter. OpenClaw's current
MemoryRuntimeBackendConfig type is exactly { backend: "builtin" }
or { backend: "qmd"; qmd?: { command?: string } }; both are
in-process backends and don't fit an external REST service.
The hook-driven recall + capture flow is the working
integration path; runtime registration is documented as a
follow-up gated on an upstream OpenClaw change to add an
"external" backend variant.
README troubleshooting block adds a specific entry for the
"unavailable slot" symptom, pointing at the registerMemoryCapability
fix. The README also documents the runtime-adapter scope decision
so users running deep host integrations aren't surprised.
3 new tests in test/openclaw-plugin.test.ts assert:
1. registerMemoryCapability is called with a promptBuilder when
the host supports it.
2. Plugin still registers hooks and does not throw on older hosts
that don't expose registerMemoryCapability.
3. promptBuilder respects the configured base_url.
871 / 871 tests pass.
Verified against the openclaw npm package surface:
- api.registerMemoryCapability is declared at
plugin-sdk/src/plugins/types.d.ts:2028
- MemoryPluginCapability is declared at
plugin-sdk/src/plugins/memory-state.d.ts
- promptBuilder signature matches MemoryPromptSectionBuilder
- No import from openclaw/extensions/* needed (and that path is
not in the package's exports map anyway)
This is a fresh implementation. The closed PR #302 attempted the
same end goal but imported memoryRuntime from a non-existent
path (openclaw/extensions/memory-core/runtime-api) and would have
failed with ERR_PACKAGE_PATH_NOT_EXPORTED at load time.
* fix(openclaw): accept params arg in promptBuilder to match MemoryPromptSectionBuilder
Reviewer flagged that promptBuilder was declared as () => [...] which
doesn't match the MemoryPluginCapability contract verified from
openclaw@2026.5.7 (plugin-sdk/src/plugins/memory-state.d.ts):
type MemoryPromptSectionBuilder = (params: {
availableTools: Set<string>;
citationsMode?: MemoryCitationsMode;
}) => string[];
JavaScript would tolerate the zero-arg form at runtime (extra args
are ignored), but the signature was misleading and would fail a
TypeScript port. Updated to (_params) => [...] with a comment
explaining we accept-but-ignore. Underscore-prefix conveys
intentional non-use to lint/readers.
Validation: 3/3 openclaw tests still pass (they invoke
promptBuilder({ availableTools: new Set() }) — both old and new
signatures accept that call).
|
||
|
|
c21410e287 |
feat(plugin): ship Codex plugin manifest + marketplace (#311)
* feat(plugin): ship Codex plugin manifest + marketplace (#codex-plugins)
OpenAI Codex shipped a plugin platform
(developers.openai.com/codex/plugins) with the same shape as Anthropic
Claude Code plugins: .codex-plugin/plugin.json manifest, optional
.mcp.json, hooks/hooks.json, and a skills/ directory.
Verified against openai/codex source
(codex-rs/hooks/src/engine/discovery.rs): Codex's hook engine
explicitly injects CLAUDE_PLUGIN_ROOT into hook subprocesses for
OOTB compat with existing Claude Code plugins. The wire-format
input schemas
(codex-rs/hooks/schema/generated/session-start.command.input.schema.json
and siblings) use the same field names as Claude Code
(session_id, cwd, hook_event_name, source, transcript_path, model,
permission_mode).
Net result: our existing plugin/ directory already works as a Codex
plugin with two small additions:
1. plugin/.codex-plugin/plugin.json — Codex manifest pointing at the
shared ./.mcp.json, ./skills/, and a Codex-specific ./hooks/
hooks.codex.json. Name (kebab-case), version, description match
the Claude Code manifest.
2. plugin/hooks/hooks.codex.json — Codex-compatible hook subset.
Drops SubagentStart, SubagentStop, SessionEnd, Notification,
TaskCompleted, PostToolUseFailure (Claude-Code-only). Keeps
SessionStart, UserPromptSubmit, PreToolUse, PostToolUse,
PreCompact, Stop. Adds Codex-specific `statusMessage` decorations
on the two hooks the user sees most (SessionStart + UserPromptSubmit).
The script commands themselves still reference ${CLAUDE_PLUGIN_ROOT}
because Codex's engine injects it.
3. .codex-plugin/marketplace.json at repo root — git-subdir source
pointing at ./plugin so `codex plugin marketplace add rohitg00/
agentmemory` works the same way `claude plugin marketplace add`
does. Mirrors the existing .claude-plugin/marketplace.json shape
but in Codex's marketplace schema (name + interface.displayName
+ plugins[].source.{source, url, path, ref} + plugins[].policy).
README updates:
- Codex CLI tile in the Works-with-every-agent grid now reads
"6 hooks + MCP + skills" instead of "MCP server" to surface the
upgraded surface.
- "Other agents" install table now distinguishes "Codex CLI (MCP
only)" (existing codex mcp add path) from "Codex CLI (full
plugin)" (new marketplace install).
- New Codex section in the "paste this prompt" block with the
two-command install (server + marketplace add + plugin install),
the list of registered surfaces, and the env-var-injection note
citing the Codex source line.
6 new tests in test/codex-plugin.test.ts cover:
1. .codex-plugin/plugin.json present with kebab-case name and
required references.
2. Manifest version matches main package.json (so future bumps
don't drift the Codex side).
3. Every path referenced in the manifest resolves to a real file or
directory on disk.
4. hooks.codex.json contains only events Codex supports — any
future addition has to be added to a Codex allowlist or the
test fails loudly.
5. Every ${CLAUDE_PLUGIN_ROOT}/scripts/* command references an
existing script file (catches typos before users hit them).
6. .codex-plugin/marketplace.json declares git-subdir source with
path: "./plugin" pointing at this repo.
874 / 874 tests pass.
* fix(plugin/codex): align manifest description + assert PreCompact hook
Two reviewer findings addressed:
1. plugin/.codex-plugin/plugin.json's description claimed "12 hooks"
but Codex only registers 6 (the Codex hook input schemas don't
define SubagentStart, SubagentStop, SessionEnd, Notification,
TaskCompleted, PostToolUseFailure). Updated the description to
"6 hooks, 51 MCP tools, 4 skills, real-time viewer." so the
marketplace listing matches the registered surface.
2. test/codex-plugin.test.ts asserted five lifecycle events
(SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop)
but omitted PreCompact, leaving a regression hole: dropping
PreCompact from hooks.codex.json would still pass the test
because the allowlist loop only blocks UNKNOWN events. Added
expect(events).toContain("PreCompact") so the suite enforces
the full six-hook contract.
Skipped (with reason):
3. marketplace.json source.ref currently "main" — reviewer asked
for a fixed tag/SHA. Skipping because pinning to a tag forces
updating marketplace.json on every release; "main" matches our
convention where releases tag from main HEAD and pre-release
work is on feature branches. Users wanting an immutable cut
can pass --ref vX.Y.Z to `codex plugin marketplace add` per
Codex docs.
Validation: 6/6 codex tests pass; 874/874 full suite unchanged.
|
||
|
|
5ea01c162c |
fix(deploy): distroless volume perms + viewer proxy + budget loop (#299, #301) (#304)
@flamerged reported three issues on a real production deployment: 1. (#301) v0.9.7's working-directory fix moved iii-config paths from ./data/... to /data/... so the named volume mount is actually reached. But iiidev/iii is distroless and runs as UID 65532, while `docker volume create` initializes the named volume mountpoint as root:root mode 755. Engine writes fail Permission denied (os error 13), the API silently buffers in RAM, every API call returns success, and state evaporates on every container restart — exactly what 0.9.7 set out to fix. Fix: ship a one-shot iii-init service in docker-compose.yml (busybox:1.36, ~4MB, exits in <100ms) that chowns /data to 65532:65532. iii-engine now has user: "65532:65532" and depends_on.iii-init.condition: service_completed_successfully. Verified live: pre-fix volume stayed 4.0K after API writes; post- fix volume grows to 44K with state_store.db/mem%3A*.bin files written through the named volume. 2. (#299) src/viewer/index.html ports detection hardcoded ':3113' as the fallback when window.location.port is empty (page served on 80/443 behind a reverse proxy). Every browser-side /agentmemory/* fetch went to <host>:3113, which is typically loopback-only on the self-hosted shape — the dashboard rendered cleanly but every panel showed the empty "first run" state. Fix: when neither ?port=N nor window.location.port is set, use window.location.origin as the REST base and window.location.host for the WebSocket URL — same-origin path works for both REST and live updates. Explicit ?port=N / non-default window.location.port paths unchanged. 3. (bundled) mem::context budget loop used `break` on first oversized block. With #288's new pinned-slot injection sorting first via recency: Date.now(), one fat pinned slot could starve every smaller block downstream that would have fit. Switched to `continue` so smaller blocks still slip into remaining budget. Total tokens still bounded by tokenBudget; only composition under contention changes. Bumping 0.9.9 -> 0.9.10 across the 8 standard files (package.json, packages/mcp/package.json, plugin/.claude-plugin/plugin.json, src/version.ts, src/types.ts ExportData literal, src/functions/export-import.ts supportedVersions, the export round-trip test expectation, and CHANGELOG.md). 868 / 868 tests pass. Build clean. Volume + viewer fixes verified end-to-end live. |
||
|
|
03fb42db39 |
chore(release): v0.9.9 — pinned slot injection + MiniMax env loader (#290)
Two field-reported fixes landed since v0.9.8: - #288 (closes #286) — wires pinned memory slots into mem::context so agentmemory pinned-slot content actually reaches SessionStart. The renderPinnedContext / listPinnedSlots helpers from #182 had zero callers; this lands the wiring behind isSlotsEnabled(). - #289 (closes #285) — MiniMax provider now reads MINIMAX_BASE_URL via the shared getEnvVar() loader (so ~/.agentmemory/.env values get picked up), and the default endpoint is bumped from the stale api.minimaxi.com to api.minimax.io/anthropic per MiniMax's current Anthropic-compatible docs. Bumping 0.9.8 -> 0.9.9 across the 8 standard files (package.json, packages/mcp/package.json, plugin/.claude-plugin/plugin.json, src/version.ts, src/types.ts ExportData literal, src/functions/export-import.ts supportedVersions, the export round-trip test expectation, and CHANGELOG.md). 868 / 868 tests pass. |
||
|
|
ccf440902a |
fix(slots): wire pinned slot injection into mem::context (#288)
* fix(slots): wire pinned slot injection into mem::context `renderPinnedContext` and `listPinnedSlots` were introduced in #182 and tested in `test/slots.test.ts`, but no code path actually invokes them. `mem::context` (which `/agentmemory/session/start` calls, and which the `session-start.mjs` hook reads back to populate Claude Code's SessionStart context) reads profile / sessions / observations but never reads slots — so pinned content stays in KV and never reaches the conversation. #182's description hedged with "pinned slots **can be** injected into SessionStart context" and described pinned slots as "**candidates for** SessionStart injection". The helpers and tests for them landed; the wiring did not. Reflection has the same shape of problem: `mem::slot-reflect` writes to `pending_items` / `session_patterns` / `project_context` on the Stop hook, but the next session never reads those slots, so the reflect → next-session loop is open. This adds the missing call at the top of `mem::context`, gated by `isSlotsEnabled()` so behaviour for `AGENTMEMORY_SLOTS=false` users is unchanged. Slot content is pushed as a `ContextBlock` with `recency = Date.now()` so pinned content ranks first in the budget-bounded selection — "always current" semantics. The existing `INJECT_CONTEXT` gate in `session-start.mjs` still decides whether the result actually reaches Claude Code stdout, so default-off users are unaffected on both flags. `test/context-slots.test.ts` covers: - pinned global slot lands in returned context - multiple pinned slots render in label-sorted order - unpinned slots are excluded even when they have content - empty pinned slots (the seeded defaults) are excluded - project-scoped slot shadows global slot with the same label - AGENTMEMORY_SLOTS=off path emits nothing Signed-off-by: wyh0626 <44987669+wyh0626@users.noreply.github.com> * fix(slots): parallelize slot and profile reads in mem::context Per CodeRabbit feedback on #288: `listPinnedSlots` and the profile fetch read independent KV scopes, so the project's `src/functions/**` guideline asks for `Promise.all`. When `AGENTMEMORY_SLOTS=false` the slot side short-circuits to `Promise.resolve([])`, so the existing flag gate is preserved and no extra KV traffic is introduced for opt-out users. Signed-off-by: wyh0626 <44987669+wyh0626@users.noreply.github.com> --------- Signed-off-by: wyh0626 <44987669+wyh0626@users.noreply.github.com> |
||
|
|
27c38b74e6 |
fix(providers/minimax): use shared env loader + bump default base URL (#285) (#289)
@rager306 reported v0.9.8 MiniMax compression failing with "MiniMax API error 401: invalid api key" against a known-good key. Direct curl to https://api.minimax.io/anthropic/v1/messages with the same key succeeded; control curl to https://api.minimaxi.com/anthropic reproduced the 401. The 401 came from the stale fallback host, not the key. Root cause: split-brain env source. src/config.ts (provider detection, API key load) reads ~/.agentmemory/.env via the merged getMergedEnv() loader. src/providers/minimax.ts read MINIMAX_BASE_URL directly off process.env. Users who put MINIMAX_BASE_URL only in ~/.agentmemory/.env (e.g. systemd / launchd / .env-only deployments) got their key loaded fine but the base URL fell through to the stale api.minimaxi.com default. MiniMax's current Anthropic-compatible endpoint per their docs is api.minimax.io/anthropic/v1/messages (https://platform.minimax.io/docs/api-reference/text-anthropic-api). Fix: - Switch MinimaxProvider.baseUrl resolution from process.env['MINIMAX_BASE_URL'] to getEnvVar('MINIMAX_BASE_URL'), matching how every other config knob in agentmemory is loaded. - Bump the default from https://api.minimaxi.com/anthropic to https://api.minimax.io/anthropic. - Update the inline JSDoc to mention ~/.agentmemory/.env as a source alongside process.env, so the env contract is discoverable from the file. - Add test/minimax-provider.test.ts asserting the default is the current host and the env override is honored. Verified locally: 862/862 tests pass on the branch. @rager306 has already verified end-to-end that a local patch with the same change fixed his /agentmemory/observe + compression flow. |
||
|
|
0f0bb5091d |
chore(release): v0.9.8 — local fallback tools/list returns 7 not 4 (#284)
Single-issue release: PR #283 fixed the local-mode tools/list path in @agentmemory/mcp to expose all 7 IMPLEMENTED_TOOLS instead of the 4-tool intersection ESSENTIAL_TOOLS ∩ IMPLEMENTED_TOOLS that bug manifested as in MCP clients (Cursor / Roo Code / others) when no agentmemory server was reachable on localhost:3111. Bumping 0.9.7 -> 0.9.8 across the 8 standard files (package.json, packages/mcp/package.json, plugin/.claude-plugin/plugin.json, src/version.ts, src/types.ts ExportData literal, src/functions/export-import.ts supportedVersions, the export round-trip test expectation, and CHANGELOG.md). |
||
|
|
0a6c399e23 |
fix(mcp): local fallback exposes all 7 IMPLEMENTED_TOOLS, not 4 (#234) (#283)
Reported live in Cursor: agentmemory MCP server shows exactly four
tools — memory_recall, memory_save, memory_sessions,
memory_smart_search — when no agentmemory server is running on
localhost:3111. Should be seven: those four plus memory_export,
memory_audit, memory_governance_delete (the full InMemoryKV-backed
local capability set the shim ships).
Root cause: the local-fallback branch of tools/list in
src/mcp/standalone.ts intersected the shim's IMPLEMENTED_TOOLS set
(7) with getVisibleTools(), which honors the shim's own
AGENTMEMORY_TOOLS env. Default env is unset, so getVisibleTools()
returns the 8 ESSENTIAL_TOOLS (the server's default "core" tier).
The 4 visible tools are exactly that intersection:
ESSENTIAL_TOOLS ∩ IMPLEMENTED_TOOLS =
{memory_save, memory_recall, memory_smart_search, memory_sessions}
The intersection was meaningless: the shim doesn't dispatch by
ESSENTIAL_TOOLS, it dispatches by IMPLEMENTED_TOOLS. Whatever
AGENTMEMORY_TOOLS the SERVER honored shouldn't shape the SHIM's
local-fallback list — the shim's local capabilities are fixed by
its in-memory KV, not by an env knob.
Fix: switch the filter source from getVisibleTools() to
getAllTools(). The filter through IMPLEMENTED_TOOLS still picks the
right 7 names, but now from the unfiltered universe of tool
definitions instead of the env-filtered subset.
Refactored the tools/list handler out of the inline createStdioTransport
callback into an exported handleToolsList() so it's directly testable.
Added a regression test that asserts the local-fallback path returns
exactly the 7 IMPLEMENTED_TOOLS regardless of AGENTMEMORY_TOOLS=core
(default) or unset.
Verified live via stdio against a downed server: shim now returns
all 7 names. Verified the previous bug repro (4 names) is gone.
860 / 860 tests pass on the branch (859 baseline + 1 regression test).
|
||
|
|
82d1b06df5 |
fix(mcp): probe diagnostics + sandbox escape hatch (#234, #278) (#280)
* fix(mcp): probe diagnostics + sandbox escape hatch (#234, #278) Three small follow-ups to v0.9.6 reported live on #234 and #278: 1. @agentmemory/mcp shim silently degraded to 7-tool local fallback for sandboxed MCP clients (Flatpak VS Codeium / Roo Code) because the 500ms livez probe failed inside the sandbox network namespace and the catch swallowed the error. Probe now logs the URL, status, and reason to stderr; default timeout raised to 2000ms; AGENTMEMORY_PROBE_TIMEOUT_MS overrides the timeout; AGENTMEMORY_FORCE_PROXY=1 skips the probe and trusts AGENTMEMORY_URL outright for sandboxed clients that can reach the server through a known route but not the host loopback. Closes the #234 follow-up reported by @jcalfee. 2. Docker compose stack persisted state to an ephemeral container path. iii-config.docker.yaml used file_path: ./data/state_store.db, which the engine resolved against its container WORKDIR=/home/nonroot -- not the /data mount where iii-data is bound. State and stream stores were silently written to the container layer and lost on every docker compose down. Paths are now absolute (/data/state_store.db and /data/stream_store). Existing users need a one-time docker compose down -v before upgrade. 3. CLI banner leaked "which: no iii in ..." when iii wasn't on PATH. execFileSync default stdio inherits stderr; GNU which writes the miss line to stderr (exit 1). Switched to explicit stdio: ["ignore", "pipe", "pipe"] in whichBinary(). 4. docker-compose.yml now caps engine container log size at 30MB total (json-file driver, max-size=10m, max-file=3) so the iiidev/iii crash/restart spam reported by @satabd in #278 can no longer fill the host disk. The upstream engine spam itself needs filing against iii-hq/iii -- this is the compose-side guardrail. Version 0.9.6 -> 0.9.7 across package.json, packages/mcp/package.json, plugin/.claude-plugin/plugin.json, src/version.ts, src/types.ts ExportData literal, src/functions/export-import.ts supportedVersions, and the export round-trip test expectation. * fix(mcp): AGENTMEMORY_DEBUG flag + loud unexpected-shape warning @jcalfee on #234 confirmed the v0.9.6 fix (and PR #280's escape hatch) still surfaces 7 tools in Roo Code even though: - Flatpak share=network is on (probe succeeds via curl in-sandbox) - server's /agentmemory/mcp/tools returns 51 tools via curl - AGENTMEMORY_FORCE_PROXY=1 is set Roo Code shows 7 tools even when docker is down -- strong signal of client-side caching at the Roo Code layer, not a shim regression. But the shim has zero visibility into what it actually returns to the MCP client right now: success path is silent, only failure paths log. Add AGENTMEMORY_DEBUG=1 (or =true) so the shim writes to stderr: - which mode handle.mode resolved to (proxy vs local) and baseUrl - shape of the /agentmemory/mcp/tools response (keys + tools type) - count of tools returned to the MCP client - tool names in the local-fallback list if we hit it Also: previously, when the server returned a JSON shape that wasn't {tools: Array}, the shim silently fell back to the 7-tool local list with no log line. Now that path warns to stderr unconditionally, pointing at AGENTMEMORY_DEBUG=1 for inspection. This is diagnosis enablement -- not a fix for whatever is making Roo Code show 7 tools. Once @jcalfee can run the shim with AGENTMEMORY_DEBUG=1 and share the stderr output, we'll know whether the shim returns 51 (Roo Code bug) or 7 (deeper shim bug). |
||
|
|
13924d2f1b |
chore(release): v0.9.6 — search recall + MCP shim + hook latency (#272)
Three reliability fixes from #269/#270/#271: - search/recall surfaces saved memories (closes #265) - MCP shim proxies full server tool set (closes #234) - session/subagent hooks no longer block startup (closes #221) Also fixes packages/mcp version drift — was stuck at 0.9.4 through v0.9.5, now lockstepped with main. |
||
|
|
5fc73f2eb2 |
fix: surface saved memories in smart_search/recall (#265) (#269)
* fix(memory): fall back to KV.memories on search enrich miss (#265) mem::remember persists memories to KV.memories under a synthetic sessionId ("memory" or memory.sessionIds[0]). The BM25 index sees that synthetic sessionId, but KV.observations under it never has an entry — so both enrichment paths silently dropped every memory hit: - HybridSearch.enrichResults (powers /smart-search, memory_smart_search) - mem::search obsResults map (powers /search, memory_recall) Result: memory_save succeeds and the memory shows up in the viewer and GET /memories, but every search/recall returns []. The save→search flow was effectively write-only. Both sites now try KV.observations first, then fall back to KV.memories and coerce the Memory record into a CompressedObservation. Verified live end-to-end against an iii-engine 0.11.2 stack: prior to the fix /smart-search and /search both returned [] for a saved memory; after the fix the memory surfaces with score > 0 in both responses. Regression tests added to test/hybrid-search.test.ts and test/search.test.ts. * refactor(memory): extract memoryToObservation helper Three copies of the Memory→CompressedObservation conversion existed: hybrid-search.ts (added in #265 fix), search.ts, remember.ts. Consolidate into src/state/memory-utils.ts and import from all three callsites. Behavior unchanged. Full suite: 854 passing. |
||
|
|
6e011e56b6 |
fix: MCP standalone shim proxies full server tool set (#234) (#270)
`agentmemory mcp` capped non-Claude clients (OpenCode, Cursor, Gemini CLI, Cline, …) at the local 7-tool `IMPLEMENTED_TOOLS` set even when an agentmemory server was reachable. With `AGENTMEMORY_TOOLS=all` on the server the user expected 51 tools — the shim filtered the response down to whatever subset overlapped its hardcoded set (4 by default, 7 with the env var on). When a server is reachable, the shim now: - Delegates `tools/list` to GET `/agentmemory/mcp/tools` and returns the server's full advertised set verbatim. - Forwards any tool not in the local `IMPLEMENTED_TOOLS` set to POST `/agentmemory/mcp/call` (the server validates per tool). The local `InMemoryKV` fallback path is unchanged — it still implements only the 7 essential tools, and unknown tools throw a clear error pointing to `AGENTMEMORY_URL` when no server is reachable. Verified live end-to-end: - Pre-fix: shim → tools/list returns 4 tools (default) / 7 (TOOLS=all); `memory_lesson_save` → "Unknown tool". - Post-fix: shim → tools/list returns 51 tools when the server runs with TOOLS=all; `memory_lesson_save` → lesson created end-to-end via the shim's stdio JSON-RPC interface. Two regression tests added covering the proxy-forward path and the no-server failure mode. Closes #234. |
||
|
|
0c73d868be |
chore(release): v0.9.5 — search recall + plugin compatibility (#261)
Bug-fix patch focused on search recall correctness and plugin compatibility. Pins iii-engine to v0.11.2 because v0.11.6 introduces a new sandbox-everything-via-`iii worker add` model that agentmemory hasn't been refactored for yet — pin lifts once that refactor lands. Adds a hard guard against silent vector-index corruption, fixes BM25 indexing for memories saved via memory_save, and lands four Hermes plugin fixes. Per AGENTS.md release checklist: - package.json version 0.9.4 -> 0.9.5 - src/version.ts VERSION constant - src/types.ts ExportData version union - src/functions/export-import.ts supportedVersions Set - test/export-import.test.ts assertion - plugin/.claude-plugin/plugin.json version - CHANGELOG.md detailed entries with contributor shoutouts Headlines (full detail in CHANGELOG): Fixed: - BM25 search now indexes memories saved via memory_save (#258, #257) Thanks @Nizar-BenHamida for the precise repro. - Embedding providers no longer silently corrupt the vector index when an API returns wrong-dimension vectors (#248, #247, #256) Thanks @AmmarSaleh50 for issue + fix + tests. - Hermes handle_tool_call returns JSON strings, not raw dicts (#255, #254) Thanks @KyoMio for the Anthropic-protocol repro. - Hermes status reflects real service state on systemd installs (#253, #250) Thanks @OptionalCoin for tracing it to env-source divergence. - Hermes hooks accept passthrough kwargs (#252, #249) Thanks @OptionalCoin again for the log analysis. - agentmemory demo now seeds observations correctly (#251, #229) Thanks @seishonagon for root-cause analysis. - LLM compression / summarization timeouts increased (#213) Thanks @xuli500177. - Pi / OpenClaw / Hermes integration plugin fixes (#230) Thanks @deepmroot. Changed: - iii-engine pinned to v0.11.2 across every install path (#260). v0.11.6 introduces a new `iii worker add` sandbox model that agentmemory still pre-dates; pin lifts when we refactor agentmemory to register as a sandboxed worker. Override with AGENTMEMORY_III_VERSION=<version> for users who've migrated manually. - README documents iii worker add extension surface (#242). - README iii Console install/launch commands corrected (#243). Validated: 852/852 tests pass, npm run build clean. |
||
|
|
1a035383de |
fix(memory): index memories into BM25 + backfill on startup (closes #257) (#258)
memory_save returned 200 with a valid memory ID, but every subsequent memory_smart_search and memory_recall returned empty results. Direct REST /agentmemory/smart-search showed the same — confirming the bug was not in the MCP layer. Root cause: mem::remember in src/functions/remember.ts wrote the new Memory to KV.memories but never called getSearchIndex().add(). The same pattern that #228 fixed for vectors-on-observe was missing here for BM25-on-remember. Compounded by rebuildIndex() walking only sessions/ observations and skipping KV.memories, so even a restart-rebuild couldn't recover indexed memories. Three changes: 1. src/functions/remember.ts — synthesize a CompressedObservation-shaped record from the saved Memory (title + content + concepts + files) and add it to BM25 right after kv.set(). Wrapped in try/catch so an indexing hiccup never blocks the durable save. 2. src/functions/search.ts — rebuildIndex() now walks KV.memories before sessions/observations, so a fresh rebuild covers the full corpus. Skips memory.isLatest === false so superseded entries don't pollute results. 3. src/index.ts — startup backfill for users upgrading from <0.9.5: when the persisted BM25 is non-empty (no rebuild triggered) but legacy memories were never indexed, walk KV.memories and add anything missing. SearchIndex.has(id) is the new idempotency gate. indexPersistence.scheduleSave() persists the augmented index. Tests in test/remember-bm25-index.test.ts cover: - SearchIndex.has() before/after add - A saved memory is findable by keyword (the case the bug broke) - The exact repro from the issue (mem_moy3u6ua_..., query "BM25 test") - Concept-only matches (no title/content overlap) Out of scope (file as follow-up): - Removing superseded memories from BM25 when mem::cascade-update fires. Currently relies on isLatest filter at result time; not perfect but doesn't regress recall. - Memory restore via IndexPersistence (covered transparently by the startup backfill, but a dedicated round-trip test would be tighter). Reported by @Nizar-BenHamida with full repro + log capture. |
||
|
|
ab4a16664e |
fix(embedding): guard provider responses against dimension mismatches (#248)
* fix(embedding): guard provider responses against dimension mismatches
Closes #247.
Embedding providers in src/providers/embedding/ trust that the API
returns vectors matching their declared dimensions. None of them check
result.length === this.dimensions. When that breaks, the failure is
silent: src/state/vector-index.ts:10 returns 0 from cosineSimilarity on
length mismatch, so a wrong-size vector gets stored, never matches
anything, and the affected memory becomes invisible without a single
error surfacing.
Add a single dimension-check wrapper at the EmbeddingProvider boundary
in createEmbeddingProvider() / createImageEmbeddingProvider(). Every
provider inherits the guard for free; new providers added later are
covered automatically. Throws a descriptive error naming the provider,
the call site (embed / embedBatch[i] / embedImage), expected vs got
dimensions.
Tests cover the happy path and each method's mismatch path.
Signed-off-by: ammarsaleh50 <ammar.alammar23@gmail.com>
* fix(embedding): preserve provider prototype in dimension-guard wrapper
Address CodeRabbit review on #248: the wrapper built a plain object
instead of preserving the provider's prototype chain, which broke the
existing toBeInstanceOf(GeminiEmbeddingProvider) /
toBeInstanceOf(OpenAIEmbeddingProvider) checks against
createEmbeddingProvider() in test/embedding-provider.test.ts.
Switch the wrapper to Object.create(provider) so the prototype chain is
preserved. `name` and `dimensions` fall through to the underlying
provider; only `embed` / `embedBatch` / `embedImage` are overridden
to insert the dimension check.
Add a regression test asserting that `withDimensionGuard` preserves
`instanceof`.
Signed-off-by: ammarsaleh50 <ammar.alammar23@gmail.com>
* fix(persistence): refuse to load vector index when persisted dim mismatches active provider
The factory-boundary dimension guard in this PR catches wrong-dim
vectors on the live-API write path. The persistence restore path is the
symmetric on-ramp:
IndexPersistence.load() at src/state/index-persistence.ts:62-66
deserializes vectors directly from KV with no dimension check.
If a user persists an index built against an N-dim provider and then
swaps embedding configuration (EMBEDDING_PROVIDER, OPENAI_EMBEDDING_MODEL,
local model upgrade, etc.), the restore brings old-dim vectors back
into a freshly-instantiated VectorIndex while live observations write
new-dim vectors alongside. cosineSimilarity returns 0 on every cross-
dim pair — same silent recall degradation #247 documents, just on a
different on-ramp.
This commit adds:
1. VectorIndex.firstDimensions() — exposes the dimension of any stored
vector (or 0 if empty). All vectors in a single index are expected
to share a dimension; the first entry is representative because the
live-write path is now gated by the guard added earlier in this PR.
2. A startup check in src/index.ts after IndexPersistence.load(). When
the persisted index has a different dimension than the active
provider, the default behavior is to refuse to start with a clear
error message:
[agentmemory] Refusing to start: persisted vector index has
dimension 384, but the active provider (openai) declares 1536.
Loading would silently corrupt search (cross-dimension cosine
returns 0). Choose one:
- Re-embed the existing index against the new provider, then start.
- Set AGENTMEMORY_DROP_STALE_INDEX=true to discard the persisted
vectors and rebuild from live observations.
- Switch the embedding provider back to the one that wrote the index.
3. Opt-in escape hatch: AGENTMEMORY_DROP_STALE_INDEX=true logs a warning,
discards the persisted vectors, and lets the live path rebuild over
time. Friendlier for users who deliberately swap providers.
Test: VectorIndex.firstDimensions() returns 0 for empty and the correct
size for populated indexes (small + 1536-dim).
Closes #256.
* fix(persistence): walk every persisted vector when validating dimensions
Review feedback on
|
||
|
|
94fc1191ef | chore: bump version to 0.9.4 + add CHANGELOG (#216) |