Commit Graph

2579 Commits

Author SHA1 Message Date
cursor[bot] 43672a9b80 chore(release): 13.24.7
chore(release): 13.24.7 — Chroma lock/owner, quota, grammar-once, chroma-mcp reap + post-tag fixes
v13.24.7
2026-09-11 01:23:24 +00:00
Alex Newman 69a12b2f96 Merge branch 'main' into cursor/version-bump-13-24-7-db81 2026-09-10 18:08:57 -07:00
Alex Newman d959572bde fix(setup): guard plugin deps on completeness, not node_modules existence (#3972)
* fix(setup): guard plugin deps on completeness, not node_modules existence

`ensurePluginDependencies()` decided whether to run `bun install` by asking
whether `node_modules/` existed. A tree that is merely present — but short
of the declared closure — satisfied that check and permanently skipped
repair on every subsequent Setup run.

Two trigger paths, and the second needs no corruption at all:

1. An install interrupted mid-fetch (network timeout, OOM, registry 5xx)
   leaves `node_modules/` behind incomplete.
2. A tree that was complete *for the version that created it*. `zod` was
   added to plugin deps after some users had already installed; their
   node_modules has been incomplete ever since, and no upgrade heals it
   because the stale tree is gitignored and gets re-seeded into each new
   cache version.

The worker then dies at boot on `Cannot find module 'zod/v3'` while memory
search keeps working — `mcp-server.cjs` bundles zod (build-hooks.js:519
hard-fails if it ever externalizes it) while `worker-service.cjs` has 19
external zod requires. So the plugin looks alive while capture is dead.
One reporter lost ~4 months of capture with no visible symptom.

Guard on completeness instead: every key of `package.json` `dependencies`
must resolve, with a `<dep>/package.json` fallback for bin-only packages
like tree-sitter-cli (gh #2730), plus the zod subpaths the worker requires.
This mirrors `verifyCriticalModules` (src/npx-cli/install/setup-runtime.ts:245),
which already applies exactly this contract on the npx install path but was
never reachable from the Setup hook. It cannot be imported here — this
script is standalone and dependency-free — so the probe is inlined and the
two are cross-referenced.

Two consequences fall out:

- The post-failure `rmSync` of node_modules is removed. It existed only
  because the existence guard would otherwise block retry forever; the
  completeness guard re-detects the gap on the next run, so deleting bought
  nothing while actively destroying a partial tree that still powers search.
- A zero exit from `bun install` is no longer trusted. It can exit 0 with a
  failed integrity check, so the closure is re-probed afterwards and the
  diagnostic reports what actually resolves.

The install diagnostic now names the unresolvable modules, which is what
stops this failure mode from being silent.

Fixes #3755. Refs #3604 (plan-16), #2730.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NT5K64VU4a7Kbc36oTVjyc

* fix(setup): keep the completeness probe inside the plugin's own node_modules

Greptile P1 on #3872, and it was right.

`require.resolve(dep, { paths: [nodeModulesPath] })` reads as tree-scoped
but is not. `paths` only seeds Node's lookup; resolution then walks every
ancestor directory and always consults the global folders
($HOME/.node_modules, $PREFIX/lib/node). Plugin roots live at
~/.claude/plugins/cache/thedotmack/claude-mem/<version>/, so a copy of a
dependency anywhere above them — or installed globally — answered for the
plugin's own.

Verified against the committed probe: a plugin whose node_modules is
completely EMPTY, with a valid zod one directory up, exits 0 with no output
and never runs the install. That is the #3755 bug reintroduced by the fix
for it, and it would have shipped silently.

Presence is now checked by statting `<node_modules>/<dep>/package.json`
directly, which cannot escape the tree. This is also the signal
scripts/check-postinstall-allowlist.js:75-78 already uses, and it handles
scoped names (split into path segments) and bin-only packages like
tree-sitter-cli (package.json present, no entry point — gh #2730) without
the bare-name/fallback dance.

zod's subpaths still need real resolution, since they are `exports` entries
that a present directory does not guarantee. Those are now accepted only
when the resolved file lands inside the plugin's own zod directory. Both
sides are realpath'd before comparison: bun can materialise node_modules
entries as links into a shared store, and Node returns the real path of
what it resolved, so a literal comparison would report a healthy linked
install as missing and loop the install forever. Containment uses
path.relative rather than string prefixing so a sibling like zod-extra is
not mistaken for being inside zod.

Regression test added; it fails against the previous commit's probe.

Note for follow-up: verifyCriticalModules (setup-runtime.ts:245) has the
same ancestor/global escape. It is far less dangerous there — a post-install
assertion that fails loud, not the gate deciding whether repair runs at all
— but worth tightening.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NT5K64VU4a7Kbc36oTVjyc

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 18:06:09 -07:00
Cursor Agent 4890e14c4b chore: bump version to 13.24.7
Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-11 01:05:17 +00:00
Alex Newman e8aa53db61 fix(openrouter): map the model list onto the native models[] fallback array (#3971)
CLAUDE_MEM_OPENROUTER_MODEL has always accepted an array, and
normalizeOpenRouterModel has always comma-joined it into a single `model`
string. OpenRouter rejects that: there is no model id containing a comma, so
the joined form is never anything a user asked for. Meanwhile OpenRouter's
native `models` fallback array — the mechanism that actually expresses "try A,
fall back to B" — was never used. That is #3829 item 3.

The first configured entry becomes `model` and the rest become the fallback
array, in priority order. A comma- or whitespace-separated STRING is the same
mistake typed a different way, so it is split too; blanks and repeats are
dropped, since a repeat would spend a fallback slot re-trying the model that
just failed. A non-string scalar still resolves to the shipped default, as
before.

Per OpenRouter's documented shape, `models` REPLACES `model` rather than
accompanying it, so the body carries one or the other and never both. The
request body moves into an exported buildOpenRouterRequestBody so that shape is
assertable without a network round trip — getting it wrong would fail silently
at exactly the moment failover was supposed to help.

`models` is sent only to openrouter.ai. A custom gateway reached through
CLAUDE_MEM_OPENROUTER_BASE_URL speaks plain OpenAI, where an unknown body field
is a 400 — the same reason `usage: { include: true }` is already gated. Such a
gateway now gets the first model instead of a rejected comma-joined string.

A single configured model is untouched: same `model` field, same body, no
`models` key. That is every install that has not opted in.

This addresses only item 3 of #3829. The per-attempt timeout and max_tokens
knobs (items 1-2, and #3794 / #3808 / #3796) and the unbounded observer history
(item 4) are separate. Note that the default this still falls back to,
xiaomi/mimo-v2-flash:free, is the deprecated id #3662 is about.

Co-authored-by: nasif-naseef <naseef771@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:04:37 -07:00
weiconghe cd5108c1db fix(windows): reclaim ghost listeners left by out-of-band worker deaths (#3900)
When the worker daemon dies out-of-band (crash, taskkill /F without /T), its chroma-mcp sidecar chain (uvx -> uv -> python) survives holding the inherited listening socket. The port stays LISTENING under the dead worker PID and every launcher refuses to start ('Port already in use, refusing to start duplicate' / 'Port in use but worker not responding to health checks') forever — hooks thrash until a human tree-kills the sidecar chain by hand (plan-15 #3603; reproduced 2026-09-07 and again by the probe that shaped this fix).

Changes:
- src/shared/port-reclaim.ts: detect a listener whose owning PID is dead and kill the surviving sidecar chain. Targets are found two ways: walking the parent chain down from the dead PID (Windows preserves parent links after death), plus a full-table scan matching the sidecar's --data-dir argument for the broken-chain case where uvx/uv exited on pipe EOF and only chroma-mcp/python survive one or two links below a dead PID. Every kill carries the start token from the same table read that discovered the target; a live owner is never touched.
- daemon duplicate gate + ensureWorkerStarted: reclaim the ghost before refusing to start, and clear the spawn cooldown when the reclaim succeeded (the cooldown's reason is gone).
- HealthMonitor: bounded (5s) HTTP probes so a ghost listener — which accepts TCP but never responds — cannot hang the liveness checks forever.
- Windows integration gate (worker-ghost-port-recovery): a detached fixture worker with a real chroma chain is killed out-of-band; the production ensureWorkerStarted must reclaim the port and start a replacement. Fails on main, where no reclaim exists.
- Unit tests for the parser, the ownership fingerprint and every reclaim decision branch.
2026-09-10 18:01:56 -07:00
Matt Nye aa8ac2e790 fix(oauth): honor CLAUDE_CONFIG_DIR for the Claude Code keychain entry and the SDK subprocess env (#3908)
Claude Code stores per-profile credentials on macOS under
"Claude Code-credentials-<sha256(configDir)[:8]>"; only the default
~/.claude profile uses the bare name. The reader used one fixed constant,
so a worker running under a named CLAUDE_CONFIG_DIR read the default
item, and the spawned SDK subprocess inherited whatever CLAUDE_CONFIG_DIR
the worker's own environment carried.

- paths.ts: DEFAULT_CLAUDE_CONFIG_DIR (literal default, env-independent)
- SettingsDefaultsManager / SettingsRoutes / docs: new setting
  CLAUDE_MEM_CLAUDE_CONFIG_DIR (empty = fall through to env, then default),
  allowlisted and validated on POST /api/settings
- oauth-token.ts: resolveEffectiveClaudeConfigDir (setting > env > default,
  tilde expanded, trailing separator stripped), deriveMacKeychainServiceName,
  readMacOsKeychain(serviceName, execImpl) with an injectable exec seam,
  threaded through readClaudeOAuthToken; darwin branch only — Windows/Linux
  readers stay on the bare name (their per-profile scheme is unverified)
- EnvManager.ts: buildIsolatedEnv stamps the effective dir onto the SDK
  subprocess env only; getAuthMethodDescription names the profile, never
  the token

Tests: 102 pass / 0 fail across the five affected files (71 on the clean
base); tsc clean; full suite unchanged versus the clean base.



Claude-Session: https://claude.ai/code/session_01RJEKSU5eVwnvBf4irjtP4E

Co-authored-by: Gwen Ives <gwen.ives@nyecorp.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 18:01:49 -07:00
kavish-19 a485a5254e fix(search): hydrate Chroma matches in relevance order, not by date (#3881)
A search returns the newest of up to 100 semantic candidates rather than
the most relevant ones, so an older observation that matches precisely
loses to a newer, vaguely related one — including when the query is the
older observation's own verbatim text. Raising the limit surfaces it
again, which shows it was a candidate all along and was dropped by the
date cut rather than by relevance.

hybridSemanticHydrate asks Chroma for 100 matches, which arrive in
distance order, then hydrates them with orderBy 'date_desc'. That
re-sorts the ranking away before the limit is applied. The same file
already does this correctly in performChromaSemanticSearch, which
hydrates with orderBy 'relevance' — the mode that preserves the
caller-provided id order.

Use 'relevance' at the three call sites that pass ids straight from
Chroma: searchObservations, getTimelineByQuery, and
searchChromaForTimeline. The last one selects the single timeline
anchor, where 'date_desc' picked the most recent candidate rather than
the top-ranked one, disagreeing with the FTS fallback directly beneath
it that returns the best match.

Closes #3876
2026-09-10 18:01:40 -07:00
kavish-19 9fac73788b fix(codex): re-inject memory after compact and clear (#3880)
In Codex, memory is never re-injected after a context compaction. The
SessionStart hook that runs `hook codex context` matches only
"startup|resume", so after a manual /compact or an auto-compaction the
hook does not fire and the session continues with an empty context. A
long-running Codex session loses all injected memory at its first
compaction and never gets it back.

Codex emits four SessionStart sources, not two — SessionStartSource in
codex-rs/hooks/src/events/session_start.rs is Startup, Resume, Clear,
Compact. clear and compact both hand the model a fresh context, which is
exactly when the injection has to run. The Claude Code config already
matches "startup|clear|compact" for the same reason.

Add the two missing sources to the matcher. The hook command is
unchanged and does not branch on source, so it injects on compact and
clear exactly as it already does on startup and resume.

Closes #3862
2026-09-10 18:01:33 -07:00
Matt Nye d07bb6cd77 fix(supervisor): parked slot-waiters follow the live concurrency cap; a provider switch restarts a parked generator (#3909)
waitForSlot captured CLAUDE_MEM_MAX_CONCURRENT_AGENTS once per waiter, so a
settings raise never released a parked generator, and a provider change only
logged "will switch after current generator finishes" — which a parked
generator never does (the idle monitor only runs once a slot is held). Live
result: six of seven sessions parked behind a cap of 1, and reverting the
provider setting processed nothing until a worker restart dropped ~426 items.

- process-registry: waitForSlot(number | () => number, signal?, sessionId?)
  re-invokes the getter on every recheck; slotWaiters carry sessionId /
  parkedSince / warnedParked; getParkedSlotWaiterCount and
  isSessionParkedForSlot exported; one WARN per waiter parked > 3 min
- ClaudeProvider: pass a settings-reading getter plus the session id
- SessionRoutes: a provider change aborts a PARKED generator
  (abortReason 'provider_switch'), awaits its exit chain, then restarts on
  the new provider via the shared admitAndStartGenerator path; mid-response
  generators unchanged; ensureGeneratorRunning serialised per sessionDbId
  (promise-map mutex) to close the concurrent double-start race;
  normalizeAbortReason + enum listings + telemetry docs gain provider_switch
- GeneratorExitHandler: provider_switch preserves the buffer like quota
- DataRoutes: /api/processing-status gains parkedSessions (additive)
- tests: parked-waiter release/FIFO/cap, provider-switch-while-parked,
  exit-handler, processing-status, double-start serialisation; shared
  singleton guards for the process registry and the quota-cooldown breaker,
  wired at top level in every file that drives them

Tests: 48 pass / 0 fail across the six affected files (800/0 under
--rerun-each=20); tsc clean; full suite's failing set unchanged versus the
clean base. A rare cross-test timeout in the provider-switch suite is
disclosed in the PR body.



Claude-Session: https://claude.ai/code/session_01RJEKSU5eVwnvBf4irjtP4E

Co-authored-by: Gwen Ives <gwen.ives@nyecorp.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 18:01:12 -07:00
Chris Yau e6e4d738f0 fix(worker): honor field optimizer deadline and compact Edit observer view (#3939)
* fix(worker): honor field optimizer deadline and compact Edit observer view

Plumb an AbortSignal through the field-compression path so a field-level
timeout can cancel the real provider request and stop retries instead of
leaking a background attempt.

- field-optimizer: withTimeout now creates an AbortController and hands its
  signal to the compressor; on timeout it aborts the in-flight work.
- OpenAICompatibleProvider/GeminiProvider/OpenRouterProvider/ClaudeProvider:
  thread the signal into query/compressField and forward it to withRetry or
  the SDK query, disabling retries when a caller-supplied signal is present.
- field-optimizer: add a conservative compactEditOutput view for Edit tool
  outcomes that strips redundant originalFile and patch lines while keeping
  the canonical oldString/newString/hunk locations intact; the raw tool
  payload stored in the DB is unchanged.
- Add tests/worker/field-deadline-wire.test.ts and
  tests/worker/edit-observer-view.test.ts.

* test(worker): make field-deadline-wire test event-driven for Bun 1.2.14

The fixed 450 ms assertion on  fails on Bun 1.2.14
because res.close does not fire within that window when the request body
is consumed and no headers are sent. The socket/keep-alive handling in
that version leaves the response object alive.

Observe the request's own abort/error and socket-close events instead.
These are runtime-agnostic and fire reliably on both Bun 1.2.14 and
Bun 1.4.1. Keep  and  assertions, so
retries and cancellation coverage are unchanged.

P2: https://github.com/thedotmack/claude-mem/pull/3939#discussion_r3965682436

* fix(worker): align Edit size checks with optimizer serialization
2026-09-10 17:59:52 -07:00
Alex Newman 22879ef523 fix(supervisor): reap chroma-mcp trees no worker owns at boot (refs #3905)
Co-authored-by: Stefan Nesiu Bedreag <stefan.nesiu-bedreag@gmail.com>
Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-10 17:52:30 -07:00
Alex Newman a51affa9ed fix(smart-file-read): build each grammar once instead of recompiling per query (fixes #3926)
`runBatchQuery()` shells out with `tree-sitter query -p <grammar-dir>`, and
`-p/--grammar-path` is documented as implying `--rebuild`: the CLI recompiled the
grammar from C on every invocation. Measured with tree-sitter-cli 0.26.9 on Linux
x86_64, one `smart_outline` over a 933-line TypeScript file cost 734/711/704 ms
across three runs, nearly all of it the compile. The artifact the CLI wrote to
its own cache was rewritten each time and never read back.

Grammars are now built once into <data-dir>/tree-sitter-libs and queried with
`-l <lib> --lang-name <language>`. The same three runs become 733/16/12 ms and
return the identical 30 symbols; the first call still pays one build.

Because `parseFilesBatch()` issues one CLI call per language group, the old cost
was a fixed ~0.7s per language per tool call regardless of batch size, paid alike
by smart_outline, smart_search and smart_unfold, and repeatedly by anything that
walks a repository.

The artifact lives in the data dir rather than in node_modules: an update
replaces node_modules wholesale, and those directories belong to the installer.
Staleness is an mtime comparison against src/parser.c and the optional scanner —
npm and bun both stamp installed files with the install time, so a plugin update
invalidates the previous artifact on its own. The check is re-run per call rather
than memoized, since four stats cost nothing next to the spawn they guard and a
memo would pin a long-lived MCP server to the grammar that was current at boot.

Every failure path falls back to the previous `-p` invocation: a build that does
not compile, or a grammar whose language function is not named after our language
key. The opt-out is remembered per language, so a mismatch costs one extra spawn
once instead of two per batch forever. All 24 shipped grammars were verified to
bind under their existing GRAMMAR_PACKAGES key, including the irregular ones
(typescript, tsx, kotlin, php, scss, sql, markdown).

Fixes #3926

Co-authored-by: rorar <rorar@users.noreply.github.com>
Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-10 17:50:43 -07:00
Alex Newman 14f1b30586 fix(quota): ignore inactive overage utilization (fixes #3903)
Co-authored-by: Gabriel Freschi <63794779+gfreschi@users.noreply.github.com>
2026-09-10 17:44:49 -07:00
Alex Newman 2f2a94d11e fix(chroma): reap unreadable writer lock past grace (fixes #3916)
Rebased from PR #3946 (L4XB) onto main after #3938 merged.

An unparseable lock (typically a 0-byte file left by a crash mid-write) has no
owner PID to probe, so isChromaWriterLockLive() is unreachable and vector sync
stays dead until someone deletes the file by hand.

Once the file's mtime is older than a short grace period (10s — long enough for
a concurrent writer to finish writing its payload), remove it and retry, mirroring
the existing dead-PID path. A freshly written unreadable lock is still refused
exactly as before.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Lukas Buck <L4XB@users.noreply.github.com>
2026-09-11 00:34:13 +00:00
ZIFeIYUuuuuuu 48357a9b9e fix: reuse Chroma writer owner within process (fixes #3919)
Co-authored-by: ZIFeIYUuuuuuu <273586639+ZIFeIYUuuuuuu@users.noreply.github.com>
2026-09-11 00:30:16 +00:00
L4XB 19d3f5cdb3 fix(chroma): stop a backfill run after repeated batch failures
When Chroma refuses writes, backfillKind() kept walking every remaining
row: each row's first batch failed, logged the same "Batch add failed"
error and moved on. On a large store that is one identical error line per
row per run (8.9M lines / 3.8 GB in three days in #3928) with nothing ever
advancing.

Count consecutive failed rows and give up the run after three, logging a
single summary with the remaining row count; a later successful row resets
the streak. The pipeline skips the other kinds once a run is aborted.
Nothing is lost: failed rows keep their pending marks or stay above the
watermark and the next backfill retries them.

Fixes #3928
2026-09-11 00:29:39 +00:00
Cursor Agent 4d21e69b09 docs: regenerate CHANGELOG for v13.24.6
Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-11 00:28:57 +00:00
Cursor Agent 9811b0eb78 chore: bump version to 13.24.6
Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
v13.24.6
2026-09-11 00:27:49 +00:00
cursor[bot] ab1046cac1 fix(chroma): record failed live writes as pending (fixes #3917) (#3949)
fix(chroma): record failed live writes as pending so the watermark cannot orphan them
2026-09-11 00:27:21 +00:00
cursor[bot] b6e4646281 fix(sqlite): keep post-v7 columns through session_summaries rebuild (#3955)
fix(sqlite): keep post-v7 columns through the session_summaries rebuild
2026-09-11 00:26:56 +00:00
cursor[bot] 55afdace77 fix: stop stale worker bundle recycle storms (#3961)
Cherry-picked from #3952 (AntonioCoppe). Fixes #3940, fixes #3951.

Rebuilt bundles to 13.24.5, added cross-hook recycle persistence, strengthened pre-build CI version checks.
2026-09-11 00:20:33 +00:00
Wu Shuwen 10f87e6925 fix(corpus): preserve observation type filters (fixes #3892)
CorpusBuilder.build() passed filter.types as `type`, but SearchOrchestrator only recognizes `obs_type` via normalizeParams. Route selected observation types through `obs_type` so phase-one search applies the filter before hydration.

Verified: 61 tests pass across knowledge + search suites (143 assertions).
2026-09-10 17:12:55 -07:00
Lukas Buck b2ce078a9c fix(parser): unwrap a label-wrapped observation title (#3947)
Some local observers echo the field label into the value and emit
`<title>[**title**: Example observation]</title>`. extractField() only
trims whitespace, so the whole wrapper was persisted as the title.

Unwrap exactly that complete, nonempty wrapper at parse time so the stored
title (and everything derived from it, such as the content hash) sees the
plain text. Partial forms and legitimately bracketed titles are unchanged.

Fixes #3907
2026-09-10 17:12:42 -07:00
Antonio Coppe 29279d9306 fix: validate all shipped runtime bundle versions 2026-09-11 00:08:30 +00:00
Antonio Coppe 2bb0a7487a fix: stop stale worker bundle recycle storms
Persist unsuccessful version recycles across hooks, retry after the bundle changes, and rebuild shipped workers. Check committed bundle versions before CI rebuilds them.

Refs #3940
2026-09-11 00:08:30 +00:00
Alex Newman d095021d85 feat(telemetry): skill_invoked for bundled skills (#3960)
* feat(telemetry): emit skill_invoked for bundled Skill and slash use

Track which first-party plugin/skills people invoke, on which host, without
collecting args, prompt bodies, or third-party skill names.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

* fix(telemetry): avoid */ inside skill-id file comment

A plugin/skills/*/ example closed the block comment and broke bun test parse.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-10 14:17:04 -07:00
Alex Newman c166e30d09 feat(grok-bot): Phase 1 hack-on-Memory JIT — rich timeline INDEX (#3959)
* feat(grok-bot): Phase 1 Memory JIT — rich timeline index via #3953 transport

Compile an editable CCS L1 markdown bucket into a slide-off timeline
INDEX (one observation per line, ID kept) and land it through
zz-claude-mem-inject.md → host Memory mid-attach. mtime-stable rewrite
when the fact block is unchanged. Pilot allowlist remains the default;
AGENT_IDS=* stays optional.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

* fix(grok-bot): stamp CCS L1 bucket at ccs/seats/<id>/TIMELINE.md

Concrete default is <agentDataRoot>/ccs/seats/<agentId>/TIMELINE.md so
bots can edit it. PRIVATE.md is a reserved sibling (never written).
house/ and groups/ stay reserved for later inherit. L1 pilot remains
the Orifice/Grok Memory allowlist.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-10 13:50:20 -07:00
L4XB 79208bd58a fix(sqlite): keep post-v7 columns through the session_summaries rebuild
The v7 rebuild that drops the UNIQUE constraint from session_summaries
recreated the table from a fixed v7 column list. Fresh installs stamp
every migration at once, so a database whose base schema already carried
v11's discovery_tokens next to the v7 constraint lost the column in that
rebuild, and the version-gated v11 guard never re-added it: every
summary write failed with "no column named discovery_tokens" from then
on while observations kept being captured.

The rebuild now carries every live column outside its v7 list over
(type and default included) and copies them along, and
ensureDiscoveryTokensColumn re-checks the live tables regardless of the
stamped version.

Fixes #3890
2026-09-10 09:33:46 +02:00
Alex Newman 1461655740 Merge pull request #3953 from thedotmack/worktree-grok-bot-session-inject
feat(grok-bot): silent Claude-Mem session inject into the Grok Bot prompt path
2026-09-09 21:10:32 -07:00
Claude 1d6eb84aaf fix(grok-bot): drop the inject fetch clock from the staged fact line
The worker's inject header carries the time it was fetched. Keeping it in the
fact block made every poll a content change, so the host saw a "memory changed"
delta and re-announced the section on the next turn for a timestamp and nothing
else. Facts already carry their own date; strip the clock and the refresh pass
goes to `unchanged` until observations actually move.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0181Cyg5g9ee7QvPynfh7iV7
2026-09-10 03:54:09 +00:00
Claude 8511f87410 feat(grok-bot): silent session inject into the Grok Bot prompt path
GrokBotAwarenessPusher gets needles into an agent's memory folder, but it
writes them as `- <date> [awareness] ...`, which the sand host's fact grammar
(`/^-\s+\((\d{4}-\d{2}-\d{2})\)\s+(.+?)\s*$/`) does not match — those lines
never reach the prompt. Nothing else pulls session context in automatically,
so a Grok seat only sees claude-mem when the model remembers to call a tool.

This adds a standalone watcher that closes that gap using only APIs that
already exist:

  worker GET /api/context/inject   (projects / platformSource only)
    -> host fact grammar, in a file the shim owns:
       agents/<id>/memory/log/zz-claude-mem-inject.md
    -> the host's own WatchedDirectory + frozen-section machinery
       (getFrozenSectionUpdatesForTurn -> promptWithInstructionsUpdate)
       attaches the delta to the agent's next cold / non-resume turn.

No host patch, no new memory API, no listening socket (outbound HTTP to the
local worker only), and no full frozen-head rebuild.

Two details the host forced:

- Tier. The host ranks recalled log facts by
  `log2(importance) + createdAt / 30d`, and importance comes only from a line
  prefix (`[episode] ` 1.5, `[note] ` 0.5, else 1). log2(1.5) is worth ~17.5
  days of recency, so on a seat whose log is already full of episode summaries
  a plain-tier line loses every slot in the 4000-char recall budget and never
  renders at all. Episode tier is the default; `CLAUDE_MEM_GROK_BOT_INJECT_TIER`
  can drop it to plain/note on a quieter seat.
- Line budget. Every emitted line spends its own length out of that same
  4000-char budget, evicting the agent's own memory, so the default is 2 lines:
  project, stats and the freshest observation IDs, plus how to fetch the rest.

Gated off by default and allowlisted per agent id. Writes are atomic, refuse
any path outside the agent's memory/log, and refuse any filename but the one
the shim owns — the host owns YYYY-MM.md and profile.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0181Cyg5g9ee7QvPynfh7iV7
2026-09-10 03:51:45 +00:00
L4XB f66aa74929 fix(chroma): record failed live writes as pending so the watermark cannot orphan them
The live sync paths (syncObservation, syncSummary, syncUserPrompt) declined
to bump the watermark when a write failed, but recorded nothing. The
watermark is a monotonic high-water mark, so the next row that did write
moved it past every row that had failed before, and recovery only looks at
`id > watermark` plus the pending list: those rows were never indexed
again. Any transient Chroma outage turned into silent, permanent loss of
vector coverage the moment it cleared (#3917: 1,900 observations).

Mark the row pending on a failed or partial write, exactly like
backfillKind() already does, and clear its pending mark when a later live
write of the same row lands. The next backfill then retries it through
the pending list.

Fixes #3917
2026-09-09 20:47:28 +02:00
Alex Newman 8bc631a71a CCS Align Phase 3 — final verification / sign-off (#3937)
* docs(ccs-align): Phase 3 verify/sign-off — mark Phases 0-2 shipped, honest 'what this is not', worker-lag ops MISS

Phase 3 closes the plan loop with no new runtime surface:
- SKILL.md: heading + status now reflect Phases 0-2 shipped (#3934/#3935/#3936)
  and Phase 3 = verify/sign-off; add explicit 'What this is NOT' (no compiler,
  no brainbeat product, no attention trough, no Focus/mouth, no second LFG
  writer, no history rewrite); record running-worker 13.24.1 vs repo version lag
  as an ops MISS to roll up (worker restart is a hard forbid for this seat).
- plan: status PLAN ONLY -> PASSED & SHIPPING (Phases 0-2 merged; Phase 3 verify).
  Defaults table and history preserved.
- Address the human as Alex.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

* chore: bump version to 13.24.5

PATCH bump for CCS Align Phase 3 sign-off (D10 — skill + plan ship, no
product claim). CHANGELOG.md left untouched (generated).

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
v13.24.5
2026-09-08 23:11:26 -07:00
Alex Newman c2d023d722 feat(ccs-align): Phase 2 rules alignment — house → project → seat conflict walk (#3936)
* feat(ccs-align): Phase 2 rules alignment — house → project → seat conflict walk

Walk house → project → seat layers, detect four conflict classes
(SHADOW_HOUSE, DENY_ALLOW, DRIFT, CLOCK_HEADER), emit an append-only
rules-report.md, and optionally apply SHADOW_HOUSE leaf patches
when CLAUDE_MEM_CCS_ALIGN_PATCH_SHADOWS=true.

- CcsAlignRulesWalker.ts: cascade rules checklist (not a parser),
  layer walk with MISS recording, conflict detection, atomic report
  append, gated shadow patch with forbidden-target guards
- 26 tests covering all four conflict classes, patch on/off,
  standing/always/never safety, MISS on absent paths, append-only
  report, edge cases
- SKILL.md updated with Phase 2 docs, conflict table, programmatic
  usage, cadence note (every 6th hour), and verification greps

Implements plans/2026-09-09-ccs-align.md §2.1–2.3.
No Focus, no attention trough, no .cas compiler, no history deletes.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

* chore: bump version to 13.24.4

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-08 22:46:42 -07:00
Alex Newman 7abdf79ccb feat(ccs-align): Phase 1 exclude marks — compile-time omit from middle cache (#3935)
Phase 1 of the CCS Align plan of record (plans/2026-09-09-ccs-align.md §1.1–1.4):

Exclude marks filter the *compiled* middle cache — the diary / SQLite stay
authoritative. A mark records observation ids and tool-use ids; the
grab→append→replace pipeline drops marked records so they never appear in
the compiled middle.jsonl. Unmarking + rebuild restores them from the diary
on the next pull. DELETE /api/observation/:id remains FORBIDDEN.

Implements:
- ExcludeMark type + exclude-marks.json schema (v:1)
- readExcludeMarks / writeExcludeMarks / addExcludeMark / removeExcludeMark
- buildExcludeSet for the atomic pipeline
- appendMiddleCacheRecordsAtomic now filters by exclude set
- landObservationsInMiddleCache loads marks and applies them
- rebuildMiddleCache for unmark+rebuild path
- Skill updated for Phase 1: exclude-marks section, layer-4 get_tool_uses
  warning (mark-time only), viewer isolation, unmark+rebuild, purge tools
- 11 new Phase 1 tests: mark drop, diary present, tool ids never in
  middle.jsonl, viewer isolation, unmark+rebuild, exclude-marks round-trip,
  buildExcludeSet, secure-isolation reason, corrupt marks fail-closed,
  marked ids skipped on ingest

Hard forbids verified:
- No DELETE /api/observation (compile-time omit, not tombstone)
- No LFG/Orifice [awareness] writes
- No profile.md touch
- No sixth processAgentResponse consumer
- No CHANGELOG hand-edit
- No 'Az' in user-facing strings

PATCH bump: 13.24.2 → 13.24.3

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-08 22:27:41 -07:00
Alex Newman e03cf5f7ef feat(ccs-align): Phase 0 breathing slice — worker health + three-layer pull + atomic middle cache (#3934)
* feat(ccs-align): seat-owned middle cache helper + settings (Phase 0)

Copy the #3931 atomic append primitive (appendAwarenessLineAtomic /
awarenessLineBody / formatAwarenessLine) into a seat helper with the three
locked Phase 0 changes: tag [ccs-align], seat-owned path root
~/.claude-mem/ccs-align/<viewerId>/, and a middle.jsonl store. Grab -> append
-> replace is atomic (temp + renameSync), deduped by observation id and by
date-excluded body. Path safety refuses profile.md, agents/**/memory/log, and
any write outside the seat root; the lander never throws into the caller.

Adds CLAUDE_MEM_CCS_ALIGN_{ENABLED,VIEWER_IDS,TRIGGER_TYPES,PATCH_SHADOWS}
defaults (needle types copied from the Grok list per D6).

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

* docs(skill): add ccs-align SKILL.md with hourly Worker Watch runbook

Phase 0 breathing slice: resolve worker port (timeline-report snippet),
prefer GET /api/health, pull search -> timeline -> get_observations, land
observations in the seat middle cache, update cursor.json. Documents the
Appendix A hourly cycle, settings, hard forbids, and later-phase stubs.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

* test(ccs-align): middle-cache format/needle/append/dedupe/path-safety

Copies the #3931 pusher test patterns: [ccs-align] format + 500-char
truncate, needle match, atomic append, id/body dedupe across days, path
safety (never profile.md / agents/memory/log / outside root), never-throw,
D2 append-only fallback, and cursor.json round-trip.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

* chore: bump version to 13.24.2 (PATCH — CCS Align Phase 0 code ships)

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

* chore: sync marketplace.json + plugin/package.json to 13.24.2

Version Consistency CI flagged .claude-plugin/marketplace.json still at
13.24.1 after the 13.24.2 bump. sync-plugin-manifests.js does not stamp
marketplace.json or plugin/package.json, so bring both source files in line
with root package.json. The worker-service.cjs stamp is regenerated by
'npm run build' in CI before the test runs.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-08 22:07:44 -07:00
Alex Newman 65a244a4b9 Merge pull request #3933 from thedotmack/cursor/ccs-align-plan-742c
docs(plan): CCS Align — Worker Watch seat plan of record
2026-09-08 21:36:31 -07:00
Cursor Agent 57937dc96e docs(plan): correct CCS Align Allowed APIs from source
Timeline depth default is 10 in SearchManager, not 3 in the MCP
blurb. Worker liveness is GET /api/health on Server.ts. Dedupe key
on the #3931 helper ignores the date. Docs lag; the seat copies code.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-09 04:32:36 +00:00
Cursor Agent 87efc50eca docs(plan): add CCS Align PR #3933 cross-link
Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-09 04:31:27 +00:00
Cursor Agent 4998a700be docs(plan): link CCS Align Prioritizer PASS packet
Cross-link the git plan of record to the Notion PASS page under CMEM Memory.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-09 04:31:00 +00:00
Cursor Agent 64e7cd00d8 docs(plan): CCS Align Worker Watch seat plan of record
Phased make-plan for the standing CCS Align seat: Phase 0 talks to the
local worker and lands observations in a seat-owned middle cache
(grab-append-replace, append-only fallback). Later phases cover exclude
marks and house→project→seat rules. Defaults locked so Prioritizer can
PASS without new answers tonight. Plan only — no /do.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-09 04:30:51 +00:00
Alex Newman 1456d161f6 Merge pull request #3932 from thedotmack/cursor/openrouter-price-history-46dd
OpenRouter list-price history for CMEM expense reports
2026-09-08 21:28:55 -07:00
Cursor Agent 913a67fa40 docs: add OpenRouter list-price history for expense reports
Daily published $/MTok input+output for CMEM-relevant models
(2026-07-01 through 2026-09-08), plus work-day popularity guesses
and a five-model July–September sample. Join observation tokens to
the CSV; do not use OpenRouter generation or activity APIs.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-09 04:18:24 +00:00
Alex Newman b6741bdf8c Merge pull request #3931 from thedotmack/cursor/grok-bot-awareness-breathing-46d6
feat(grok-bot): Phase 0+1 awareness breathing for LFG and Orifice
2026-09-08 20:58:27 -07:00
Cursor Agent 37f808ef93 fix(install): narrow clack cancel symbols so typecheck passes
@clack/prompts 1.8 types isCancel as unique CANCEL_SYMBOL. select/multiselect
still return generic symbol, so the existing cancel guards no longer narrow.
Treat leftover symbols as cancel. Pre-existing on main; needed for this PR CI.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-09 03:50:07 +00:00
Cursor Agent 03eeee7699 feat(grok-bot): Phase 0+1 awareness breathing for LFG and Orifice
Carry agentId from transcript watches through ingest, watch agents that
already have a memory tree, and append needle observations as dated
[awareness] fact lines into the pilot agent's Grok Bot memory log.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2026-09-09 03:42:52 +00:00
Alex Newman fd0ecf0233 feat(sqlite): durable tool_uses backup index + get_tool_uses disclosure (#3898)
Raw tool I/O had no durable home. `pending_messages` is the generation
queue -- rows are claimed, summarized, and deleted -- so once an
observation existed the original tool_input/tool_response were gone.

Adds `tool_uses` (schema v51) as a by-reference side index for those
bodies, written from the one ingest choke point both the PostToolUse hook
route and the transcript-watch processor already share. The JSONL
transcripts and `src/services/transcripts/*` remain the spine and are
untouched.

Schema (Receipt freeze 2026-09-06): UNIQUE(content_session_id,
tool_use_id), nullable `or_generation_id` / `or_session_id` as join keys
back to an OpenRouter spend line, and deliberately no cost_usd/micros --
this table carries tool identity, dollars stay on the OR stamp. No FK on
session_db_id/observation_id: a FK there can abort the constructor
migration chain (#3378), and observation_id is linked late by design.

Write path dual-writes alongside -- never instead of -- the
pending_messages enqueue, and swallows its own failures so an observation
is never lost to a backup-index error. `toolUseId` now actually reaches
the worker from hooks: it was missing from NormalizedHookInput and every
hook adapter, so only the transcript path supplied it. ResponseProcessor
links the batch's claimed ids to the first stored observation.

Read path is progressive-disclosure layer 4: POST /api/tool-uses/batch
requires explicit ids (never a full-table scan), GET /api/tool-uses
returns a cheap index shape with size hints and never the payloads, and
the `get_tool_uses` MCP tool is described as a last resort. claude-mem's
own read tools are skipped by the writer -- without that, every call to
get_tool_uses would store the bodies it just returned.

Payloads are stored in-row with a 64 KB soft cap and a UTF-8-safe
truncation marker; content_hash is computed over the original.

Join contract for Receipt: RECEIPT-JOIN.md


Claude-Session: https://claude.ai/code/session_01QgdJ6gExgBirDoAxknt94m

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 18:25:26 -07:00
Alex Newman 3939fbb2de docs(configuration): say what the off-LAN TV shows, and what cloud sync sends (#3891)
The Observation TV section ended at the LAN: a token, a non-loopback bind, and
a second device on your own network. Watching from a coffee shop was simply not
described, and the natural next question — "so do I open a port?" — had no
answer here.

It does now, and the answer is no. Off-LAN viewing is cmem.ai Pro at
/tv, it rides the cloud sync your worker already pushes, and the box never
accepts an inbound connection for it. CLAUDE_MEM_TV_TOKEN and
CLAUDE_MEM_WORKER_HOST belong to the LAN path and do nothing for it, so they
stay at their defaults. No tunnel is needed, and none is recommended.

The part that needed saying twice: the hosted page shows titles and nothing
else, AND cloud sync uploads your narratives and full prompt text. Those are
two different statements. Someone who reads only the first will conclude that
only titles ever leave their machine, which is wrong, so both sit in the same
subsection rather than one here and one three pages away.

Also notes the one rendering difference a user would otherwise hit and assume
was a bug: an observation with no title shows on the LAN TV, which falls back
to the subtitle, and does not show on the phone, which never receives one.

Docs only. No settings key, no code.


Claude-Session: https://claude.ai/code/session_01NevXJ2xL9PtiKEsCMR4SJT

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 19:40:00 -07:00
Alex Newman ad3dcaa2fc feat(worker): read-only Observation TV broadcast behind CLAUDE_MEM_TV_TOKEN
* feat(ui): observation TV — fullscreen fading titles off the existing SSE stream

Adds a standalone, dependency-free page that consumes the same /stream the
React viewer does and plays each observation's title as a fullscreen fading
card. Live arrivals play first; a seeded backlog from /api/observations cycles
while the worker is idle, so the screen is never blank.

Picture-in-picture without a broadcast library: Document PiP (Chromium) moves
the real DOM into the floating window so the CSS fades keep running, and
everywhere else — including iOS Safari, the phone case — the card is painted
to a canvas whose captureStream() feeds a muted video into native PiP.

Served two ways: express.static already exposes plugin/ui, so /tv.html works
with no route change, and a /tv alias is cached at boot the same way
viewer.html is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6QPdnPducVehMwCM2HYNC

* docs(plans): observation TV read-only broadcast + shared-secret token

Phased plan for the locked 2026-09-05 decision: expose Observation TV to a
second device on the LAN without exposing the rest of the worker.

The worker has no request authentication anywhere; its only defence is the
loopback bind, and the codebase says so out loud (ServerService.ts:129-131).
So CLAUDE_MEM_WORKER_HOST=0.0.0.0 today does not put the TV on the LAN, it
puts GET /api/settings — which returns the user's Gemini and OpenRouter API
keys in plaintext — on the LAN, alongside the settings writer, the row
deletes, bulk import, and better-auth's key issuance.

The design is one guard middleware mounted at position zero in the Server
constructor, the only spot that covers /api/auth/*, /api/admin/*, the static
mount, and every route registered later. It is a no-op for loopback and, for
non-loopback requests, default-deny with a four-path exact-match allowlist
behind a new CLAUDE_MEM_TV_TOKEN. An empty token means the guard is never
mounted, so every existing install — including the documented Docker 0.0.0.0
setup — is byte-identical to today.

Phase 0 is written out rather than delegated: ~45 routes inventoried with
file:line, the copy-ready patterns named (requireLocalhost, parseBearerToken,
safeEqualHex, the securityHeaders opt-in precedent), and five traps recorded,
including that SettingsDefaultsManager.get() cannot see settings.json and that
the worker never calls finalizeRoutes() so the guard must write its own
responses. Appendix B lists every rejected option with its reason —
cloudflared first among them.

Plan only. Nothing implemented.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMh2GZST1UgKDSML17qCmh

* feat(worker): read-only Observation TV broadcast behind CLAUDE_MEM_TV_TOKEN

The worker's HTTP surface (45+ routes) has no request authentication; the
loopback bind is its only defence. So setting CLAUDE_MEM_WORKER_HOST=0.0.0.0 —
which the Docker docs tell people to do — puts GET /api/settings (provider API
keys in plaintext), POST /api/admin/restart, DELETE /api/observation/:id,
POST /api/import and better-auth on the LAN.

Add one guard middleware, mounted at position zero in the Server constructor —
the only spot that covers /api/auth/*, /api/admin/*, the static mount and every
route registered later, including routes that do not exist yet. It is a no-op
for loopback and, for non-loopback requests, default-deny with an exact-match
four-path allowlist behind a shared secret:

  /tv, /tv.html, /stream, GET /api/observations

A GET/HEAD method gate kills every mutation; non-allowlisted paths get 404 so a
scanner is not told which routes exist; the token is compared constant-time and
accepted as Authorization: Bearer, X-Api-Key, or ?token= (the query form exists
only because EventSource cannot set headers). The token is never logged.

Empty token means the guard is never mounted, so every existing install behaves
exactly as before and CLAUDE_MEM_WORKER_HOST keeps its 127.0.0.1 default. A
boot-time SECURITY warning fires when the host is non-loopback with no token —
warn, not refuse, so the documented Docker deployment keeps working.

Also fixes createCorsMiddleware forwarding next(new Error('CORS not allowed')):
the worker never calls finalizeRoutes(), so that reached Express's default
handler and returned a 500 HTML stack trace with absolute filesystem paths —
newly reachable from the LAN. It now writes its own 403 JSON.

tv.html carries the token through to both of its calls, and cards now show
platform_source with a per-source accent colour in both the DOM and canvas
render paths.

No new dependencies. 38 tests in tests/server/tv-remote-guard.test.ts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xcn8Gf6ACkfDqLYaULAj2k

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 17:59:00 -07:00