diff --git a/docs/cli/gateway.md b/docs/cli/gateway.md index 4c8bd78284a..502dae2d5b3 100644 --- a/docs/cli/gateway.md +++ b/docs/cli/gateway.md @@ -99,10 +99,10 @@ openclaw gateway run Alias for `--ws-log compact`. - Log raw model stream events to jsonl. + Log raw model stream events to SQLite diagnostics. - Raw stream jsonl path. + Optional raw stream JSONL export path. ## Restart the Gateway diff --git a/docs/help/debugging.md b/docs/help/debugging.md index c3e110a07d1..afa8e1fac76 100644 --- a/docs/help/debugging.md +++ b/docs/help/debugging.md @@ -274,9 +274,10 @@ OPENCLAW_RAW_STREAM=1 OPENCLAW_RAW_STREAM_PATH=~/.openclaw/logs/raw-stream.jsonl ``` -Default file: +Default storage: -`~/.openclaw/logs/raw-stream.jsonl` +SQLite diagnostics (`diagnostics.raw_stream`). Set `--raw-stream-path` or +`OPENCLAW_RAW_STREAM_PATH` only when you need an explicit JSONL export file. ## Raw chunk logging (pi-mono) diff --git a/src/agents/pi-embedded-subscribe.raw-stream.test.ts b/src/agents/pi-embedded-subscribe.raw-stream.test.ts new file mode 100644 index 00000000000..74fe4146b9c --- /dev/null +++ b/src/agents/pi-embedded-subscribe.raw-stream.test.ts @@ -0,0 +1,33 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js"; +import { listOpenClawStateKvJson } from "../state/openclaw-state-kv.js"; +import { appendRawStream } from "./pi-embedded-subscribe.raw-stream.js"; + +describe("appendRawStream", () => { + afterEach(() => { + closeOpenClawStateDatabaseForTest(); + vi.unstubAllEnvs(); + }); + + it("stores default raw stream events in SQLite state", () => { + const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-raw-stream-")); + try { + vi.stubEnv("OPENCLAW_RAW_STREAM", "1"); + vi.stubEnv("OPENCLAW_STATE_DIR", stateDir); + + appendRawStream({ type: "chunk", text: "hello" }); + + const entries = listOpenClawStateKvJson>("diagnostics.raw_stream", { + env: process.env, + }); + expect(entries).toHaveLength(1); + expect(entries[0]?.value).toMatchObject({ type: "chunk", text: "hello" }); + } finally { + closeOpenClawStateDatabaseForTest(); + fs.rmSync(stateDir, { recursive: true, force: true }); + } + }); +}); diff --git a/src/agents/pi-embedded-subscribe.raw-stream.ts b/src/agents/pi-embedded-subscribe.raw-stream.ts index 144866f3e6a..2d210e5620e 100644 --- a/src/agents/pi-embedded-subscribe.raw-stream.ts +++ b/src/agents/pi-embedded-subscribe.raw-stream.ts @@ -1,20 +1,20 @@ import fs from "node:fs"; import path from "node:path"; -import { resolveStateDir } from "../config/paths.js"; import { isTruthyEnvValue } from "../infra/env.js"; import { appendRegularFile } from "../infra/fs-safe.js"; +import { getStateDiagnosticWriter, type StateDiagnosticWriter } from "./state-diagnostic-writer.js"; let rawStreamReady = false; +const rawStreamStateWriters = new Map(); +const RAW_STREAM_SQLITE_LABEL = "sqlite://state/diagnostics/raw-stream"; +const RAW_STREAM_SQLITE_SCOPE = "diagnostics.raw_stream"; function isRawStreamEnabled(): boolean { return isTruthyEnvValue(process.env.OPENCLAW_RAW_STREAM); } function resolveRawStreamPath(): string { - return ( - process.env.OPENCLAW_RAW_STREAM_PATH?.trim() || - path.join(resolveStateDir(), "logs", "raw-stream.jsonl") - ); + return process.env.OPENCLAW_RAW_STREAM_PATH?.trim() || RAW_STREAM_SQLITE_LABEL; } export function appendRawStream(payload: Record) { @@ -22,6 +22,13 @@ export function appendRawStream(payload: Record) { return; } const rawStreamPath = resolveRawStreamPath(); + if (rawStreamPath === RAW_STREAM_SQLITE_LABEL) { + getStateDiagnosticWriter(rawStreamStateWriters, { + label: rawStreamPath, + scope: RAW_STREAM_SQLITE_SCOPE, + }).write(`${JSON.stringify(payload)}\n`); + return; + } if (!rawStreamReady) { rawStreamReady = true; try {