fix(chat): stabilize replay and control events - #365
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe change adds canonical control and commentary events, separates their runtime state from response runs, hydrates truncated history messages through ChangesControl and commentary events
History message hydration
Attachment preview scrolling
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant TaskRoutes
participant Gateway
participant OpenClaw
participant ChatRuntime
TaskRoutes->>Gateway: sendSessionControlEvent(sessionKey, message)
Gateway->>OpenClaw: chat.inject
Gateway->>OpenClaw: wake
OpenClaw-->>ChatRuntime: canonical control event
ChatRuntime->>ChatRuntime: store session control and settle selected session
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7b3014b03c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
contracts/chatCanonicalHistory.ts (1)
36-47: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winUse
v.variantinstead ofv.unionfor this discriminated union.
canonicalChatHistoryMessageResultSchemais a textbook discriminated union on theokfield (v.literal(true)vsv.literal(false)). Valibot's own documentation recommendsvariantoverunionfor this exact case.For better performance, more type safety, and a more targeted output of issues, you can use variant for discriminated unions... we recommend using variant over union whenever possible.
♻️ Proposed refactor
-export const canonicalChatHistoryMessageResultSchema = v.union([ - v.strictObject({ +export const canonicalChatHistoryMessageResultSchema = v.variant("ok", [ + v.strictObject({ message: canonicalChatHistoryRowSchema, ok: v.literal(true), schemaVersion: v.literal(CANONICAL_CHAT_HISTORY_SCHEMA_VERSION), }), v.strictObject({ ok: v.literal(false), schemaVersion: v.literal(CANONICAL_CHAT_HISTORY_SCHEMA_VERSION), unavailableReason: v.picklist(["not_found", "not_visible", "oversized"]), }), ]);Please confirm
v.variantis available and behaves as documented in valibot 1.4.2, as declared for this project.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@contracts/chatCanonicalHistory.ts` around lines 36 - 47, Update canonicalChatHistoryMessageResultSchema to use Valibot’s v.variant discriminated-union API keyed by the ok field, preserving both strictObject branches, their literals, and unavailableReason validation. Confirm the project’s Valibot 1.4.2 API supports this usage.frontend/src/components/features/chat/transport/openClawHistoryLoader.ts (1)
375-409: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy liftHydration fully blocks
history()before any messages are returned.
#loadFresh(Line 527) and#load(Lines 586-588) bothawait this.#hydrateRows(...)before returning.#hydrateRows(Lines 375-409) processes every truncated row through a 4-worker pool, onechat.message.getround trip per row. Since#pagesUntilwalks pages untilhasMoreisfalserather than stopping atlimit, this hydration step can cover the full session transcript, not just a page.For a session with many truncated rows (large tool outputs, long file reads), the caller of
history()waits for all of them to hydrate sequentially in batches of 4 before the transcript renders at all, on every fresh load and on every reconnect (sincereset()clears#fullMessageCache). Consider returning the bounded previews immediately and patching in hydrated content as it resolves, so a handful of large truncated messages cannot delay the entire transcript from appearing.Please confirm how many truncated rows a typical long-running session accumulates, since that determines whether this is a real-world latency risk or a rare edge case.
Also applies to: 518-535, 586-588
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/features/chat/transport/openClawHistoryLoader.ts` around lines 375 - 409, Change `#loadFresh` and `#load` so history() returns bounded preview rows without awaiting full `#hydrateRows` completion. Refactor `#hydrateRows` to hydrate truncated rows asynchronously and patch each resolved message into the displayed/cached history through the existing update path, preserving ordering and avoiding stale updates after reset or reconnect. Ensure hydration failures do not block or replace the initial preview response, and verify typical long-running sessions’ truncated-row counts to validate the latency impact.contracts/chat/openClawHistoryPageAdapter.ts (1)
34-36: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy liftConfirm the OpenClaw truncation wording is part of the stable contract.
isLightweightPreviewdepends on exact...(truncated)...suffix or[chat.history omitted: message too large]equality. If OpenClaw changes wording,normalizedText.trimEnd()may still normalize the text enough that these exact matches fail, leavingtruncatedunset and preventing full-message hydration becauseOpenClawHistoryLoader.#fullMessage()only requests full content whenpreview.truncatedis truthy.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@contracts/chat/openClawHistoryPageAdapter.ts` around lines 34 - 36, Ensure the truncation detection in isLightweightPreview does not depend solely on exact OpenClaw suffix or placeholder wording, so wording changes still set preview.truncated and allow OpenClawHistoryLoader.#fullMessage() to hydrate full content. Preserve recognition of the existing CHAT_HISTORY_TRUNCATION_SUFFIX and CHAT_HISTORY_OVERSIZED_PLACEHOLDER values while adding robust detection for equivalent truncation markers.frontend/src/components/features/chat/domain/chatState.ts (1)
763-783: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winClarify the tautological name comparison at line 780.
call.name === (result.name || call.name)always evaluates totruewhenresult.nameis falsy, because the right side then becomescall.name. This is logically equivalent to!result.name || call.name === result.name, which intentionally falls back to an unconditional match whenresult.nameis absent, relying onuniqueMatchingDiagnosticIndexto keep the match safe when only one candidate qualifies.Write the condition in its equivalent explicit form. This removes the "compares a value to itself" pattern that a future maintainer could mistake for a bug and "fix," which would silently break the single-candidate name-fallback path.
✏️ Proposed clarification
index = uniqueMatchingDiagnosticIndex( diagnostics, (entry) => entry.message.toolCalls?.some( (call) => !call.id && !call.toolResult && - call.name === (result.name || call.name) + (!result.name || call.name === result.name) ) === true );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/features/chat/domain/chatState.ts` around lines 763 - 783, In the result-matching predicate inside uniqueMatchingDiagnosticIndex, replace the tautological call.name === (result.name || call.name) expression with its explicit equivalent: match when result.name is absent or call.name equals result.name. Preserve the existing !call.id and !call.toolResult constraints.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/src/gateway.ts`:
- Around line 1690-1699: Update sendSessionControlEvent so both chat.inject and
wake use the same stable notification idempotency key for a single control
event, or use the protocol’s supported idempotent chat.inject mechanism. Ensure
retries after wake failure do not create duplicate durable control notices,
while preserving the existing request order and timeout behavior.
In `@contracts/chat/openClawHistoryPageAdapter.ts`:
- Around line 216-254: Update canonicalizeOpenClawHistoryMessageResult to
validate the raw message identity before calling canonicalHistoryRow: read the
id from message.__openclaw and require it to match the trimmed
options.messageId. Reject missing or mismatched ids through the existing
invalid/unavailable error path so the response cannot be accepted under the
requested identity.
---
Nitpick comments:
In `@contracts/chat/openClawHistoryPageAdapter.ts`:
- Around line 34-36: Ensure the truncation detection in isLightweightPreview
does not depend solely on exact OpenClaw suffix or placeholder wording, so
wording changes still set preview.truncated and allow
OpenClawHistoryLoader.#fullMessage() to hydrate full content. Preserve
recognition of the existing CHAT_HISTORY_TRUNCATION_SUFFIX and
CHAT_HISTORY_OVERSIZED_PLACEHOLDER values while adding robust detection for
equivalent truncation markers.
In `@contracts/chatCanonicalHistory.ts`:
- Around line 36-47: Update canonicalChatHistoryMessageResultSchema to use
Valibot’s v.variant discriminated-union API keyed by the ok field, preserving
both strictObject branches, their literals, and unavailableReason validation.
Confirm the project’s Valibot 1.4.2 API supports this usage.
In `@frontend/src/components/features/chat/domain/chatState.ts`:
- Around line 763-783: In the result-matching predicate inside
uniqueMatchingDiagnosticIndex, replace the tautological call.name ===
(result.name || call.name) expression with its explicit equivalent: match when
result.name is absent or call.name equals result.name. Preserve the existing
!call.id and !call.toolResult constraints.
In `@frontend/src/components/features/chat/transport/openClawHistoryLoader.ts`:
- Around line 375-409: Change `#loadFresh` and `#load` so history() returns bounded
preview rows without awaiting full `#hydrateRows` completion. Refactor
`#hydrateRows` to hydrate truncated rows asynchronously and patch each resolved
message into the displayed/cached history through the existing update path,
preserving ordering and avoiding stale updates after reset or reconnect. Ensure
hydration failures do not block or replace the initial preview response, and
verify typical long-running sessions’ truncated-row counts to validate the
latency impact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7a416527-347b-471a-827d-fcf39e3a2e38
📒 Files selected for processing (39)
backend/src/chat/openClawChatBridge.tsbackend/src/chat/openClawChatRetention.tsbackend/src/gateway.tsbackend/src/routes/taskRoutes.tsbackend/test/gatewayBehavior.test.tsbackend/test/openClawChatBridge.test.tsbackend/test/routeAndServiceBehavior.test.tsbackend/test/serviceBehavior.test.tscontracts/chat/openClawAdapterValues.tscontracts/chat/openClawHistoryNormalizer.tscontracts/chat/openClawHistoryPageAdapter.tscontracts/chat/openClawRuntimeAdapter.tscontracts/chat/openClawToolAdapter.tscontracts/chatCanonical.tscontracts/chatCanonicalHistory.tscontracts/chatCanonicalTurn.tsfrontend/src/components/features/chat/AttachmentPreviewModal.tsxfrontend/src/components/features/chat/ChatAttachmentPickerModal.tsxfrontend/src/components/features/chat/ChatMessagesList.tsxfrontend/src/components/features/chat/chatTypes.tsfrontend/src/components/features/chat/domain/chatCanonicalProjection.tsfrontend/src/components/features/chat/domain/chatPresentation.tsfrontend/src/components/features/chat/domain/chatProjection.tsfrontend/src/components/features/chat/domain/chatState.tsfrontend/src/components/features/chat/transport/openClawHistoryLoader.tsfrontend/src/components/features/chat/transport/useOpenClawChatTransport.tsfrontend/src/components/features/chat/useChatRuntime.tsfrontend/src/components/features/files/viewers/JsonPreview.tsxfrontend/src/components/features/files/viewers/MarkdownPreview.tsxfrontend/src/components/ui/Modal.tsxfrontend/src/test/chatCanonicalHistory.test.tsfrontend/src/test/chatCanonicalProjection.test.tsfrontend/src/test/chatProjection.test.tsfrontend/src/test/chatRuntimeController.test.tsxfrontend/src/test/chatState.test.tsfrontend/src/test/componentBehavior.test.tsxfrontend/src/test/openClawAdapterVariants.test.tsfrontend/src/test/openClawHistoryLoader.test.tsfrontend/src/test/support/canonicalChatHistory.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
- GitHub Check: backend-checks
- GitHub Check: Analyze JavaScript and TypeScript
🔇 Additional comments (41)
frontend/src/components/features/chat/AttachmentPreviewModal.tsx (1)
71-95: LGTM!Also applies to: 196-197, 227-241, 258-258
frontend/src/components/features/chat/ChatAttachmentPickerModal.tsx (1)
175-193: LGTM!frontend/src/components/features/files/viewers/JsonPreview.tsx (1)
25-35: LGTM!frontend/src/components/features/files/viewers/MarkdownPreview.tsx (1)
8-8: LGTM!Also applies to: 22-27
frontend/src/components/ui/Modal.tsx (1)
15-15: LGTM!Also applies to: 36-36, 82-90
frontend/src/test/componentBehavior.test.tsx (1)
549-560: LGTM!Also applies to: 1270-1275
contracts/chat/openClawHistoryPageAdapter.ts (1)
54-73: LGTM!contracts/chatCanonicalHistory.ts (1)
16-22: LGTM!Also applies to: 69-81
frontend/src/components/features/chat/transport/openClawHistoryLoader.ts (1)
260-286: LGTM!Also applies to: 320-373
frontend/src/components/features/chat/transport/useOpenClawChatTransport.ts (1)
29-32: LGTM!frontend/src/test/openClawHistoryLoader.test.ts (1)
6-31: LGTM!Also applies to: 64-118
frontend/src/test/support/canonicalChatHistory.ts (1)
1-6: LGTM!Also applies to: 50-62
frontend/src/test/chatCanonicalHistory.test.ts (1)
16-94: LGTM!contracts/chat/openClawAdapterValues.ts (1)
187-208: LGTM!contracts/chat/openClawHistoryNormalizer.ts (1)
49-63: LGTM!Also applies to: 428-428, 446-456
contracts/chat/openClawRuntimeAdapter.ts (1)
15-15: LGTM!Also applies to: 118-133, 234-235, 362-395, 424-426, 528-531, 586-593
contracts/chat/openClawToolAdapter.ts (1)
18-18: LGTM!Also applies to: 240-244
contracts/chatCanonical.ts (1)
77-80: LGTM!Also applies to: 129-139
backend/src/chat/openClawChatBridge.ts (1)
1336-1343: LGTM!backend/src/gateway.ts (1)
5-8: LGTM!Also applies to: 1341-1351, 1784-1784
frontend/src/test/openClawAdapterVariants.test.ts (1)
917-966: LGTM!Also applies to: 1026-1061
backend/test/serviceBehavior.test.ts (1)
6460-6462: 🎯 Functional CorrectnessNo change needed.
The enclosing test callback is already
async, and the un-awaited chained.rejectsmatcher is valid in this test file; awaiting it is optional for this assertion.contracts/chatCanonicalTurn.ts (1)
30-30: LGTM!backend/src/routes/taskRoutes.ts (1)
323-328: LGTM!frontend/src/components/features/chat/ChatMessagesList.tsx (1)
545-545: LGTM!frontend/src/components/features/chat/chatTypes.ts (1)
624-628: LGTM!frontend/src/components/features/chat/domain/chatCanonicalProjection.ts (1)
143-148: LGTM!frontend/src/components/features/chat/domain/chatPresentation.ts (1)
73-74: LGTM!Also applies to: 346-352
frontend/src/components/features/chat/domain/chatProjection.ts (3)
126-146: LGTM!Also applies to: 233-262
1413-1439: LGTM!Also applies to: 1768-1830, 1842-1854, 1902-1902, 1929-1933
2150-2161: LGTM!Also applies to: 2180-2198
frontend/src/test/chatCanonicalProjection.test.ts (1)
650-650: LGTM!Also applies to: 960-960, 1204-1241
backend/src/chat/openClawChatRetention.ts (1)
83-88: LGTM!backend/test/gatewayBehavior.test.ts (1)
623-653: LGTM!backend/test/openClawChatBridge.test.ts (1)
3546-3621: LGTM!backend/test/routeAndServiceBehavior.test.ts (1)
944-947: LGTM!Also applies to: 964-964
frontend/src/components/features/chat/domain/chatState.ts (1)
30-31: LGTM!Also applies to: 44-44, 66-66, 110-118, 165-166, 312-312, 352-352, 649-669, 683-699, 910-985, 1027-1058, 1115-1115, 1274-1303, 1350-1352, 1388-1390, 1417-1437, 1614-1614
frontend/src/components/features/chat/useChatRuntime.ts (1)
95-95: LGTM!Also applies to: 124-162, 286-291, 292-315, 317-562
frontend/src/test/chatProjection.test.ts (1)
947-1194: LGTM!Also applies to: 2567-2573
frontend/src/test/chatRuntimeController.test.tsx (1)
105-143: LGTM!frontend/src/test/chatState.test.ts (1)
61-155: LGTM!
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a0e86690fa
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/test/pullRequestPreviewGatewayProxy.test.ts`:
- Around line 470-480: Await the promise returned by socket.next() in the
allowed-full-message assertion before applying toMatchObject, ensuring the test
waits for the proxy response and validates the forwarding result.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0a737ede-1e7c-45a0-9892-0507212e905e
📒 Files selected for processing (14)
backend/src/development/developmentGatewayPolicy.tsbackend/src/gateway.tsbackend/test/bunNativeServerBehavior.test.tsbackend/test/gatewayBehavior.test.tsbackend/test/pullRequestPreviewGatewayProxy.test.tsbackend/test/utilityBehavior.test.tscontracts/chat/openClawHistoryPageAdapter.tscontracts/chatCanonicalHistory.tsfrontend/src/components/features/chat/chatUtilities.tsfrontend/src/components/features/chat/domain/chatState.tsfrontend/src/components/features/chat/transport/openClawHistoryLoader.tsfrontend/src/test/chatCanonicalHistory.test.tsfrontend/src/test/chatProjection.test.tsfrontend/src/test/openClawHistoryLoader.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- contracts/chat/openClawHistoryPageAdapter.ts
- backend/src/gateway.ts
- frontend/src/test/openClawHistoryLoader.test.ts
- contracts/chatCanonicalHistory.ts
- frontend/src/components/features/chat/transport/openClawHistoryLoader.ts
- frontend/src/components/features/chat/domain/chatState.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
- GitHub Check: Analyze JavaScript and TypeScript
- GitHub Check: backend-checks
- GitHub Check: frontend-checks
🔇 Additional comments (8)
backend/src/development/developmentGatewayPolicy.ts (1)
3-3: LGTM!Also applies to: 23-23
backend/test/bunNativeServerBehavior.test.ts (1)
493-507: LGTM!backend/test/gatewayBehavior.test.ts (1)
223-244: LGTM!Also applies to: 645-675, 1510-1592
backend/test/pullRequestPreviewGatewayProxy.test.ts (1)
526-526: LGTM!Also applies to: 549-549, 565-565
backend/test/utilityBehavior.test.ts (1)
674-674: LGTM!Also applies to: 695-703
frontend/src/test/chatProjection.test.ts (1)
83-92: LGTM!Also applies to: 957-1016, 1018-1076, 1078-1149, 1151-1231, 2604-2610
frontend/src/components/features/chat/chatUtilities.ts (1)
741-747: 🎯 Functional CorrectnessNo change needed.
applyControlEventassigns a session-uniqueruntimeKey(control:${controlId || event.id || event.sequence}) whencontrolIdis absent, so runtime controls keep distinctmessageIdentitykeys and are not deduplicated as the same text-only control row.frontend/src/test/chatCanonicalHistory.test.ts (1)
96-112: LGTM!Also applies to: 114-135
## Summary - split the agents, cache refresh, Docker updater, log rotation, and pull-request services into explicit source, producer, policy, persistence, runtime, and scheduler seams while preserving their public entry modules - extract Delivery, Docker, Jobs, Task detail, ChatComposer, and chat-projection controllers/panels/models from large frontend units - close the remaining P2 seams with per-producer cache modules, separate Docker update policy/notifications, a session-file source, and post-stability chat projection identity/diagnostics This PR is the top layer of native GitHub stack #367 and is based on #365 (`b5a51638`). Its diff contains only the decomposition work. `git range-diff` reports all ten decomposition commits as patch-equivalent after the final rebase. ## Behavior and regression coverage - public imports and route/service contracts are preserved; this is an internal restructuring with no intended API or product behavior change - cache scheduling, serial execution, and producer registration now have explicit boundaries - Docker discovery, registry polling, update policy, compose transactions, notifications, persistence, and scheduling are independently reviewable - the chat projection seams were re-extracted from the verified replay-stability implementation so stable control/response/tool identities are retained ## Verification - [x] Repository lint: `bun run lint` - [x] Repository formatting: `bun run format:check` - [x] Frontend build/typecheck: `bun run build:frontend` - [x] Frontend tests/coverage: full suite passed (**680 tests**, 4,413 expectations); the 85% coverage gate passed on head `08eea037` - [x] Backend build/typecheck: `bun run build:backend` - [x] Backend tests/coverage: full suite passed (**719 tests**, 4,612 expectations); the 85% coverage gate passed on head `08eea037` - [x] Focused regression tests: projection integration (**265**) and backend service seams (**208**) passed before the patch-equivalent final rebase - [x] Rebase audit: all ten commits match their pre-rebase patches via `git range-diff` - [x] Native stack audit: stack #367 is `main ← #365 b5a5163 ← #366 08eea03`, with `needsRebase: false` for both layers - [ ] Manual UI/API smoke check: not run; no live state was changed - [x] `git diff --check` ## Risk checklist - [x] No secrets, tokens, `.env` files, database dumps, runtime state, lockfiles, or configuration changes committed - [x] Gateway, Docker, filesystem, scheduling, and notification boundaries were reviewed carefully - [x] No new or changed public API routes - [x] No migrations or persisted data-shape changes - [x] Runtime and scheduling behavior remains behind the original public entry points and characterization tests - [x] Frontend changes are boundary extractions with no intended visual change, so screenshots are not applicable ## Deployment / operations - [x] Standard Dashboard release/restart needed after the stack merges - [x] No config or secret changes needed - [x] Rollback is commit/PR reversion; there are no migrations to unwind ## Notes for reviewers Reviewing commit-by-commit follows the original seam batches: log rotation, agents, cache refresh, Docker updater, Delivery/pull requests, frontend controllers, and chat projection. Large cohesive internals such as the security-sensitive log-rotation core and GitHub client were deliberately retained rather than split solely by line count. GitHub Dashboard/CodeQL workflows are filtered to `main`-based pull requests, so this stacked PR relies on the full local verification above until its base advances to `main`. Dashboard task: #388
Summary
chat.injectfollowed by an immediate wake, without creating a synthetic humanchat.sendchat.message.getrequests (four workers, 128-entry cache, 2M-character ceiling), including transcript-backed omitted imagesBehavior and regression coverage
Verification
bun run lintbun run format:checkbun run build:frontendbun run build:backendgit diff --checkRisk checklist
.envfiles, database dumps, or runtime state committedDeployment / operations
Notes for reviewers
Please focus on inject-before-wake ordering, control retention boundaries, full-message identity/image hydration, and row identity during live-to-history settlement.
Verified against the installed and running OpenClaw v2026.7.1-2 (
0790d9f): the publicchat.injectschema exposes onlysessionKey, optionalagentId,message, and optionallabel, with additional properties disabled. Its installed validator rejectsidempotencyKey. Dashboard therefore does not retry the combined inject-then-wake sequence after a partial failure; a wake failure is logged while the durable injected control remains visible.Non-blocking coverage gaps: the inject/wake test uses a fake Gateway client; the four-worker hydration ceiling is not directly stress-tested; and the pre-ack runless-assistant row race has no dedicated regression test.
Native GitHub stack: #367 (bottom layer; #366 is stacked above this PR).
Dashboard task: #389