`getProfile`, `documentList` and `memoryForget` all declare `required: []`,
so a model may legitimately call them with no arguments at all. The OpenAI
API serialises that as `arguments: ""`, and `parseToolArguments` handed the
empty string straight to `JSON.parse`, so every no-argument call came back as
{"success":false,"error":"Invalid JSON arguments for getProfile"}
Those three tools were unreachable in their documented no-argument form.
The same gate also lets non-object JSON through. `"null"` parses cleanly and
then rejects in the destructuring parameter of every tool function --
`TypeError: Cannot destructure property 'containerTag' of 'object null'` --
which escapes `executeToolCall`, since it has no catch, and fails the whole
request. That is precisely the throw #1488 added this gate to contain. `"5"`
and `"\"text\""` are quieter but worse: they destructure to `undefined` and
call the API with no container tag at all.
Treat blank arguments as `{}`, and require the parsed value to be a non-null,
non-array object. Malformed JSON still returns the tool error #1488 added.
Adds eight regression tests. Six of them fail against the current
implementation -- two on the blank-argument path and four on the non-object
path, one carrying the raw TypeError. The two guard tests, malformed JSON and
an ordinary well-formed call, pass both before and after, so the behaviour
`supermemoryProfileSearch` in `shared/memory-client.ts` is the only
Supermemory HTTP call in this package with neither a request timeout nor
redirect handling. The identical `/v4/profile` call in
`openai/middleware.ts` sets both, and `/v4/conversations`
(`conversations-client.ts`) and `/v4/memories` (`shared/forget-memory.ts`)
each set a 30s budget.
Two consequences:
- **Unbounded request.** A timeout only applied when the caller supplied a
signal. `withSupermemory` passes one (5s), but `buildMemoriesText` is
called with no signal by the Mastra processor and the VoltAgent
middleware, and by the exported `buildMemoriesText` / `addSystemPrompt`
helpers. `fetch` has no default deadline, so a stalled connection blocks
the agent turn indefinitely — the failure both integrations' surrounding
try/catch is written to absorb, but which never surfaces as an error.
- **Redirects followed.** The request carries `Authorization: Bearer
<apiKey>`; a 3xx from a misconfigured or attacker-influenced `baseUrl`
was followed silently rather than refused.
Apply a 30s `PROFILE_REQUEST_TIMEOUT_MS` unconditionally and set
`redirect: "error"`. A caller signal is composed with the timeout via
`AbortSignal.any` rather than replacing it, so a caller-side budget can
only shorten the request, never leave it unbounded — the wrapper is kept
separate so the composition is stated once rather than re-derived at the
call site.
`src/shared/memory-client.test.ts` existed but was absent from the
`test:unit` file list CI runs, so its assertions never ran on a pull
request; add it alongside the new coverage.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L7GkmUn6skD6dCzKtcHDbe
Same pattern as forgetMemoryRequest: `options?.signal ?? AbortSignal.timeout(...)`
dropped the 30s bound whenever a caller supplied its own signal. Compose the
two with AbortSignal.any so the caller signal adds cancellation instead of
replacing the timeout.
`forgetMemoryRequest` combined the caller's signal and the 30s abort with
`??`, making them mutually exclusive. Passing a cancellation signal removed
the timeout, so a hung `DELETE /v4/memories` could wedge the tool call again
— the exact condition #1451 set out to remove. There was also no way for a
caller to ask for both cancellation and a timeout.
Compose the two with `AbortSignal.any` instead of choosing between them.
`AbortSignal.any` is available in Node 20.3+, Bun and workerd.
No production call site passes `options` today (`ai-sdk.ts` and
`openai/tools.ts` both omit it), so this was latent rather than live.
The existing test asserted the buggy behaviour (`init.signal` being the
caller's own signal), so it is replaced by two tests that pin the composed
semantics: aborting the caller aborts the request, and the timeout leg still
aborts the request on its own. Both fail against the previous implementation.
Fixes#1549
Three places where ClaudeMemoryTool diverges from the documented
memory_20250818 wire format:
- rename sends old_path/new_path, not path. handleCommand validated
command.path, so every rename coming from a real model died with
"Cannot read properties of undefined (reading 'startsWith')".
path is still accepted as the source for existing callers.
- insert_line means "insert after this line" (0 = top of file), but we
spliced at insertLine - 1 and rejected 0, so every insert landed one
line above where Claude asked and inserting at the top was impossible.
- str_replace with new_str omitted is a deletion per the spec; we
rejected it.
The new tests mock the supermemory client so they run without an API
key. Also fixed the rename example in the docs, which showed the same
path shape the code expected.
Anthropic's memory_20250818 spec defines insert as: insert_text is inserted
AFTER line insert_line, 0 inserts at the beginning of the file, and the valid
range is [0, n_lines]. The implementation treated insert_line as a 1-based
insert-BEFORE index with range [1, n_lines + 1].
Since the caller of this tool is Claude itself, which is trained on the spec
semantics, every model-driven insert landed one line earlier than intended,
insert_line: 0 (insert at top of file) was rejected as invalid, and
insert_line: n_lines (append) inserted before the last line instead of after
it.
Fix the validation range to [0, n_lines], splice at insert_line directly
(0-based insert-after), and update the error and success messages to match.
One existing tool-operations test encoded the old insert-before behavior; its
insert_line is adjusted so its expected output is unchanged under spec
semantics. Adds four regression tests covering top-of-file, middle,
append, and both out-of-range directions.
- 402 now tells the agent the org is out of credits and links to console.supermemory.ai/billing (the old message said "memory limit" and pointed at the retired app).
- PostHog MCP analytics tags these calls `$mcp_error_type: out_of_credits` with `$mcp_is_error: false`, so an empty balance no longer inflates the MCP error rate.
The server is stateless HTTP, so every tool call landed in its own PostHog session. Enable conversation ids and let $mcp_conversation_id and $session_id through the metadata filter so echoed handles group calls.
## Summary
- Instrument the MCP v2 server with the pinned PostHog MCP Analytics SDK and replace the custom `mcp_tool_executed` wrapper with standard `$mcp_*` events.
- Send only allowlisted metadata, disable schema injection and exception autocapture, and use personless user IDs with person-profile processing disabled.
- Deliver events through immediate capture guarded by Cloudflare `waitUntil`.
## Verification
- The initial implementation passed the MCP typecheck, existing unit suite, Biome, and Wrangler dry-run bundle.
- A local `who_am_i` MCP call returned successfully and emitted a metadata-only `$mcp_tool_call` on the initial implementation.
- All five checks passed on the final `54a2384` head, including the Cloudflare MCP build.
Actual PostHog ingestion is not verified yet; this workspace still needs a PostHog project token and authenticated Supermemory MCP credential.
Document-to-memory links and actual `derives` relations were both emitted as `derives`, so they shared the same color and legend entry.
This separates structural document links into a `document` edge type, adds a dedicated theme color with `--graph-edge-document` support, and updates force-layout and level-of-detail handling to preserve existing structural behavior. The package and MCP widget legends/themes now distinguish document links from derived-memory relations.
Adds regression coverage for edge classification and validates the package plus its MCP consumer.
<!-- capy-badge:start -->
<a href="https://capy.ai/thread/jam_01M36F4MPFXZCA8J14T53YY029"><picture><source media="(prefers-color-scheme: dark)" srcset="https://capy.ai/badge/accent-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://capy.ai/badge/accent-light.svg"><img alt="Open in Capy" src="https://capy.ai/badge/accent-light.svg"></picture></a>
<!-- capy-badge:end -->
## Summary
The billing guide omits Max and contradicts the pricing page about Gmail access. Add Max ($100/month with $130 in monthly credits), correct the plan feature matrix, list all six current coding plugins as available on Free with credit-consuming usage, and update both remaining Scale-only requirements on the Gmail connector page to Max or above.
Clarify that paid-plan automatic top-up limits cap automatic credit purchases, not the total invoice, and correct the documented auto-topup update endpoint from PATCH to POST.
Companion website changes: https://github.com/supermemoryai/landing-2/pull/32
## Validation
- Compared prices, credit inclusions, connector gates, and top-up behavior with the console and API source on main. Verified that all six coding plugins are on FREE_TIER_PLUGIN_IDS and the authentication route bypasses the generic Pro gate for them. Usage still consumes plan credits.
- Checked Markdown table column counts and required Max entries; `git diff --check` passed.
- Confirmed the Gmail introduction, prerequisite, and troubleshooting requirement consistently say Max or above.
- Documentation-only change. A full Mintlify build was not run.
Crawler-access verification, AI-search benchmarking, and analytics/measurement work are excluded from this PR.
Before: npm serves 0.2.3 without the merged fixes.
After: merging publishes 0.2.4 with the theme, initial fit, and node settling fixes.
Validation: package typecheck and build passed.
**Before:** Console lost its theme and dots. Incoming nodes needed a drag to reorganize, fit missed later pages, and clicks or drag release could leave the layout moving.
**After:** Restore themed rendering with configurable dots. Automatically settle and fit incoming nodes, keep clicks from reheating forces, and cool the layout after drag release.
**Checked:** Package types/build and Console build linked to this package.
Console companion: [mono#3295](https://github.com/supermemoryai/mono/pull/3295).
## Summary
The `workflow_run` auto-fix job has write permissions and checks out the triggering PR branch. It now runs only when all three conditions hold:
- the tracked CI workflow failed on a pull request;
- the pull request branch belongs to this repository, not a fork;
- the triggering actor is not a bot account.
The same-repository check closes the privileged fork boundary. The generic `[bot]` suffix check covers Polylane, Graphite, Dependabot, and other GitHub App bot users without maintaining a name list.
## Validation
- `go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 .github/workflows/claude-auto-fix-ci.yml`
- YAML parse with the repository's installed parser
- Final diff audit: one workflow, +3/-1, no added comments
Human-triggered same-repository pull requests keep the existing auto-fix behavior.
chore(web): reduce the app to a redirect shell, drop the browser extension
app.supermemory.ai now forwards everything to the console: plugin, OAuth and invite paths get an immediate 308 with the query intact, everything else shows a short notice first. Removes the browser extension workspace.
chore(web): give the moved notice a proper design
Hostnames become the headline, one primary action, a draining line for the countdown, DM Sans and the dot-grid backdrop from the brand.
chore(docs): point docs and README at the console, drop stale sections
Docs and both READMEs now link to console.supermemory.ai for API keys. Removed the company-brain docs tab with a redirect, and trimmed the README app section.
chore(web): give the redirect five seconds
getDocument filtered on the caller's active space, so an ID from listDocuments in any other space returned "Document not found". With activeSpace unset the fallback is sm_project_default, which broke most cross-space reads.
The API already scopes document reads to the caller's org, so the extra filter added no protection. Verified locally against the mono API: own-space and cross-space IDs now resolve, foreign-org IDs still 404.