fix: carry transcript update sequence · openclaw/openclaw@3fa9658
steipete
·
2026-05-14
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
|
15 | 15 | ### Fixes |
16 | 16 | |
17 | 17 | - CLI: lazy-load model and plugin runtime helpers for command actions so parent/help output renders without importing those runtime paths. |
| 18 | +- Gateway/session history: carry monotonic transcript message sequence through live updates and refresh SSE history when stale sequence input would otherwise append bad incremental state. (#81474) Thanks @samzong. |
18 | 19 | - Security/sandbox: include Windows `USERPROFILE` in the sandbox blocked home roots so credential-bearing binds (such as `.codex`, `.openclaw`, or `.ssh` under the Windows user profile) are denied even when `HOME` points at a different shell home. (#63074) Thanks @luoyanglang. |
19 | 20 | - Models config/auth: stop inferring provider env-var markers from broad `^[A-Z_][A-Z0-9_]*$` strings, and resolve config-backed provider `apiKey` values only through structured env SecretRefs (`secrets.providers[id]` / `secrets.defaults`), so unrelated env vars cannot accidentally become provider credentials. Thanks @sallyom. |
20 | 21 | - Media fetch: skip allocating and buffering the response body for bodyless media responses (HEAD probes and 204-style empty bodies), avoiding wasted heap on streams that carry no payload. Thanks @shakkernerd. |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | import type { SessionLifecycleEvent } from "../sessions/session-lifecycle-events.js"; |
2 | 2 | import type { SessionTranscriptUpdate } from "../sessions/transcript-events.js"; |
| 3 | +import { asPositiveSafeInteger } from "../shared/number-coercion.js"; |
3 | 4 | import { projectChatDisplayMessage } from "./chat-display-projection.js"; |
4 | 5 | import type { GatewayBroadcastToConnIdsFn } from "./server-broadcast-types.js"; |
5 | 6 | import type { |
@@ -18,10 +19,6 @@ import {
|
18 | 19 | type SessionEventSubscribers = Pick<SessionEventSubscriberRegistry, "getAll">; |
19 | 20 | type SessionMessageSubscribers = Pick<SessionMessageSubscriberRegistry, "get">; |
20 | 21 | |
21 | | -function normalizeMessageSeq(value: unknown): number | undefined { |
22 | | -return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined; |
23 | | -} |
24 | | - |
25 | 22 | function buildGatewaySessionSnapshot(params: { |
26 | 23 | sessionRow: GatewaySessionRow | null | undefined; |
27 | 24 | includeSession?: boolean; |
@@ -122,11 +119,11 @@ async function handleTranscriptUpdateBroadcast(
|
122 | 119 | if (connIds.size === 0) { |
123 | 120 | return; |
124 | 121 | } |
125 | | -let messageSeq = normalizeMessageSeq(update.messageSeq); |
| 122 | +let messageSeq = asPositiveSafeInteger(update.messageSeq); |
126 | 123 | if (messageSeq === undefined) { |
127 | 124 | const { entry, storePath } = loadSessionEntry(sessionKey); |
128 | 125 | messageSeq = entry?.sessionId |
129 | | - ? normalizeMessageSeq( |
| 126 | + ? asPositiveSafeInteger( |
130 | 127 | await readSessionMessageCountAsync(entry.sessionId, storePath, entry.sessionFile), |
131 | 128 | ) |
132 | 129 | : undefined; |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +import { asPositiveSafeInteger } from "../shared/number-coercion.js"; |
1 | 2 | import { |
2 | 3 | DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS, |
3 | 4 | projectChatDisplayMessages, |
@@ -67,10 +68,6 @@ function resolveCursorSeq(cursor: string | undefined): number | undefined {
|
67 | 68 | return Number.isFinite(value) && value > 0 ? value : undefined; |
68 | 69 | } |
69 | 70 | |
70 | | -function resolvePositiveInteger(value: unknown): number | undefined { |
71 | | -return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined; |
72 | | -} |
73 | | - |
74 | 71 | function toSessionHistoryMessages(messages: unknown[]): SessionHistoryMessage[] { |
75 | 72 | return messages.filter( |
76 | 73 | (message): message is SessionHistoryMessage => |
@@ -92,7 +89,7 @@ function buildPaginatedSessionHistory(params: {
|
92 | 89 | } |
93 | 90 | |
94 | 91 | function resolveMessageSeq(message: SessionHistoryMessage | undefined): number | undefined { |
95 | | -return resolvePositiveInteger(message?.__openclaw?.seq); |
| 92 | +return asPositiveSafeInteger(message?.__openclaw?.seq); |
96 | 93 | } |
97 | 94 | |
98 | 95 | function paginateSessionMessages( |
@@ -238,7 +235,7 @@ export class SessionHistorySseState {
|
238 | 235 | if (this.limit !== undefined || this.cursor !== undefined) { |
239 | 236 | return null; |
240 | 237 | } |
241 | | -const carriedSeq = resolvePositiveInteger(update.messageSeq); |
| 238 | +const carriedSeq = asPositiveSafeInteger(update.messageSeq); |
242 | 239 | if (carriedSeq !== undefined) { |
243 | 240 | if (carriedSeq <= this.rawTranscriptSeq) { |
244 | 241 | return { shouldRefresh: true }; |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +import { asPositiveSafeInteger } from "../shared/number-coercion.js"; |
1 | 2 | import { normalizeOptionalString } from "../shared/string-coerce.js"; |
2 | 3 | |
3 | 4 | export type SessionTranscriptUpdate = { |
@@ -12,10 +13,6 @@ type SessionTranscriptListener = (update: SessionTranscriptUpdate) => void;
|
12 | 13 | |
13 | 14 | const SESSION_TRANSCRIPT_LISTENERS = new Set<SessionTranscriptListener>(); |
14 | 15 | |
15 | | -function normalizeMessageSeq(value: unknown): number | undefined { |
16 | | -return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined; |
17 | | -} |
18 | | - |
19 | 16 | export function onSessionTranscriptUpdate(listener: SessionTranscriptListener): () => void { |
20 | 17 | SESSION_TRANSCRIPT_LISTENERS.add(listener); |
21 | 18 | return () => { |
@@ -38,7 +35,7 @@ export function emitSessionTranscriptUpdate(update: string | SessionTranscriptUp
|
38 | 35 | if (!trimmed) { |
39 | 36 | return; |
40 | 37 | } |
41 | | -const messageSeq = normalizeMessageSeq(normalized.messageSeq); |
| 38 | +const messageSeq = asPositiveSafeInteger(normalized.messageSeq); |
42 | 39 | const nextUpdate: SessionTranscriptUpdate = { |
43 | 40 | sessionFile: trimmed, |
44 | 41 | ...(normalizeOptionalString(normalized.sessionKey) |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | export function asFiniteNumber(value: unknown): number | undefined { |
2 | 2 | return typeof value === "number" && Number.isFinite(value) ? value : undefined; |
3 | 3 | } |
| 4 | + |
| 5 | +export function asPositiveSafeInteger(value: unknown): number | undefined { |
| 6 | +return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined; |
| 7 | +} |
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。