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 {