mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 01:31:08 +00:00
fix: canonicalize topic session transcript fallback (#64869)
* fix: canonicalize topic session transcript fallback When initSessionState has a topic-scoped SessionKey but no MessageThreadId, fallback transcript selection should still land on the topic-qualified JSONL path instead of the bare session file. Match the existing transcript resolver by parsing the thread id from the session key, and cover the regression with a session init test that loads the Telegram session-conversation grammar. Regeneration-Prompt: | Investigate why a Telegram topic session could alternate between <session-id>.jsonl and <session-id>-topic-<n>.jsonl for the same logical session. The fix should be in OpenClaw's session initialization path, not in lossless-claw. Keep behavior unchanged when MessageThreadId is present, but when the inbound turn only carries a topic-scoped SessionKey, derive the same topic-specific transcript path that the canonical transcript resolver would use. Add a regression test that proves initSessionState chooses the topic-qualified file even without MessageThreadId, and make the test load the session-conversation registry needed to parse Telegram :topic: grammar. * fix: preserve topic session transcript history
This commit is contained in:
@@ -8,6 +8,8 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Telegram/sessions: keep topic-scoped session initialization on the canonical topic transcript path when inbound turns omit `MessageThreadId`, so one topic session no longer alternates between bare and topic-qualified transcript files. (#64869) thanks @jalehman.
|
||||
|
||||
## 2026.4.11-beta.1
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -16,11 +16,12 @@ import {
|
||||
registerSessionBindingAdapter,
|
||||
} from "../../infra/outbound/session-binding-service.js";
|
||||
import { enqueueSystemEvent, resetSystemEventsForTest } from "../../infra/system-events.js";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import {
|
||||
createChannelTestPluginBase,
|
||||
createTestRegistry,
|
||||
} from "../../test-utils/channel-plugins.js";
|
||||
import { createSessionConversationTestRegistry } from "../../test-utils/session-conversation-registry.js";
|
||||
import { drainFormattedSystemEvents } from "./session-updates.js";
|
||||
import { persistSessionUsageUpdate } from "./session-usage.js";
|
||||
import { initSessionState } from "./session.js";
|
||||
@@ -561,6 +562,35 @@ describe("initSessionState thread forking", () => {
|
||||
`${result.sessionEntry.sessionId}-topic-456.jsonl`,
|
||||
);
|
||||
});
|
||||
|
||||
it("records topic-specific session files from SessionKey when MessageThreadId is absent", async () => {
|
||||
const root = await makeCaseDir("openclaw-topic-session-key-");
|
||||
const storePath = path.join(root, "sessions.json");
|
||||
|
||||
const cfg = {
|
||||
session: { store: storePath },
|
||||
} as OpenClawConfig;
|
||||
|
||||
setActivePluginRegistry(createSessionConversationTestRegistry());
|
||||
try {
|
||||
const result = await initSessionState({
|
||||
ctx: {
|
||||
Body: "Hello topic",
|
||||
SessionKey: "agent:main:telegram:group:123:topic:456",
|
||||
},
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
const sessionFile = result.sessionEntry.sessionFile;
|
||||
expect(sessionFile).toBeTruthy();
|
||||
expect(path.basename(sessionFile ?? "")).toBe(
|
||||
`${result.sessionEntry.sessionId}-topic-456.jsonl`,
|
||||
);
|
||||
} finally {
|
||||
resetPluginRuntimeStateForTest();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("initSessionState RawBody", () => {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { resolveAndPersistSessionFile } from "../../config/sessions/session-file
|
||||
import { resolveSessionKey } from "../../config/sessions/session-key.js";
|
||||
import { resolveMaintenanceConfigFromInput } from "../../config/sessions/store-maintenance.js";
|
||||
import { loadSessionStore, updateSessionStore } from "../../config/sessions/store.js";
|
||||
import { parseSessionThreadInfo } from "../../config/sessions/thread-info.js";
|
||||
import {
|
||||
DEFAULT_RESET_TRIGGERS,
|
||||
type GroupKeyResolution,
|
||||
@@ -620,8 +621,15 @@ export async function initSessionState(params: {
|
||||
}
|
||||
}
|
||||
}
|
||||
const threadIdFromSessionKey = parseSessionThreadInfo(
|
||||
sessionCtxForState.SessionKey ?? sessionKey,
|
||||
).threadId;
|
||||
const fallbackSessionFile = !sessionEntry.sessionFile
|
||||
? resolveSessionTranscriptPath(sessionEntry.sessionId, agentId, ctx.MessageThreadId)
|
||||
? resolveSessionTranscriptPath(
|
||||
sessionEntry.sessionId,
|
||||
agentId,
|
||||
ctx.MessageThreadId ?? threadIdFromSessionKey,
|
||||
)
|
||||
: undefined;
|
||||
const resolvedSessionFile = await resolveAndPersistSessionFile({
|
||||
sessionId: sessionEntry.sessionId,
|
||||
|
||||
Reference in New Issue
Block a user