mirror of
https://github.com/rohitg00/agentmemory.git
synced 2026-09-14 20:16:33 +08:00
fix/engine-spawn-absolute-paths
485 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5649e9c647 |
fix: pass plugin mcp env overrides (#386)
* fix: pass plugin mcp env overrides Signed-off-by: laplace young <yangqk12@whu.edu.cn> * fix: simplify plugin mcp env templates --------- Signed-off-by: laplace young <yangqk12@whu.edu.cn> Co-authored-by: laplace young <yangqk12@whu.edu.cn> |
||
|
|
f5e67ea133 |
Add sudo for global installation command (#454)
* Add sudo for global installation command * Update README.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update README with EACCES retry instructions Added installation instructions for macOS/Linux users. * docs: drop backticks around package name inside bash code fence Backticks inside a ```bash fenced block are still copy-pasted literally by users, and bash interprets them as command substitution. The package name in the install line had decorative backticks that turn into a shell syntax error when pasted as-is. --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Rohit Ghumare <ghumare64@gmail.com> |
||
|
|
ec6282382b |
docs: clarify env file setup (#321)
Signed-off-by: aqilaziz <gonzes7@gmail.com> |
||
|
|
edd1ceb916 |
fix(cli): accurately display bound viewer port on splash screen (#560)
* fix(cli): accurately display bound viewer port on splash screen - Expose viewerPort and viewerSkipped state in /agentmemory/livez endpoint. - Update CLI readiness check to poll until the viewer port is bound or explicitly skipped. - Prevents misleading default port (3113) display on splash screen when the viewer falls back to another port. * fix(viewer): address CodeRabbitAI review |
||
|
|
8e313271c9 | Update index.html (#542) | ||
|
|
a9945144a3 |
fix(providers/openai): send explicit stream:false in chat completion body (#526)
OpenAI API spec defines `stream` as defaulting to false when absent, so
the current code (which omits it) should yield JSON. Some OpenAI-compatible
proxies disagree and default to text/event-stream, which crashes the
`response.json()` parser below with:
Unexpected token 'd', "data: {"id"... is not valid JSON
After a few of these in a row, the resilient wrapper's circuit breaker
trips and all subsequent compression calls fail with `circuit_breaker_open`,
silently disabling LLM-backed compression / summarisation / reflection.
Reproduced upstream in decolua/9router#1260: 9Router's `handleChatCore`
returns SSE unless `stream: false` is explicit. PR
https://github.com/decolua/9router/pull/1272 fixes the proxy side, but
sending the field explicitly here is defensive — other OpenAI-compatible
endpoints (older self-hosted proxies, vLLM compat shims, …) hit the same
spec gap.
No behavior change for spec-compliant endpoints (openai.com, Azure
OpenAI, well-behaved proxies): they already default to non-streaming
when `stream` is absent, so making it explicit is a no-op there.
Co-authored-by: Ptah-CT <221234802+Ptah-CT@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
3cb7f90894 |
fix: read tool_response instead of tool_output in PostToolUse hook (#561)
* fix: read tool_response instead of tool_output in PostToolUse hook Claude Code's PostToolUse payload sends the field as `tool_response`, not `tool_output`. The hook was reading `data.tool_output` which is always undefined, so `cleanOutput` was undefined, the observe request contained no `tool_output` value, and mem::compress consistently failed its XML schema validation (requires narrative >= 10 chars + facts >= 1). Fix: read `data.tool_response` with `data.tool_output` as a fallback so older integrations that emit the legacy field name keep working. Fixes #539 * style: remove explanatory comment per repo guidelines |
||
|
|
93d1bdd81a |
fix(deps): pin iii-sdk to 0.11.2 (closes #555) (#567)
* fix(deps): pin iii-sdk to 0.11.2 to avoid routing regression in 0.11.6 iii-sdk@0.11.6 changes nested behavior so that all /agentmemory/* routes return 404 against the iii-engine, even though both packages still satisfy the previous "^0.11.2" semver range. npm picked up the new version on `npm install -g @agentmemory/agentmemory` after 0.9.21 shipped, silently breaking installs. Two pin sites: 1. package.json — caret -> exact "0.11.2" so npm cannot drift forward on minor releases until the upstream regression is sorted. 2. src/cli.ts — `agentmemory setup` previously ran `pnpm up iii-sdk@latest` / `npm install iii-sdk@latest`, which would re-pull 0.11.6+ even after a freshly-pinned install. Both call sites now pin to 0.11.2 with a label referencing this issue. Tests (1081) + build pass against iii-sdk@0.11.2. Closes #555. * fix(cli): drop issue ref from iii-sdk pin label Source labels should describe what the code does, not point at issues that rot as the codebase evolves. Issue context lives in the PR body. |
||
|
|
e371be9d98 |
fix(codex): --with-hooks workaround for openai/codex#16430 (closes #509) (#564)
Codex Desktop currently does not dispatch plugin-local hooks.json even
though both CodexHooks and PluginHooks feature flags are stable +
default-enabled in codex-rs/features/src/lib.rs (openai/codex#16430).
MCP tools still work; lifecycle observations are silently missing.
Adds `agentmemory connect codex --with-hooks` which mirrors the bundled
hooks.codex.json into the user-scope ~/.codex/hooks.json:
- Resolves ${CLAUDE_PLUGIN_ROOT} to the absolute bundled plugin/ path
(user-scope hooks don't get plugin-root injection)
- Idempotent merge: previous agentmemory entries are stripped on
reinstall via the resolved scripts/ path prefix; unrelated user
hooks are preserved untouched
- Preserves matcher fields from the bundled manifest so PreToolUse
routing still works
- findPluginRoot walks up from import.meta.url to locate the plugin/
dir; works for both dist/cli.mjs (bundled) and src/ (dev) layouts
- Dry-run path previews both TOML and hooks.json changes
Closes #509.
|
||
|
|
7fb72f4010 |
feat(eval): pluggable benchmark harness with in-house coding-agent corpus (#562)
* feat(eval): pluggable benchmark harness with in-house coding-agent corpus Adds eval/ tree (outside files field so npm tarball stays thin) with Adapter interface, three reference adapters (grep / vector / agentmemory-hybrid), two benchmarks (LongMemEval _s public, coding-agent-life-v1 in-house 15 sessions), scoring (P@K, R@K, hit, top-gold-rank), NDJSON output, sandbox script. coding-agent-life-v1 published scorecard at docs/benchmarks/2026-05-20-coding-agent-life-v1.md: agentmemory-hybrid R@5=0.967 P@5=0.578 (100% hit) vs grep R@5=0.967 P@5=0.267. 2.2x better precision on identical input, sandbox-reproducible. Adapter contract: init(sessions, config) -> State; query(q, state, k) -> RankedDoc[] npm scripts: npm run eval:coding-life (no download, no API key for grep) npm run eval:longmemeval (needs OPENAI key + 278MB download) eval/scripts/sandbox.sh boots clean agentmemory + iii-engine on ports 3411/3412 with isolated data dir; tears down on exit. README headline updated. 1072/1072 tests pass + 5 new eval tests. * fix(eval): address review findings on benchmark harness - agentmemory adapter: prefer row.sessionId before observationToSession lookup - vector adapter: validate embedBatch response (length, indexes, non-empty rows) - coding-life: positive-int guard on --k; wrap query loop in try/finally so teardown runs - longmemeval: positive-int guards on --k/--limit/--stratify; per-question try/finally - load: throw on haystack_session_ids vs haystack_sessions length mismatch - score: P@K denominator is k (requested cutoff) not topK.length - sandbox.sh: guard rm -rf with non-empty + /tmp/ prefix check - README: drop unsafe rm "$(which iii)"; instruct ~/.local/bin + PATH instead; add language tag to repo-layout fenced block - sessions.json: fix "two-phase" -> "three-phase" wording mismatch |
||
|
|
e9dc710e56 |
ci: cross-platform matrix + paths-ignore + concurrency (#556)
* ci: cross-platform matrix + paths-ignore + concurrency 1. **OS matrix** — Linux + Windows + macOS, both Node 20 + 22. 6 cells, ~3min each, ~18min wall time. Direct test against the class of bug #487 caught: hooks crashing on Windows usernames with spaces. Pre-merge Linux-only CI meant that bug landed in main + a release. fail-fast: false so a flake on one cell doesn't mask whether the same failure reproduces elsewhere. 2. **paths-ignore** — skip CI runs on README / CHANGELOG / docs / website / assets / .md / .mdx pushes. ~half the runner minutes back on doc-only churn. Source / config / workflow changes always run. 3. **concurrency + cancel-in-progress** — PR force-pushes cancel in-flight runs instead of piling them up. Push to main protected (concurrency group still scoped to ref, no cancel for main pushes). Plus minor hardening: persist-credentials: false on the checkout step so the GITHUB_TOKEN doesn't land in .git/config. What was NOT lifted (rationale per plan): - Per-package reusable workflows (Rust/Python/Homebrew — non-TS). - License-header check (no per-file Apache banners in agentmemory). - CLA bot (defer until external PR volume justifies friction). - tsc --noEmit lint job (codebase has ~10 pre-existing type errors tsdown skips; gating CI on those would block every PR until fixed; tracked as separate cleanup). - Smoke test (`agentmemory demo + livez`) — defer to its own PR with its own validation cycle. - Codecov badge — defer until baseline is set. * ci(windows): force bash shell so build script's POSIX idioms work Windows runners default to cmd.exe for npm run scripts; the build script uses POSIX patterns the build script's exit codes (`cp ... 2>/dev/null || true`, `mkdir -p`) that cmd doesn't parse. ubuntu + macos already use bash by default so this is Windows-only behaviour change. Alternative: rewrite the build script in Node. Bigger lift, not minimal. * ci(windows): point npm script-shell at git-bash before build `shell: bash` on the step only sets the shell for the step's own runner; `npm run` still spawns its inner script via npm's `script-shell` config, which defaults to cmd.exe on Windows. Configure npm to use Git-Bash (preinstalled on GitHub-hosted Windows runners) so `npm run build` and `npm run test` execute the build script the same way ubuntu + macos do. Step is gated on `runner.os == 'Windows'` so it's a no-op on the other matrix cells. * ci: drop windows-latest from matrix (obsidian-export hardcoded POSIX paths) Windows runners fail on test/obsidian-export.test.ts because the test + src hardcode `/tmp/...` POSIX paths that don't resolve on the D:\ drive Windows uses. Fixing it cleanly requires reworking src/functions/obsidian-export.ts to use os.tmpdir() + path.join, which is a separate scope. Drop windows from the matrix for now. Ship ubuntu + macos coverage (real darwin/linux divergence catch) and file a follow-up to make obsidian-export cross-platform so Windows can be added back. * test(fs-watcher): bump waits to 1500ms + describe retry for macos fsevents flake |
||
|
|
1838f4d74c |
chore(release): v0.9.21 (#551)
Quality + integration wave. Bundles 11 PRs since v0.9.20: Contributor feature: - #237 OpenCode plugin with 22 auto-capture hooks (@cl0ckt0wer) Bug fixes (9): - #516 memory_recall endpoint + format/token_budget (@serhiizghama, closes #507/#440) - #461 env-file AGENTMEMORY_DROP_STALE_INDEX flag honored (@honor2030, closes #456) - #487 Windows hook path quoting (@honor2030, closes #477) - #517 viewer IME composition guard (@jonathanzhan1975) - #472 chunk large sessions for LLM context window (@efenex) - #473 surface lessons in smart-search + diagnose tally (@efenex) - #486 declare all Hermes plugin hooks (@honor2030) - #500 rebuildIndex non-blocking on boot (@efenex) - #504 batched embed in rebuildIndex (25h -> 3h) (@efenex) - #491 cli skip onboarding without tty (@honor2030) Upstream-installer revert: - #546 drop --next workaround now that iii-hq/iii#1660 shipped 1067/1067 tests pass across 95 files.v0.9.21 |
||
|
|
cb2e0131dc |
fix(visibility): surface lessons in smart-search + tally per-store in diagnose (#473)
* fix(visibility): surface lessons in smart-search + tally per-store in diagnose
Two related UX gaps in the memory layer's reflection surfaces. A consumer
that calls `memory_lesson_save` and gets `success:true` reasonably expects
to find the lesson via `memory_smart_search` ("did my save land?") and
to see it counted in `memory_diagnose` ("what's in the store?"). Neither
was true: lessons live in their own KV store (`KV.lessons`), and both
diagnostic surfaces only looked at `KV.observations` / `KV.memories`.
A 4,350-lesson store could read as "memories: 0" on diagnose and return
zero hits on smart_search — the trust-shock that prompted this fix.
A) mem::smart-search: also return lessons in the compact response.
- New optional `project` and `includeLessons` (default true) params.
- Delegates lesson scoring to the existing mem::lesson-recall via
sdk.trigger, so confidence + recency weighting stays consistent
with mem::lesson-recall (no duplicate scoring logic).
- Lessons come back in a separate `lessons` field on the response,
not merged into `results`. Existing consumers reading `results`
are unaffected; new consumers can read `result.lessons` too.
- Content truncated to 240 chars in compact mode (full content
remains available via mem::lesson-recall directly).
- Lesson-recall failures are soft: log + return empty lessons,
observation results still flow through.
B) mem::diagnose: add per-store tally categories for lessons,
summaries, semantic, procedural, crystals, insights. Mirrors the
existing `memories` pattern: count + light consistency check
(confidence range for scored memories; non-empty title/narrative/
steps for the rest). Each new category is in ALL_CATEGORIES so
`--categories lessons` filtering works as expected.
The empty-system pass count goes from 8 to 14 (8 original + 6 new
stores). Test updated accordingly.
- src/types.ts: add CompactLessonResult
- src/functions/smart-search.ts: lesson recall + merge (single-call
path unchanged, expand mode unchanged)
- src/functions/diagnostics.ts: six new category blocks before mesh
- test/smart-search.test.ts: 6 new cases (lesson inclusion, content
preview truncation, includeLessons=false opt-out, project filter
passthrough, soft-fail on recall error / non-success response)
- test/diagnostics.test.ts: 7 new pass/warn cases for each new
category + filter check; empty-system pass count bumped 8→14
43/43 tests pass.
* fix(diagnostics): defensive guards on new validators (CodeRabbit #473 review)
CodeRabbit flagged two patterns in the per-store validators added in
the parent commit:
1. .trim() on .title / .narrative was unconditional — a corrupted row
with title=null or title=42 would throw, abort the whole diagnose
run, and silently skip every later category. Add typeof guards.
2. confidence range checks were `< 0 || > 1` which silently passes
NaN and Infinity (NaN < 0 is false, NaN > 1 is false → "healthy").
Add Number.isFinite(...) prefix so corrupted scored rows surface
as warnings instead.
Applied across all 6 new validators: lesson confidence, summary title,
semantic confidence, crystal narrative, insight confidence.
Tests added in test/diagnostics.test.ts under "defensive row-shape
handling": NaN confidence on a lesson, null summary title (verifies
diagnose still completes and later categories still execute),
undefined crystal narrative, Infinity / NaN on insight + semantic.
34/34 tests pass.
|
||
|
|
e68d4ebe9b |
fix(summarize): chunk large sessions to fit LLM context window (#472)
* fix(summarize): chunk large sessions to fit LLM context window JSONL-imported sessions can have far more observations than the 500-cap MAX_OBS_PER_SESSION that constrains native sessions. mem::summarize previously built one prompt containing every observation and shipped it as a single LLM call, which exceeded the provider's context window for sessions >~7,000 observations and returned an unhelpful 400 from upstream — silently leaving large bulk-imported sessions out of the semantic tier. Approach: map-reduce inside mem::summarize. - Sessions ≤ SUMMARIZE_CHUNK_SIZE (default 400) take the legacy single-call path with no overhead - Larger sessions are split into chunks, each summarized with the existing per-session prompt in parallel batches of SUMMARIZE_CHUNK_CONCURRENCY (default 6), and partial summaries merged via a new REDUCE_SYSTEM prompt - Per-chunk retry-once on transient parse / provider errors - Persistently-failing chunks are skipped (not propagated) so a flaky chunk doesn't waste 30+ already-completed LLM calls on the same session - Bail with too_many_chunks_skipped only if >50% of chunks fail Companion operator tool: scripts/backfill-imported-sessions.sh walks jsonl-imported sessions and POSTs mem::summarize per session, with project / agent / obs-count filters, cost estimation, and per-failure payload dumping for debugging provider rejections. Validated locally against a real corpus: - 5,392-obs session (14 chunks, c=6): 39s - 10,704-obs session (27 chunks, c=6): 34s - 105,966-obs session (265 chunks, c=50): handler completes server-side and persists - 52-session bulk backfill → 25 new semantic facts + 6 new reflect insights produced by consolidate-pipeline Known limit: iii-engine has a hardcoded 180s function-invocation timeout. Sessions large enough that chunked summarize wallclock exceeds that will return a timeout/500 to the HTTP client even though the handler completes and persists server-side. High-RPM providers (Novita / DeepInfra / DeepSeek typically allow 100+ concurrent) can raise SUMMARIZE_CHUNK_CONCURRENCY to push the cliff well past any realistic session size. True fix is an async-job pattern; left as follow-up. - src/prompts/summary.ts: add REDUCE_SYSTEM + buildReducePrompt - src/functions/summarize.ts: chunking, retry, skip, parallelism - test/summarize.test.ts: 9 cases covering single-call path, chunking, env-override, retry-then-success, persistent skip, too-many-skipped bail, provider error after retry, concurrency - .env.example: document SUMMARIZE_CHUNK_SIZE / _CONCURRENCY - .gitignore: agentmemory-debug/, data-*/ (operator artefacts) - scripts/backfill-imported-sessions.sh: bulk-import backfill tool 9/9 new tests pass; existing tests untouched. * fix(summarize): address CodeRabbit review on #472 Four nits flagged by the automated reviewer, all worth fixing: - scripts/backfill: add curl --connect-timeout + --max-time profiles (META_CURL_OPTS vs WORK_CURL_OPTS). Metadata reads fail fast and retry on transient blips; LLM-backed work calls get a wide 30-min cap and no retry (retrying a half-finished LLM job double-spends). - scripts/backfill: sanitize sessionId before joining with DEBUG_DIR in dump_failure() (otherwise a session id containing `/` or `..` could escape the debug dir). UUIDs in practice, but the server doesn't enforce that. - scripts/backfill: switch the observations query to `--get --data-urlencode "sessionId=$id"` so special characters can't corrupt the query string. - scripts/backfill: guard `jq` on summarize + consolidate responses with `jq -e . </dev/null 2>&1` first. iii's HTTP layer occasionally returns non-JSON (HTML 5xx, empty body on timeout). Without the guard, `set -e` aborts the whole backfill loop on a single bad response — now it logs `invalid_json_response` and moves on. - test/summarize.test.ts: fix `vi.mock("./audit.js", ...)` path to `"../src/functions/audit.js"`. The old path resolved to `test/audit.js` (nonexistent), so the mock was a silent no-op. Tests passed anyway because `safeAudit` writes to a mocked KV. 9/9 tests still pass; backfill dry-run still resolves the corpus cleanly. |
||
|
|
6c2a689daa |
fix(rebuild): batch embed calls in rebuildIndex (25h → 3h on large corpora) (#504)
* fix(rebuild): batch embed calls in rebuildIndex (25h → 3h on large corpora) rebuildIndex called `await vectorIndexAddGuarded(...)` per memory and per observation. Each call is one HTTP round-trip to the embedding provider for a single input. On a 500k-observation imported corpus against an embedding endpoint with even modest latency, that's serial 100-200ms per call = 14-28 hours of wallclock. The new non-blocking rebuild path (#500) made this no longer block boot, but the rebuild itself still takes the same wallclock. Add `vectorIndexAddBatchGuarded()` next to the existing per-item helper, accepting an array of items and calling `provider.embedBatch()` once. For batchable endpoints (vLLM, Triton, OpenAI's `/v1/embeddings` all accept an `input` array), latency for N items is roughly the latency of a single embed because network + GPU setup amortize. Refactor `rebuildIndex` to accumulate items into a buffer and flush every REBUILD_EMBED_BATCH_SIZE (default 32). BM25 add stays per-item-synchronous; only the vector path is batched. Validated against a vLLM Qwen3-Embedding-8B endpoint: - single embed: 175ms - batch-of-32: 737ms (= 23ms/item amortized, ~7.6× speedup) - projected backfill time for 500k obs: 25h → 3h Per-item failure shape is preserved: - whole-batch network/provider error → all skipped, single warn line (vs N warns previously when the same error hit every item) - per-item dimension mismatch → that item skipped, others continue - rebuildIndex return value unchanged (count of attempted items) Override knob: - REBUILD_EMBED_BATCH_SIZE (default 32) — set lower for endpoints with small per-request input limits, higher for endpoints that prefer larger batches. Set to 1 to fall back to the per-item path. 39/39 existing tests in search-index/vector-index/remember-bm25-index pass unchanged. Related: #500 (non-blocking rebuildIndex), #503 (separate embedding base URL). * fix(rebuild): per-item vi.add try/catch to preserve soft-fail Restores the pre-batch soft-fail behavior — a single failing vi.add() no longer aborts the entire rebuild batch. Failures are logged and counted toward fail, just like dimension mismatches above. |
||
|
|
c6a1fec2f1 |
fix(boot): make rebuildIndex non-blocking so viewer + later boot steps run (#500)
mem::observe's boot flow had this sequence in main():
1. registerSearchFunction / registerContextFunction / ...
(sync — completes immediately)
2. restore persisted vector index from disk
3. await rebuildIndex(kv) ← blocks here
4. bootLog "Ready" / "REST API" / "MCP surface"
5. startViewerServer(...)
6. setInterval auto-forget / lesson decay / consolidation
rebuildIndex iterates every observation across every session and AWAITS
an embedding-provider call per record. On a large corpus + a rate-limited
embedding endpoint (e.g. 100 RPM), step 3 takes hours to days.
Everything that runs AFTER it — including startViewerServer — is
silently delayed for the same duration.
Symptoms in the wild:
- http://localhost:3113/ unreachable (no listening socket on the viewer
port) even on a freshly-started server
- `agentmemory doctor` reports "viewer-unreachable"
- log floods with `vector-index add: embed failed — skipping {429: ...}`
from the still-running rebuild burning rate-limit budget
- no error message — the worker stays alive serving HTTP because
sdk.registerFunction had already completed synchronously in step 1
Fix: detach rebuildIndex with `void` + .then/.catch instead of awaiting.
The index lazily fills in over time, search degrades gracefully (BM25
keeps working immediately, vector results fill in as the embed queue
drains), and the viewer comes up in seconds.
Repro on the operator side:
1. import a sizeable jsonl corpus (`mem::replay::import-jsonl`)
2. clear the persisted vector index so rebuildIndex runs on next boot
3. restart agentmemory with EMBEDDING_PROVIDER pointed at a rate-limited
endpoint (any OpenAI-compat with low RPM)
4. observe: REST API responds on :3111, but :3113 is never bound, and
the doctor's "viewer-unreachable" check fires until the rebuild
finishes (hours-to-days for a 300+ session corpus)
The 5-second non-fix workaround was a hard kill + restart; that just
re-entered the same hang.
No tests added — main() isn't unit-tested today and wiring up a fake
slow rebuildIndex + asserting the post-rebuild boot lines run early
would need the full worker mock harness. The change is one line and
the failure mode is dramatic; visual review + integration smoke covers
the regression risk.
|
||
|
|
c1c2c3a944 |
fix(hermes): declare all plugin hooks (#486)
* fix(hermes): declare all plugin hooks * test(hermes): compare manifest hooks to provider --------- Co-authored-by: honor2030 <19909783+honor2030@users.noreply.github.com> |
||
|
|
db9f000dca |
fix(cli): skip onboarding prompts without a tty (#491)
Co-authored-by: honor2030 <19909783+honor2030@users.noreply.github.com> |
||
|
|
564c24bdb1 |
docs(readme): drop sponsor badge — sidebar widget covers it (#549)
GitHub auto-renders the "Sponsor this project" widget in the right sidebar from .github/FUNDING.yml (Sponsor button + heart icon + "Learn more about GitHub Sponsors" link). The README badge was redundant noise on the top badge row. Sidebar widget is the canonical surface — one path, one click. |
||
|
|
632fa3531d |
revert: drop GH Packages mirror, keep single canonical install path (#548)
Reverting the GH Packages publish from #545. GH Packages is a separate registry from npmjs.com — anyone installing `@rohitg00/agentmemory` from `npm.pkg.github.com` needs to point their registry there and authenticate, which is friction users don't have on the canonical `@agentmemory/agentmemory` install from public npm. The right-sidebar Packages widget on the repo page was the only motivation for the mirror. Acceptable to leave it empty — the single canonical install path is the better DX. - Drop `publish-github-packages` job from `.github/workflows/publish.yml` - Drop `packages: write` perm wording from the workflow comment block - Remove "GitHub Packages mirror" badge from README Manual follow-up (post-merge): delete the already-published `@rohitg00/agentmemory@0.9.20` from GH Packages registry via github.com/users/rohitg00/packages/npm/agentmemory/settings → Delete. |
||
|
|
6ed47c155a |
fix(funding): tighten FUNDING.yml to canonical single-line form (#547)
Sponsor button still missing from the repo page despite #545 merging. The committed FUNDING.yml started with 4 lines of `#` comments before the canonical `github: [rohitg00]` directive. GitHub's FUNDING parser documents only the canonical key-value form; leading comments shouldn't break it but some users have reported indexer lag when the file starts with non-data lines. Strip to the bare single-line form to match the documented schema and remove any ambiguity. Sponsor profile is enabled (github.com/sponsors/rohitg00 returns 200 + 'Sponsor @rohitg00' button), so the only remaining gap is GitHub's side-bar indexing. Tightening the file forces a re-parse. |
||
|
|
b4259229a6 |
feat(repo): add Sponsor button + GH Packages mirror for sidebar surface (#545)
* feat(repo): add Sponsor button + GH Packages mirror for sidebar surface
Three additions that make the repo page surface clearer + give users
a single place to fund the project:
1. `.github/FUNDING.yml` — `github: [rohitg00]` renders the "Sponsor"
button at the top of the repo + the Sponsor widget in the right
sidebar. Requires GitHub Sponsors to be enabled at
github.com/sponsors/accounts on the rohitg00 profile before the
link resolves (currently 404s — enable before merging this PR).
2. `.github/workflows/publish.yml` — new `publish-github-packages`
job runs after the existing public-npm publish completes.
Republishes the main package as `@rohitg00/agentmemory` to
`npm.pkg.github.com`. The repo's right-sidebar "Packages" widget
only surfaces packages on GitHub Packages, not packages on the
public npm registry, so this is what makes the sidebar widget
non-empty. Public npm remains the canonical install source;
GH Packages is purely a discovery surface.
- Uses built-in GITHUB_TOKEN, no new secrets needed.
- Rewrites package.json `name` + `publishConfig` in-runner via a
small node one-liner, publishes, then restores the original so
main isn't permanently scope-changed.
- Skip-on-already-published guard mirrors the existing public
publish steps.
- Marked `|| echo "non-fatal"` so a GH Packages hiccup never blocks
the canonical npm release.
- `permissions: packages: write` added at workflow level.
3. README badge row — added `npm downloads`, `GitHub Packages mirror`,
and `Sponsor rohitg00 on GitHub Sponsors` badges alongside the
existing `npm version` / `CI` / `License` / `Stars` row. The
sponsor badge is the same link the FUNDING.yml sidebar widget
uses; surfacing it in-README means readers who don't notice the
sidebar still see it.
Out of scope (asked, declined):
- Docker Hub / ghcr.io publish workflow. Not in this PR.
* ci(publish): scope write perms per-job + persist-credentials false
Inline review on #545 flagged that the workflow-level permissions
block granted `id-token: write` + `packages: write` to every job,
including ones that don't need them. Tightened to least-privilege:
- Workflow-level: only `contents: read`.
- `publish` job: adds `id-token: write` (required for `npm publish
--provenance` to mint a Sigstore OIDC token). The GH Packages
job doesn't inherit this.
- `publish-github-packages` job: adds `packages: write` (required
to push to npm.pkg.github.com). The public-npm publish job
doesn't inherit this.
Both `actions/checkout@v6` calls also pick up `persist-credentials:
false`. The publish steps never push back to the repo, so the
GITHUB_TOKEN doesn't need to land in `.git/config` after checkout.
Same posture both jobs.
Skipped from the same review pass:
- **Pin actions to commit SHAs.** Industry rule but introduces real
maintenance friction — Renovate/Dependabot don't auto-bump
SHA-pinned actions to new minors, so SHA pinning trades easy
semver tracking for stale-action drift. We stay on `@v6` major-tag
pins (GitHub publishes those via verified moving refs).
- **Disable setup-node cache.** `actions/setup-node@v6` defaults to
cache-off (the `cache:` input is opt-in). `package-manager-cache`
only auto-enables when `package.json` has a `packageManager` field
— agentmemory's doesn't (verified via `grep`). The fix is a no-op
on this workflow.
|
||
|
|
2a027d8952 |
revert: drop --next workaround for iii-console installer (upstream #1660 shipped) (#546)
v0.9.19 (#460 / commit
|
||
|
|
c2f231fe8b |
fix(mcp): memory_recall hits the right endpoint and forwards format/token_budget (#507) (#516)
* fix(mcp): route memory_recall to /agentmemory/search and forward format/token_budget memory_recall and memory_smart_search were sharing the smart-search endpoint, which always returns compact mode and silently drops the format and token_budget parameters that the tool schema advertises. Split the cases so memory_recall hits /agentmemory/search (which honors format) while memory_smart_search keeps its own endpoint. Default format to "full" for memory_recall so the documented behavior matches the wire call. Signed-off-by: serhiizghama <zmrser@gmail.com> * test(mcp): cover memory_recall endpoint, format forwarding, and defaults Two new proxy tests for issue #507: one asserts memory_recall calls POST /agentmemory/search with the format and token_budget fields, and never falls through to smart-search; the other pins the default format to "full" when the caller omits it. Signed-off-by: serhiizghama <zmrser@gmail.com> --------- Signed-off-by: serhiizghama <zmrser@gmail.com> |
||
|
|
48bf700f62 |
fix(hooks): quote plugin script paths (#487)
Co-authored-by: honor2030 <19909783+honor2030@users.noreply.github.com> |
||
|
|
a74c2880d5 |
fix(config): honor env file drop-stale-index flag (#461)
Co-authored-by: 이민재 <19909783+honor2030@users.noreply.github.com> |
||
|
|
3a24c326b8 |
fix(viewer): prevent IME composition interruption in search inputs (#517)
The viewer's five search inputs (graph, memories, lessons, actions, crystals) destroy and recreate their input DOM via innerHTML on every keystroke, which interrupts active IME composition sessions and makes non-Latin input (Chinese, Japanese, Korean) unusable. Additionally, the viewer's CSP includes script-src-attr 'none', which silently blocks the inline oninput=/onchange= handlers on the lessons, actions, and crystals panels. Those three search/filter controls have been non-functional under the strict CSP. This patch: 1. Adds a bindImeSafeSearch helper that guards on both an explicit compositionstart/compositionend flag and event.isComposing. compositionend triggers an immediate commit and sets a justCommitted one-shot flag to suppress the redundant trailing input event that browsers dispatch after compositionend. 2. Adds captureSearchFocus/restoreSearchFocus helpers to preserve focus and cursor position across innerHTML rebuilds, so multi-word IME input doesn't require clicking back into the search box after each commit. 3. Migrates all five search inputs to addEventListener via the new helpers, removing the CSP-blocked inline handlers on lessons, actions, and crystals. The actions panel's status filter <select> is also migrated for the same reason. 4. Unifies debounce delay to 200ms across all five panels. Verified via: - 8/8 jsdom + synthetic CompositionEvent regression cases (ASCII debounce, IME composing suppresses input, compositionend immediate commit + justCommitted suppression, post-IME ASCII resumes, fast typing coalesces, composition cancel returns to idle). - 8/8 manual browser cases on Windows + Chrome (Chinese pinyin commit, multi-word IME without re-focusing, English regression, all five panels, zero CSP violations in DevTools, cursor position retained). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
68fddd418e |
feat: OpenCode plugin with 22 auto-capture hooks (closes #236, #244) (#237)
* feat: OpenCode plugin with 22 auto-capture hooks (closes #156) - 22 hook handlers across session lifecycle, messages, tool lifecycle, parts, files, permissions, tasks, commands, and config - Two-layer enrichment pipeline: /context + /enrich via system.transform - Two slash commands: /recall and /remember - Full Claude Code hook parity documented with gap analysis * fix(plugin): use prompt_submit hookType so sessions get firstPrompt mem::observe checks hookType === "prompt_submit" to extract raw.userPrompt and set session.firstPrompt. The plugin was using "user_prompt_submit" which didn't match, so sessions were never named. * fix: address CodeRabbit review feedback on OpenCode plugin - Use ctx.worktree for projectPath instead of opaque project.id - Add Array.isArray guards before output.system/.context.push() - Only delete stashed files after successful enrich POST - Defensive JSON.stringify for undefined tool inputs - Per-session Map-based dedup sets to prevent unbounded growth - Fix negative duration_ms when time.completed is unset - Validate props.file and enforce MAX_STASHED_FILES in file.edited - Deduplicate /summarize call on session idle - Buffer early config events until session.created flushes them - Move contextInjectedSessions.add after successful context fetch - Add DEBUG-gated error logging to network helpers - Guard config input.agent/mcp/provider against non-object types - Fix MCP badge count 44→51 in plugin README * fix: add language identifier to fenced code block in plugin README * fix: store OpenCode session title as summary/firstPrompt on creation * fix(plugin): add session instruction injection and consolidation pipeline (closes #233) Three gaps from the Claude Code plugin port sweep: - Inject agentmemory usage instructions (memory_save, memory_recall, etc.) into the system prompt on first turn via experimental.chat.system.transform, replacing the skills mechanism that OpenCode lacks - Call /crystals/auto and /consolidate-pipeline on session.deleted, mirroring Claude's CONSOLIDATION_ENABLED behavior - Document MEMORY.md vs AGENTS.md architecture comparison (two-hop file bridge vs one-hop direct injection) Gap A (SubagentStop) is unfixable — OpenCode's SubtaskPart type has no completion/result fields. Gap C (Claude MEMORY.md bridge) is intentionally skipped — OpenCode uses direct injection. * fix(plugin): address adversarial code review — 7 critical/high fixes - Guard instructions push with Array.isArray check + fix TOCTOU race by moving contextInjectedSessions.add() before the await - Session-scope stashedFiles via stashFor() helper (was process-global, could cross-contaminate concurrent sessions) - Fix tool prefix in instructions (agentmemory_memory_ not agentmemory_) - Check typeof string before pushing .context into system arrays - Change olderThanDays: 0 → 7 in consolidation fire-and-forget - Increase fire-and-forget timeout from 5s to 30s (consolidation takes minutes, 5s was guaranteed to abort) * fix(plugin): scope stashedFiles.delete(sid) not .clear() on session.deleted * fix(plugin): address round 2 adversarial review — 3 critical fixes - Fix session cross-contamination: file.edited and tool.execute.before now use props.sessionID/input.sessionID fallback instead of only activeSessionId, preventing subagent hijack of parent stash - Fix contextInjectedSessions regression from round 1: move add(sid) to after instructions push (synchronous) but before context fetch (async), so failed /context calls don't permanently skip injection - Fix Map memory leak: prune session entries (stashedFiles, seenSubtaskIds, seenToolCallIds) when session.status goes idle, preventing unbounded growth for crash-killed sessions * fix(plugin): address round 2 remaining medium issues - Enforce MAX_STASHED_FILES cap on chat.message (was missing, could grow unbounded from 50+ file-ref messages) - Remove activeSessionId fallback from session.deleted; log warning when both info.id and sessionID are missing instead of guessing - Add subtaskSetFor/toolCallSetFor lazy-init wrappers matching stashFor pattern; fixes dedup failures for subagents spawned without a preceding session.created event - Guard chat.params against missing input.model (TypeError crash) * fix(plugin): address round 3 adversarial review — 10 HIGH/MEDIUM fixes - Add safeSlice() helper replacing all unsafe (v as string || "").slice() calls; handles objects/BigInt/circular refs via try/catch JSON.stringify - Restore activeSessionId fallback on session.deleted (if both info.id and sessionID missing, fall back like every other handler) - Fix message.updated to check info.id before info.sessionID (matches session.created/session.updated pattern) - Add props.sessionID to message.part.updated fallback chain - Guard undefined callID and subtask ID (prevent dedup silently dropping all subsequent undefined-ID events) - Cap todo.updated at 100 entries (match session.diff's slice pattern) - Fix duration_ms || → ?? (0ms genuine duration no longer falsifies) - Fix extractErrorMessage || → ?? (0/false error values preserved) - Replace extractErrorMessage.slice with safeSlice in retry handler * fix(plugin): address round 4 adversarial review — 6 CRITICAL/HIGH fixes CRITICAL: - Revert message.updated session ID resolution: info.id is the message UUID, not session ID. In message.updated, info is the message object (has role/tokens/modelID), not session info like in session.created. Use props.sessionID || info.sessionID || activeSessionId instead. - Fix post_tool_use_failure -> post_tool_failure: server types.ts defines "post_tool_failure" (no "use_"); compress-synthetic.ts classifies by that exact string. All error observations were misclassified as "other" instead of "error". - Fix contextInjected ordering: move add(sid) after the context fetch completes, not before. If /context times out, session is no longer permanently marked injected, allowing retry on next transform. HIGH: - Add process.cwd() fallback for projectPath (was null, causing silent 400s on every REST call when no workspace) - Guard session.start against null activeSessionId with early return (was sending sessionId: null to API) - Fix duration_ms: use typeof number checks instead of || 0 defaults; missing timing data now correctly reports null instead of 0ms * fix(plugin): address final adversarial blockers --------- Co-authored-by: xuli500177 <62830942+xuli500177@users.noreply.github.com> Co-authored-by: Trip <5579540+cl0ckt0wer@users.noreply.github.com> Co-authored-by: Rohit Ghumare <ghumare64@gmail.com> |
||
|
|
f1e7b2c577 |
fix(website): move Product Hunt badge into FeaturedIn (correct post_id 1144164)
Earlier PH badge URL pointed at post_id 773058 which returned 404 — the WebFetch I pulled the ID from grabbed a wrong number. Correct post_id is 1144164 per the official embed snippet from the PH "Embed" tab. Badge now resolves with HTTP 200 + Content-Type image/svg+xml against api.producthunt.com. Position fix: pulled the badge out of the Testimonials section (wrong place — buried below the fold) and added Product Hunt as a fourth entry in the existing FeaturedIn "AS FEATURED IN" bar at the top of the page where it sits naturally alongside AlphaSignal, Agentic AI Foundation, and Trendshift. - FeaturedIn ITEMS: Product Hunt entry added with the canonical featured.svg embed URL (theme=neutral to match the existing badges, no t= cache-buster since CDN cache is fine for a marketing surface) - FeaturedIn grid: 3-col → 4-col at desktop, 2-col at 980px, 1-col at 540px so the four cells stay balanced - next.config: api.producthunt.com added to remotePatterns so the Next image loader accepts the SVG badge - Testimonials: removed the broken badge row + the now-unused CSS (.badgeRow / .badgeLink / .badge); section keeps the use-cases + endorsements grid as before |
||
|
|
af1ba5b5e9 |
fix(website): update Peter Neyra quote to Cursor-backfill use case
PH discussion comment #5379518 is the specific, meatier framing of how Peter is using agentmemory: backfilled a full month of Cursor agent transcripts and saw the system pick up on pivots that he had moved away from. Concrete tool name + concrete time horizon reads much harder than the generic "tracking product pivots" framing that ended up in the earlier card. Also pointed the use-case card's href at the specific comment ID so hover-target lands on the right reply, not the thread top. |
||
|
|
255d8030e9 |
feat(website): split Testimonials into use-cases + endorsements + PH badge
The launch-thread quotes broke into two natural shapes — concrete use cases (Peter Neyra tracking product pivots, Pranav Prakash on two weeks of production use) deserve more weight than the shorter positioning endorsements. Top of section now embeds the official Product Hunt featured.svg badge so the live upvote count + day rank stays current without any client refresh. - Featured "HOW THEY USE IT" row: 2 large cards with a gold use-case tag chip + larger quote type - "WHAT THEY SAY" row: 4 compact cards in a 4-col grid - PH badge embedded at api.producthunt.com/widgets/embed-image/v1/ featured.svg?post_id=773058&theme=dark — live, no client JS - Responsive: use-cases 2→1 col at 980px, endorsements 4→2→1 |
||
|
|
57ceaf45c7 |
feat(website): add Testimonials section with PH launch-thread quotes
Six verbatim quotes pulled from the Product Hunt launch discussion, each linked back to the source comment for verification. Quotes cover the spectrum: concrete use cases (Peter Neyra on product-pivot tracking, Pranav Prakash on two weeks of production use), framing endorsements (Alper Tayfur, Mia Taylor on agentmemory's intent versus storage-only tools), and onboarding feel (Zoe Alexandra). Section header: "BUILDERS USING AGENTMEMORY" with the title "IN THE WILD." in the gold accent. 3-column grid above 980px, 2-column at tablet, single-column on mobile. Cards are clickable and route to the original PH comment thread. Slotted between Compare and Agents — social proof after the comparison table, before the agent-list grid. Matches the existing FeaturedIn aesthetic (charcoal border + iron fill + gold hover) so the section feels native, not bolted on. website/lib/generated-meta.json also picked up the v0.9.20 bump + the two new MCP tools shipped via #498 (memory_commit_lookup, memory_commits) — clean by-product of `npm run build`. - website/components/Testimonials.tsx: 6 verbatim quotes + attribution - website/components/Testimonials.module.css: 3/2/1 grid + card chrome - website/app/page.tsx: wired between Compare and Agents - website/lib/generated-meta.json: regenerated against v0.9.20 Site builds clean. |
||
|
|
08d781d431 |
chore(release): v0.9.20 — hotfix Codex Stop revert (#501)
v0.9.19 shipped #495 which chained session-end.mjs after stop.mjs on the Codex Stop hook. Field-testing surfaced the underlying issue: Codex fires Stop multiple times within a single conversation (once per assistant turn), so chaining session-end marked sessions as completed while later observations were still arriving. #501 reverts the chain. Stop returns to summarize-only behavior. The SessionEnd-shaped solution (a dedicated terminate event the agent sends only once on real session end) tracks at #493. 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 1034/1034 tests pass.v0.9.20 |
||
|
|
a953cadb62 |
Revert "fix: end Codex sessions on Stop hook (#495)" (#501)
This reverts commit
|
||
|
|
29e6ebdcc0 | docs(changelog): credit contributors on v0.9.19 entries | ||
|
|
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.
v0.9.19
|
||
|
|
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)v0.9.18 |
||
|
|
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> |