fix(build): rebuild bridge.cjs when any TypeScript source is newer#1850
Merged
syzsunshine219 merged 4 commits intoJul 23, 2026
Merged
Conversation
All columns added by 008-feedback-experience-metadata were backported into 001-initial.sql; the ALTER TABLE statements now fail on fresh DBs. Keeps only the idempotent CREATE INDEX lines. Also fix scripts/replay.py bridge_client import path (adapters/ is under apps/memos-local-plugin/, not the repo root) and add scripts/export-state-sessions.py for exporting state.db sessions to JSON files compatible with replay.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_rebuild_if_stale() only checked bridge.cts mtime, so changes to core/**/*.ts (e.g. memory-core.ts) didn't trigger an auto-rebuild on gateway restart. Walk all .ts files under plugin_root and compare the newest mtime against the compiled binary instead.
Collaborator
|
Automated Test Results: PASSED Cloud test-engine rerun against
Manual code review is still required before merge. |
2 tasks
CarltonXiang
added a commit
that referenced
this pull request
Jul 24, 2026
* ci: automate OpenClaw local plugin release notes (#2129) Generate evidence-backed OpenClaw local plugin release notes through the configured draft service. Add dry-run and publish workflow safeguards, retry/repair validation, prerelease handling, failure reporting hooks, redacted inspection artifacts, and focused local plugin test coverage. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: Adjust feedback update review flow & using independent * refactor: unify LLM provider environment config # Conflicts: # examples/basic_modules/llm.py # src/memos/mem_reader/multi_modal_struct.py * fix: fix log for embedding models * feat(api):update SDK version number * fix: fix tests * fix: preserve text when downgrading feedback updates * feat(api):update SDK version number * fix: type parser factory config as base parser config * fix: Open Code Review problems fix * feat: OpenRouter provider routing and reasoning control (#1958) * feat(memos): add OpenRouter provider routing * fix(memos): route OpenRouter prefs through all slots * Fix OpenRouter reasoning config types * fix(openrouter): harden routing detection * fix(openrouter): harden review follow-ups * fix(openrouter): preserve provider defaults --------- Co-authored-by: jiachengzhen <jiacz@memtensor.cn> Co-authored-by: shinetata <149466187+shinetata@users.noreply.github.com> * refactor: summarize search and embedding logs * perf: cache and coalesce embedding requests * fix: summarize cosine reranker logs * perf: optimize PolarDB retrieval round trips * perf: prune MMR candidates by memory bucket * Fix #1611: classifyTopic fails when summarizer uses Anthropic endpoint (Kimi Code) (#2133) * fix(openclaw): route classifyTopic + arbitrateTopicSplit per provider (#1611) `Summarizer.classifyTopic` and `Summarizer.arbitrateTopicSplit` always dispatched to the OpenAI implementation regardless of the summarizer's configured provider. When configured against an Anthropic-only endpoint (e.g. Kimi Code's `/coding/v1/messages`), every call returned 404 because the OpenAI helper appended `/chat/completions` to a URL that does not exist — wasting tokens, polluting logs, adding event-loop latency. Fix (Option A from the issue): - Add native classifyTopic{Anthropic,Gemini,Bedrock} and arbitrateTopicSplit{Anthropic,Gemini,Bedrock} in the three provider files, mirroring the shape of the existing judgeNewTopic* helpers. - Re-export TOPIC_CLASSIFIER_PROMPT / TOPIC_ARBITRATION_PROMPT from openai.ts so all providers share prompt strings and parseTopicClassifyResult remains the single JSON parser — no behavioral drift across providers. - Rewire callTopicClassifier / callTopicArbitration switch statements so the anthropic / gemini / bedrock arms route to their native implementations instead of falling through to the OpenAI helpers. - Bedrock helpers require cfg.endpoint (throws `Bedrock topic-classifier|arbitration requires 'endpoint'`) matching every other Bedrock helper in the file. - Mirror every edit into packages/memos-core/src/ingest/providers/ so the byte-similar sibling tree cannot silently regress the bug after the next sync. Tests: new tests/topic-classifier-dispatch.test.ts (8 cases) stubs globalThis.fetch and asserts each provider hits the correct URL + body shape; includes a regression case where an Anthropic 404 surfaces an `Anthropic topic-classifier failed` error rather than the old `OpenAI topic-classifier failed` — the exact string from issue #1611. TDD baseline: 7/8 fail against buggy code. Post-fix: 8/8 pass. Existing tests/topic-judge-minimax-1315.test.ts (5/5) continues to pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(openclaw): harden topic-classifier/arbitrator providers per OCR review Address code-review findings on the topic-classifier + arbitrator dispatch helpers added in #2133 (issue #1611). Applied to both apps/memos-local-openclaw and packages/memos-core (byte-similar mirrors). Correctness / security fixes: - anthropic: validate cfg.apiKey up front instead of silently sending ""; move cfg.headers spread BEFORE x-api-key + anthropic-version so a misconfigured header cannot silently overwrite the credential - anthropic: guard content parsing — treat missing/non-array json.content as [] to avoid TypeError on unexpected API payloads - anthropic: reduce arbitrateTopicSplit max_tokens from 60 to 10 to match arbitrateTopicSplitOpenAI (single-word NEW/SAME reply) - gemini: validate cfg.apiKey up front; construct URL via new URL() + URLSearchParams so a caller-supplied endpoint that already contains a query string ("?version=v1beta") does not produce malformed "…?version=v1beta?key=…" - bedrock: emit warn log when arbitrateTopicSplit sees empty or unexpected text so silent bias toward SAME becomes visible at normal log levels (production usually suppresses debug) Intentionally not applied (out-of-scope refactors that would touch pre-existing helpers not in this PR): DRY-extract shared low-level callers for anthropic/gemini/bedrock (findings #4/#7/#10/#14) and the DEFAULT_BEDROCK_MODEL constant (finding #11). Existing tests unchanged: tests/topic-classifier-dispatch.test.ts (8/8) and tests/topic-judge-minimax-1315.test.ts (5/5) still pass. The new guards use apiKey values already provided by every test case, so no test edits were required. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(openclaw): address remaining OCR round-2 findings on topic-classifier providers Round 1 (commit 646c28a) did not fully converge OCR review. Round 2 targets the 2 repeated findings and 10 newly detected ones without expanding scope beyond the classifier/arbitration functions added by PR #2133. Bedrock (packages/memos-core + apps/memos-local-openclaw): - Fix TRUE MISS from Round 1: `arbitrateTopicSplitBedrock` was still using `maxTokens: 60` while Anthropic's arbitrator was already lowered to 10. Aligned to `maxTokens: 10`. - Introduce `DEFAULT_BEDROCK_TOPIC_MODEL` constant scoped to the two new functions only (`classifyTopicBedrock`, `arbitrateTopicSplitBedrock`). Pre-existing helpers (`judgeDedupBedrock`, `summarizeBedrock`, etc.) keep their inline default to stay out of scope. - Extract `bedrockConverseTopic()` shared transport used by the two new functions to eliminate the DRY duplication OCR flagged. Gemini (packages/memos-core + apps/memos-local-openclaw): - Add empty/unexpected-response `log.warn` branches to `arbitrateTopicSplitGemini`, mirroring the Bedrock arbitrator's defensive handling (Round 1 covered Bedrock only). - Extract `callGeminiTopic()` shared transport used by the two new functions to eliminate the DRY duplication OCR flagged. - Rejected escalating the empty-response case to `throw`: throwing would cascade through `Summarizer.tryChain` and burn tokens on every fallback; warn+default-to-SAME is the safer choice for a boundary detector. Anthropic (packages/memos-core + apps/memos-local-openclaw): - Extract `callAnthropicMessagesTopic()` shared transport used by the two new functions to eliminate the DRY duplication OCR flagged. Header spread order is preserved verbatim: `...cfg.headers` comes BEFORE the credential and `anthropic-version` so user-supplied headers cannot override them (this was the Round 1 security fix — do not undo). Pre-existing helpers in each provider are intentionally untouched to keep the diff minimum-scoped to PR #2133's classifier/arbitrator additions. Verification (executed in apps/memos-local-openclaw): - `./node_modules/.bin/vitest run tests/topic-classifier-dispatch.test.ts \\ tests/topic-judge-minimax-1315.test.ts` → 13/13 passed - `./node_modules/.bin/tsc --noEmit` on the 3 modified provider files → 0 errors - All 6 files (3 apps + 3 packages/memos-core mirrors) are byte-identical --------- Co-authored-by: MemOS AutoDev <autodev@memtensor.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix #2131: [Bug] memos-local-plugin : chunks table never created — causes viewer dashboard (#2132) * fix: viewer dashboard drifts to zero after a namespace flip (#2131) The viewer's Overview/metrics/traces/session routes filtered reads through the core's mutable activeNamespace, which every turn/session rewrites from caller context hints (openclaw: profileId = ctx.agentId). When the gateway session or a sub-agent turn flipped the profile, all historical rows failed the visibility clauses and dashboard counts collapsed to zero. The reported root cause (missing chunks table) is a misdiagnosis: the only 2.0 reference to chunks is the legacy 1.0-import reader in server/routes/migrate.ts; the live pipeline queries traces/episodes/ sessions with vectors stored in BLOB columns, so no schema change is needed. Fix: complete the existing includeAllNamespaces viewer convention (already used by diag.ts/session.ts/memory.ts) — add the option to core.metrics() and core.listEpisodes(), and pass it from overview.ts, metrics.ts, trace.ts and session.ts. Scoped reads and turn-time retrieval isolation are unchanged; explicit owner filters still narrow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: align listApiLogs namespace scoping with viewer metrics feeds Follow-up to #2131 (OCR round 1/2): - listApiLogs: accept includeAllNamespaces in the contract and core facade (contract symmetry with listTraces/listSkills/listPolicies); metrics/tools now passes it explicitly so both feeds of the bump() aggregation are scoped identically - metrics(): document that the traces fetch feeding sessions/ writesToday/embeddings/dailyWrites is intentionally cross-namespace; only totalTurns respects includeAllNamespaces - listEpisodes: apply limit/offset after the visibility filter on the namespace-scoped path so pages are not silently under-filled by other namespaces' rows Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: memos-autodev[bot] <autodev-bot@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(plugin): paginate dirty-closed reward recovery (#2120) * fix(plugin): page dirty-closed reward recovery * fix(plugin): share dirty scan cursor type --------- Co-authored-by: HarveyXiang <harvey_xiang@163.com> * fix(adapter): fire session.close in daemon thread to unblock event loop (#1953) * fix(adapter): fire session.close in daemon thread to unblock event loop on_session_end() called bridge.request("session.close") inline with a 30 s blocking urlopen(). gateway/run.py calls this synchronously from _handle_reset_command (an async fn), so the blocking I/O ran on the asyncio event loop thread, preventing Discord heartbeats from firing and causing forced reconnection after 10–30 s. The session.close response is unused and errors are already suppressed, so the call is semantically fire-and-forget. Moving it to a daemon thread is the correct fix: the event loop is never blocked, the request still goes out, and a 5 s timeout keeps it bounded if the bridge is dead. Reproducer: Violet 2026-06-12 09:54 (agent.log lines 3629–3791). Spec: ~/specs/memos-bridge-blocking-shutdown-spec.md * Fix #1897: bug(memos-local-plugin): Hermes background model status checks can trigger paid (#1899) * Fix #1897: fix(memos-local-plugin): add LLM circuit breaker for terminal provider errors Issue #1897 reported ~12,900 paid LLM requests in 24 h on Hermes against a DeepSeek key with insufficient balance. The local `system_model_status` row count (12,900) closely tracked the provider-side `request_count` (11,344) for the same billing window. The naming is misleading: `system_model_status` is not a health probe; it is the audit row written once per LLM call (ok / fallback / error) inside `core/llm/client.ts`. With no circuit breaker, every pipeline subscriber (capture / session-relation / reward / L2 / L3 / skill / retrieval LLM filter / world-model) kept firing on every turn / closed episode / induction, generating one paid request each. Add a per-`LlmClient` circuit breaker: - Trips on terminal errors: HTTP 401/402/403 or messages containing `insufficient balance` / `invalid api key` / `unauthorized` / `account suspended` / `billing`. - Open: short-circuits subsequent calls inside the facade without contacting the provider. Throws `MemosError(LLM_UNAVAILABLE)` with `details.circuitOpen=true` so existing catch blocks still work. - Half-open after cool-down (default 5 min, configurable, min 30 s): next call probes the provider; success closes the breaker, terminal failure re-opens it for another cool-down. - Host fallback rescues a call without tripping the breaker — fallback exists precisely to keep going when the primary is down. - Coalesces `system_model_status="circuit_open"` audit rows to at most one per ~25 s while the breaker stays open, so we don't replace paid spam with audit-row spam. - Exposes `circuitOpen` / `circuitOpenUntil` / `circuitOpenedReason` via `LlmClientStats` for the Overview viewer card. - Enabled by default; legacy behaviour available via `circuitBreaker.enabled = false`. Tests: 9 new vitest cases under `tests/unit/llm/client.test.ts` covering trip on 402, trip on "insufficient balance" message, no trip on generic transient, coalescing, half-open close on success, host-fallback rescues without trip, disabled mode, stats fields, and re-open on terminal probe failure. All 59 LLM and 28 pipeline tests pass; `tsc --noEmit` clean. Out of scope (tracked separately): 429 `Retry-After` handling (issue #1620), per-tool rate limits, daily budget caps. * Fix LLM breaker with host fallback --------- Co-authored-by: autodev-bot <autodev@memtensor.local> Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com> Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com> Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com> --------- Co-authored-by: Memtensor-AI <project@memtensor.cn> Co-authored-by: autodev-bot <autodev@memtensor.local> Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com> Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com> Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com> * feat: configurable log timezone (#1956) feat(2c): add log timezone support Co-authored-by: Fayenix <fayenix@users.noreply.github.com> Co-authored-by: jiachengzhen <jiacz@memtensor.cn> * feat: chunk batch reflection scoring (#2146) Co-authored-by: Erick Wipprecht <53452309+chiefmojo@users.noreply.github.com> * Fix #1897 recovery replay request storm (#1939) * Fix #1897: fix(memos-local-plugin): add LLM circuit breaker for terminal provider errors Issue #1897 reported ~12,900 paid LLM requests in 24 h on Hermes against a DeepSeek key with insufficient balance. The local `system_model_status` row count (12,900) closely tracked the provider-side `request_count` (11,344) for the same billing window. The naming is misleading: `system_model_status` is not a health probe; it is the audit row written once per LLM call (ok / fallback / error) inside `core/llm/client.ts`. With no circuit breaker, every pipeline subscriber (capture / session-relation / reward / L2 / L3 / skill / retrieval LLM filter / world-model) kept firing on every turn / closed episode / induction, generating one paid request each. Add a per-`LlmClient` circuit breaker: - Trips on terminal errors: HTTP 401/402/403 or messages containing `insufficient balance` / `invalid api key` / `unauthorized` / `account suspended` / `billing`. - Open: short-circuits subsequent calls inside the facade without contacting the provider. Throws `MemosError(LLM_UNAVAILABLE)` with `details.circuitOpen=true` so existing catch blocks still work. - Half-open after cool-down (default 5 min, configurable, min 30 s): next call probes the provider; success closes the breaker, terminal failure re-opens it for another cool-down. - Host fallback rescues a call without tripping the breaker — fallback exists precisely to keep going when the primary is down. - Coalesces `system_model_status="circuit_open"` audit rows to at most one per ~25 s while the breaker stays open, so we don't replace paid spam with audit-row spam. - Exposes `circuitOpen` / `circuitOpenUntil` / `circuitOpenedReason` via `LlmClientStats` for the Overview viewer card. - Enabled by default; legacy behaviour available via `circuitBreaker.enabled = false`. Tests: 9 new vitest cases under `tests/unit/llm/client.test.ts` covering trip on 402, trip on "insufficient balance" message, no trip on generic transient, coalescing, half-open close on success, host-fallback rescues without trip, disabled mode, stats fields, and re-open on terminal probe failure. All 59 LLM and 28 pipeline tests pass; `tsc --noEmit` clean. Out of scope (tracked separately): 429 `Retry-After` handling (issue #1620), per-tool rate limits, daily budget caps. * Fix LLM breaker with host fallback * fix(plugin): bound recovery reflect replay LLM calls --------- Co-authored-by: autodev-bot <autodev@memtensor.local> Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com> Co-authored-by: jiachengzhen <jiacz@memtensor.cn> * fix: guard against episode storm stalling foreground sessions (#1844) fix: guard against episode storm stalling foreground sessions (#1755) Co-authored-by: jiachengzhen <jiacz@memtensor.cn> * fix(logging): initialize logger from config in the standalone bridge (#1955) The MemOS daemon (`bridge.cts`) resolved config inside `bootstrapMemoryCoreFull` but never called `initLogger`, so the active logger stayed on the `bootstrapConsoleOnly()` default with `tz` pinned to "UTC". This made `logging.timezone` (PR #44) inert in the daemon — and the rest of `logging.*` (level, channels, file/audit/llm/perf/events sinks) dead too. Pretty/compact timestamps kept printing UTC regardless of config. Add an opt-in `initLogging` flag to `BootstrapOptions`. When set, `bootstrapMemoryCoreFull` calls `initLogger(config, home)` right after config resolution, before anything logs. The standalone bridge passes it; embedded plugin hosts (`adapters/openclaw/index.ts`) leave it off so the host keeps control of its own logging. Test: bootstrap with `logging.timezone` set + `initLogging: true` stamps records with the configured tz; without the flag the logger is untouched. * fix(bridge): add 20s timeout guard to core.shutdown() to prevent orphaned processes (#1799) * fix(bridge): add shutdown timeout to prevent orphan bridge processes core.shutdown() drains the L2/L3/skill flush pipeline which can block on hanging LLM calls. Without a deadline the bridge never exits after stdin EOF when the Python parent is already gone, re-creating the process-leak condition. Race all three shutdown sites (daemon SIGTERM, non-daemon SIGTERM, headless stdin-EOF) against a 20s timeout so the process always terminates within a bounded time. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(bridge): wrap remaining core.shutdown() calls with 20s timeout Three sites missed by the original patch (56ebe7a3) were calling core.shutdown() without withShutdownTimeout, leaving the bridge process able to hang indefinitely if L2/L3/skill LLM calls stalled at shutdown: • bridge.cts: EADDRINUSE exit (×2) — daemon can't bind viewer port • bridge.cts: viewer-running keepalive path — stdin closes but viewer is still serving; core.shutdown fires from the interval callback All six core.shutdown() call sites now go through withShutdownTimeout, guaranteeing the bridge exits within 20s regardless of which path is taken. Adds bridge-shutdown-audit.md documenting the full call chain and confirming no blocking sync calls prevent the timeout from firing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docs): correct stale line references + move audit doc to apps/memos-local-plugin/docs/ - Removed companion-stable branch reference from header - Updated bridge.cts line numbers to match main (per shinetata review) - Removed companion-stable commit hash 56ebe7a3 - Moved from repo root to apps/memos-local-plugin/docs/ per review request --------- Co-authored-by: Fayenix <fayenix@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(build): rebuild bridge.cjs when any TypeScript source is newer (#1850) * fix(migrations): remove duplicate ALTER TABLE stmts from 008 now in 001 All columns added by 008-feedback-experience-metadata were backported into 001-initial.sql; the ALTER TABLE statements now fail on fresh DBs. Keeps only the idempotent CREATE INDEX lines. Also fix scripts/replay.py bridge_client import path (adapters/ is under apps/memos-local-plugin/, not the repo root) and add scripts/export-state-sessions.py for exporting state.db sessions to JSON files compatible with replay.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(daemon): rebuild when any .ts source is newer than dist/bridge.cjs _rebuild_if_stale() only checked bridge.cts mtime, so changes to core/**/*.ts (e.g. memory-core.ts) didn't trigger an auto-rebuild on gateway restart. Walk all .ts files under plugin_root and compare the newest mtime against the compiled binary instead. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: remove 500-row cap that truncated viewer count displays (#1954) * fix: remove 500-row cap that truncated viewer count displays clampLimit() in _helpers.ts capped all repo list() calls at 500, silently truncating skill/policy/world-model counts everywhere they were derived from array length rather than a COUNT(*) query. - Raise clampLimit cap 500 → 100,000 so metrics() and other internal analytics can read full datasets (fixes Analytics tab) - Rewrite /api/v1/overview to use countSkills/countPolicies/ countWorldModels/countEpisodes instead of list+length, making Overview counts correct regardless of scale (fixes Overview tab) - Align countSkills to use limit:100_000 consistent with other count methods * feat: chunk batch reflection scoring (#1957) * Fix #1540: fix: (#1824) docs(memos-local-plugin): clarify install path and stale dir names (#1540) The README's 'Quick start' section told users to use install.sh instead of npm install, but the warning was buried and users still tried 'npm install -g @memtensor/memos-local-plugin' first. The reporter in #1540 encountered this on a Hermes deployment. This change: - Promotes the 'do not run npm install -g' notice to a prominent IMPORTANT callout explaining why global install is wrong (no agent-home deploy, no config.yaml, no bridge/viewer) and that the tarball intentionally ships built artifacts only. - Adds a Troubleshooting subsection covering the two specific symptoms in the bug report: the 'package not found' misread, and the stale web/ and site/ directory names (web/ is now viewer/, site/ was removed by commit 26e7e3d). - Mentions install.ps1 for Windows alongside install.sh. - CHANGELOG: record the docs fix and reference #1540. Documentation-only change; no code or runtime behavior touched. Co-authored-by: MemOS AutoDev <autodev@memtensor.ai> Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn> * Fix #1888: [Bug] test_system_parser.py: SystemParser.__init__() got an unexpected keyword a (#1889) fix: remove invalid chunker parameter from SystemParser test instantiation - SystemParser.__init__() signature changed to (embedder, llm=None) - Test was still passing chunker=None causing TypeError - Fixes all 5 failing tests in test_system_parser.py Fixes #1888 Co-authored-by: MemOS AutoDev <autodev@memos.ai> Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn> * Fix #1525: [Bug] clean_json_response crashes with cryptic AttributeError when given None (#1884) * test: add comprehensive tests for clean_json_response (issue #1525) - Add test suite in tests/mem_os/test_format_utils.py - Cover None input ValueError with diagnostic message - Cover markdown removal, whitespace stripping, edge cases - Verify fix for AttributeError when LLM returns None * style: format clean_json_response tests --------- Co-authored-by: MemOS AutoDev <autodev@memos.ai> Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn> * Fix #1901: share_cube_with_user passes swapped args to _validate_cube_access — fails for ev (#1903) fix: validate current user not target in share_cube_with_user (#1901) share_cube_with_user(cube_id, target_user_id) called _validate_cube_access(cube_id, target_user_id), but the validator signature is (user_id, cube_id). The cube_id therefore landed in the user_id slot and _validate_user_exists raised "User '<cube_id>' does not exist or is inactive" for every well-formed call, making the API unusable. The in-code comment "Validate current user has access to this cube" already documented the correct intent: the sharing user (self.user_id) must have access to the cube being shared, not the target. Switch the call to self._validate_cube_access(self.user_id, cube_id). The target user's existence is independently checked on the next line via validate_user(target_user_id), so that path is unchanged. Add regression tests in tests/mem_os/test_memos_core.py that pin down: - validate_user_cube_access is consulted with (self.user_id, cube_id), - add_user_to_cube is called with (target_user_id, cube_id) on success, - a missing target raises "Target user '<id>' does not exist". Closes #1901 Co-authored-by: MemOS AutoDev Bot <autodev@memtensor.local> Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn> * feat(memos): chunk batch reflection scoring * Fix #1897: bug(memos-local-plugin): Hermes background model status checks can trigger paid (#1899) * Fix #1897: fix(memos-local-plugin): add LLM circuit breaker for terminal provider errors Issue #1897 reported ~12,900 paid LLM requests in 24 h on Hermes against a DeepSeek key with insufficient balance. The local `system_model_status` row count (12,900) closely tracked the provider-side `request_count` (11,344) for the same billing window. The naming is misleading: `system_model_status` is not a health probe; it is the audit row written once per LLM call (ok / fallback / error) inside `core/llm/client.ts`. With no circuit breaker, every pipeline subscriber (capture / session-relation / reward / L2 / L3 / skill / retrieval LLM filter / world-model) kept firing on every turn / closed episode / induction, generating one paid request each. Add a per-`LlmClient` circuit breaker: - Trips on terminal errors: HTTP 401/402/403 or messages containing `insufficient balance` / `invalid api key` / `unauthorized` / `account suspended` / `billing`. - Open: short-circuits subsequent calls inside the facade without contacting the provider. Throws `MemosError(LLM_UNAVAILABLE)` with `details.circuitOpen=true` so existing catch blocks still work. - Half-open after cool-down (default 5 min, configurable, min 30 s): next call probes the provider; success closes the breaker, terminal failure re-opens it for another cool-down. - Host fallback rescues a call without tripping the breaker — fallback exists precisely to keep going when the primary is down. - Coalesces `system_model_status="circuit_open"` audit rows to at most one per ~25 s while the breaker stays open, so we don't replace paid spam with audit-row spam. - Exposes `circuitOpen` / `circuitOpenUntil` / `circuitOpenedReason` via `LlmClientStats` for the Overview viewer card. - Enabled by default; legacy behaviour available via `circuitBreaker.enabled = false`. Tests: 9 new vitest cases under `tests/unit/llm/client.test.ts` covering trip on 402, trip on "insufficient balance" message, no trip on generic transient, coalescing, half-open close on success, host-fallback rescues without trip, disabled mode, stats fields, and re-open on terminal probe failure. All 59 LLM and 28 pipeline tests pass; `tsc --noEmit` clean. Out of scope (tracked separately): 429 `Retry-After` handling (issue #1620), per-tool rate limits, daily budget caps. * Fix LLM breaker with host fallback --------- Co-authored-by: autodev-bot <autodev@memtensor.local> Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com> Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com> Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com> --------- Co-authored-by: Memtensor-AI <project@memtensor.cn> Co-authored-by: MemOS AutoDev <autodev@memtensor.ai> Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn> Co-authored-by: MemOS AutoDev <autodev@memos.ai> Co-authored-by: MemOS AutoDev Bot <autodev@memtensor.local> Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com> Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com> Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com> * Revert "feat: chunk batch reflection scoring (#1957)" (#2145) This reverts commit 21a27ed. Co-authored-by: jiachengzhen <jiacz@memtensor.cn> * test: stub overview count methods --------- Co-authored-by: jiachengzhen <jiacz@memtensor.cn> Co-authored-by: Memtensor-AI <project@memtensor.cn> Co-authored-by: MemOS AutoDev <autodev@memtensor.ai> Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn> Co-authored-by: MemOS AutoDev <autodev@memos.ai> Co-authored-by: MemOS AutoDev Bot <autodev@memtensor.local> Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com> Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com> Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com> Co-authored-by: zhaxi <syzsunshine219@gmail.com> * fix(memos-local-openclaw): declare contracts.tools in plugin manifest (#1757) OpenClaw runtime requires every plugin to declare contracts.tools before registering agent tools. Without it, the gateway logs the following warning on every registration: [plugins] plugin must declare contracts.tools before registering agent tools (plugin=memos-local-openclaw-plugin, source=index.js) In a production OpenClaw deployment with active memos plugin, this warning flooded gateway.err.log at ~1300 lines/day, masking real errors and bloating logs by ~33MB/day. This commit declares all 20 tools the plugin actually registers (grep'd via `api.registerTool(...)` calls in index.ts): - memory_*: get, search, share, timeline, unshare, viewer, write_public - network_*: memory_detail, skill_pull, team_info - skill_*: file_get, files, get, install, publish, search, unpublish - task_*: share, summary, unshare Verified: post-deploy gateway.err.log warning count = 0. Refs: contracts.tools field per OpenClaw plugin spec (see openclaw-lark and similar plugins as reference). Co-authored-by: Richard-JC-Yan <150349207+Richard-JC-Yan@users.noreply.github.com> Co-authored-by: jiachengzhen <jiacz@memtensor.cn> * Fix #2148: [Bug] captureRunner uses reflectLlm (skill-evolver) for batch reflection — shoul (#2151) * fix(plugin): wire capture reflection to main llm (#2148) Capture batch reflection is a JSON-output task. When operators configure skillEvolver.enableThinking=true alongside llm.enableThinking=false (the typical Qwen3 setup), passing `deps.reflectLlm` into the capture runner made the batch reflection run on the thinking-enabled skill-evolver model, which emits <think> blocks that break JSON parsing and produce `malformed JSON` errors during capture summarisation. Route the capture runner's reflectLlm slot to `deps.llm` so JSON tasks stay on the non-thinking main model, matching the l3Llm pattern from #1959. - deps.ts: captureRunner.reflectLlm = deps.llm - tests: regression guard in tests/unit/pipeline/capture-reflect-llm-wiring.test.ts Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(plugin): clarify reflectLlm exposure comment (#2148 OCR follow-up) Enumerate the two read-only consumers that still reach `deps.reflectLlm` (reward-runner evaluator metadata + Overview health card via `resolveSkillEvolver`) and note that no other subscriber is wired to it. Prevents future readers from misreading the previous prose as "skill / capture also use reflectLlm". --------- Co-authored-by: autodev-bot <autodev@memtensor.local> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: MemTensor Bot <bot@memtensor.local> Co-authored-by: jiachengzhen <jiacz@memtensor.cn> * test(logging): verify standalone bootstrap writes memos.log (#2147) (#2150) * fix(local-plugin): call initLogger in bootstrapMemoryCoreFull `bootstrapMemoryCoreFull` in `apps/memos-local-plugin/core/pipeline/memory-core.ts` never invoked `initLogger(config, home)` after `loadConfig(home)`. The logger stayed in the `bootstrapConsoleOnly()` fallback (console + memory buffer + SSE broadcast only), so the `FileRotatingTransport` / `JsonlEventsTransport` sinks were never wired and `MEMOS_HOME/logs/` (memos.log, error.log, audit.log, llm.jsonl, perf.jsonl, events.jsonl) was left empty even though `config.logging.file.enabled` defaults to true. The fix adds `initLogger` to the logger import and calls it after the config is resolved and before the first `rootLogger.child(...)` call. `initLogger` is idempotent and swaps the active root in place, so existing child handles keep working. A new regression test `tests/unit/pipeline/bootstrap-init-logger.test.ts` boots the pipeline against a tmp home, emits a marker line, and asserts that `memos.log` is created on disk and contains the marker. It fails on the previous code (ENOENT for memos.log) and passes with this fix. Fixes #2147 * style(hermes): format daemon manager --------- Co-authored-by: MemOS AutoDev <autodev@memtensor.local> Co-authored-by: jiachengzhen <jiacz@memtensor.cn> * fix: align generated skill guidance (#2100) * feat(api): align OpenMem v1 SDK with cloud API * fix: align generated skill guidance * feat(api): align OpenMem v1 SDK with cloud API * fix(memos-local): include json hint in user messages (#1756) Co-authored-by: shinetata <149466187+shinetata@users.noreply.github.com> --------- Co-authored-by: dingliang <2650876010@qq.com> Co-authored-by: Qi Weng <bittergreen.wengqi@hotmail.com> Co-authored-by: de1ty <7804799+de1tydev@users.noreply.github.com> Co-authored-by: shinetata <149466187+shinetata@users.noreply.github.com> Co-authored-by: jiachengzhen <jiacz@memtensor.cn> * fix: Add dedicated document LLM, improve Markdown detection and context-aware structured extraction --------- Co-authored-by: Memtensor-AI <project@memtensor.cn> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: bittergreen <bittergreen.wengqi@hotmail.com> Co-authored-by: dingliang <2650876010@qq.com> Co-authored-by: Erick Wipprecht <53452309+chiefmojo@users.noreply.github.com> Co-authored-by: jiachengzhen <jiacz@memtensor.cn> Co-authored-by: shinetata <149466187+shinetata@users.noreply.github.com> Co-authored-by: Wenqiang Wei <46308778+endxxxx@users.noreply.github.com> Co-authored-by: MemOS AutoDev <autodev@memtensor.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: memos-autodev[bot] <autodev-bot@users.noreply.github.com> Co-authored-by: HarveyXiang <harvey_xiang@163.com> Co-authored-by: autodev-bot <autodev@memtensor.local> Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com> Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com> Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com> Co-authored-by: Fayenix <fayenix@users.noreply.github.com> Co-authored-by: de1ty <7804799+de1tydev@users.noreply.github.com> Co-authored-by: MemOS AutoDev <autodev@memtensor.ai> Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn> Co-authored-by: MemOS AutoDev <autodev@memos.ai> Co-authored-by: Richard-JC-Yan <128337844+Richard-JC-Yan@users.noreply.github.com> Co-authored-by: Richard-JC-Yan <150349207+Richard-JC-Yan@users.noreply.github.com> Co-authored-by: MemTensor Bot <bot@memtensor.local> Co-authored-by: Ziyang Guo <121015044+RerankerGuo@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two independent fixes: stale-build detection and a migration 008 idempotency correction.
1. Rebuild
dist/bridge.cjswhen any.tssource is newerPreviously the rebuild check in
daemon_manager.pyonly comparedbridge.ctsmtime againstdist/bridge.cjs. Changes tocore/**/*.ts(e.g.memory-core.ts,reward.ts) did not trigger an auto-rebuild on gateway restart, so a plugin update that only touched core files would silently run stale compiled output until manually rebuilt.Fix:
_rebuild_if_stale()now walks all.tsfiles under the plugin root and compares the newest mtime against the compiled binary.2. Remove duplicate ALTER TABLE statements from migration 008
All columns added by
008-feedback-experience-metadatawere backported into001-initial.sql. TheALTER TABLEstatements in 008 now fail with "column already exists" on fresh database installs. Keeps only the idempotentCREATE INDEXlines which are safe to re-run.Test plan
core/reward/reward.tsthen gateway restart:_rebuild_if_stale()detects the newer source and triggersnpm run build_rebuild_if_stale()returnsTruewithout rebuilding🤖 Generated with Claude Code