From eca1d65ff8482ca8ef4d71e0d804163abdd3a4c3 Mon Sep 17 00:00:00 2001 From: MemOS AutoDev Date: Thu, 23 Jul 2026 18:07:51 +0800 Subject: [PATCH 1/2] 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 --- .../core/pipeline/memory-core.ts | 13 +++- .../pipeline/bootstrap-init-logger.test.ts | 71 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 apps/memos-local-plugin/tests/unit/pipeline/bootstrap-init-logger.test.ts diff --git a/apps/memos-local-plugin/core/pipeline/memory-core.ts b/apps/memos-local-plugin/core/pipeline/memory-core.ts index 39c6f3049..3e002256f 100644 --- a/apps/memos-local-plugin/core/pipeline/memory-core.ts +++ b/apps/memos-local-plugin/core/pipeline/memory-core.ts @@ -75,7 +75,7 @@ import type { import type { ResolvedConfig, ResolvedHome } from "../config/index.js"; import { loadConfig, resolveHome, SECRET_FIELD_PATHS } from "../config/index.js"; import { feedbackText, runFeedbackExperience } from "../experience/feedback-builder.js"; -import { rootLogger } from "../logger/index.js"; +import { initLogger, rootLogger } from "../logger/index.js"; import type { Logger } from "../logger/types.js"; import { openDb } from "../storage/connection.js"; import { runMigrations } from "../storage/migrator.js"; @@ -204,6 +204,17 @@ export async function bootstrapMemoryCoreFull( : await loadConfig(home); const config = configResult.config; + // Wire up file / audit / llm / perf / events transports as soon as the + // resolved config is available. Without this call the logger stays in + // `bootstrapConsoleOnly()` mode for the lifetime of the process, so + // `MEMOS_HOME/logs/` (memos.log, error.log, audit.log, llm.jsonl, + // perf.jsonl, events.jsonl) is never created even though + // `config.logging.file.enabled` defaults to true. See issue #2147. + // + // `initLogger` is idempotent and swaps the active root in place; any + // pre-existing `rootLogger.child(...)` handles keep working after the swap. + initLogger(config, home); + const log = rootLogger.child({ channel: "core.pipeline.bootstrap", ctx: { agent: options.agent }, diff --git a/apps/memos-local-plugin/tests/unit/pipeline/bootstrap-init-logger.test.ts b/apps/memos-local-plugin/tests/unit/pipeline/bootstrap-init-logger.test.ts new file mode 100644 index 000000000..bc58af878 --- /dev/null +++ b/apps/memos-local-plugin/tests/unit/pipeline/bootstrap-init-logger.test.ts @@ -0,0 +1,71 @@ +/** + * Regression test for issue #2147: + * `bootstrapMemoryCoreFull` must call `initLogger(config, home)` after the + * config is resolved so file transports (memos.log, error.log, audit.log, + * llm.jsonl, perf.jsonl, events.jsonl) are actually created on disk. Before + * the fix, the logger stayed in the `bootstrapConsoleOnly()` mode, so + * `home.logsDir` remained empty even though `config.logging.file.enabled` + * defaults to `true`. + * + * The assertion is deliberately tight: emit a distinctive marker line + * through `rootLogger.child(...)` after bootstrap, flush the logger, then + * confirm the marker landed in `memos.log`. Any regression that leaves the + * console-only sinks in place would make `memos.log` either missing or + * marker-free. + */ + +import { afterEach, describe, expect, it } from "vitest"; +import { promises as fs } from "node:fs"; +import { join } from "node:path"; + +import { makeTmpHome, type TmpHomeContext } from "../../helpers/tmp-home.js"; +import { bootstrapMemoryCoreFull } from "../../../core/pipeline/memory-core.js"; +import { rootLogger, shutdownLogger } from "../../../core/logger/index.js"; +import type { MemoryCore } from "../../../agent-contract/memory-core.js"; + +describe("bootstrapMemoryCoreFull → initLogger", () => { + let home: TmpHomeContext | null = null; + let core: MemoryCore | null = null; + + afterEach(async () => { + if (core) { + try { await core.shutdown(); } catch { /* ignore */ } + core = null; + } + await shutdownLogger(); + if (home) await home.cleanup(); + home = null; + }); + + it("wires file transports so memos.log is created and receives log lines", async () => { + home = await makeTmpHome({ agent: "openclaw" }); + + // Sanity check: fixture leaves logsDir empty before bootstrap. + const before = await fs.readdir(home.home.logsDir); + expect(before).toEqual([]); + + const result = await bootstrapMemoryCoreFull({ + agent: "openclaw", + home: home.home, + config: home.config, + pkgVersion: "issue-2147-test", + }); + core = result.core; + + // Distinctive marker so we can grep memos.log without depending on + // whichever config warnings bootstrap emitted. + const marker = "issue-2147.marker.line.abcdef"; + rootLogger.child({ channel: "core.pipeline.bootstrap" }).info(marker, { + probe: "bootstrap-init-logger", + }); + await rootLogger.flush(); + + const memosLogPath = join(home.home.logsDir, "memos.log"); + const stat = await fs.stat(memosLogPath); + expect(stat.isFile()).toBe(true); + expect(stat.size).toBeGreaterThan(0); + + const text = await fs.readFile(memosLogPath, "utf8"); + expect(text).toContain(marker); + }); +}); From d1cae3905254506f18a6870bb0ea76d1a29d5960 Mon Sep 17 00:00:00 2001 From: jiachengzhen Date: Thu, 23 Jul 2026 19:21:33 +0800 Subject: [PATCH 2/2] style(hermes): format daemon manager --- .../adapters/hermes/memos_provider/daemon_manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/memos-local-plugin/adapters/hermes/memos_provider/daemon_manager.py b/apps/memos-local-plugin/adapters/hermes/memos_provider/daemon_manager.py index 98d242731..0666fde72 100644 --- a/apps/memos-local-plugin/adapters/hermes/memos_provider/daemon_manager.py +++ b/apps/memos-local-plugin/adapters/hermes/memos_provider/daemon_manager.py @@ -215,7 +215,6 @@ def _rebuild_if_stale() -> bool: return False - def ensure_bridge_running(*, probe_only: bool = False) -> bool: """Return True when the bridge is (or can be) operational.