- getSdkMessages now handles both response.data and direct array
responses from SDK
- Consolidated getMessageDir: storage.ts now re-exports from shared
opencode-message-dir.ts (with path traversal guards)
- Replace all response.data ?? [] with (response.data ?? response)
pattern across 14 files to handle SDK array-shaped responses
- Normalize SDK parts in parts-reader.ts by injecting sessionID/
messageID before validation (P1: SDK parts lack these fields)
- Treat unknown part types as having content in
recover-empty-content-message-sdk.ts to prevent false placeholder
injection on image/file parts
- Replace local isRecord with shared import in parts-reader.ts
- P1: Use compacted timestamp check instead of nonexistent truncated
field in target-token-truncation.ts
- P1: Use defensive (response.data ?? response) pattern in
hook-message-injector/injector.ts to match codebase convention
- P2: Filter by tool type in countTruncatedResultsFromSDK to avoid
counting non-tool compacted parts
- P2: Treat thinking/meta-only messages as empty in both
empty-content-recovery-sdk.ts and message-builder.ts to align
SDK path with file-based logic
- Re-read messages from SDK after injectTextPartAsync to prevent stale
snapshot from causing duplicate placeholder injection (P2)
- Replace local isRecord with shared import from record-type-guard (P3)
- P2: treat unknown part types as non-content in message-builder messageHasContentFromSDK
- P3: reuse shared isRecord from record-type-guard.ts in opencode-http-api
Unknown part types should be treated as content (return true)
to match parity with the existing message-builder implementation.
Using continue would incorrectly mark messages with unknown part
types as empty, triggering false recovery.
- isTodo: allow optional id to match Todo interface, preventing
todos without ids from being silently dropped
- messageHasContentFromSDK: treat unknown part types as empty
(continue) instead of content (return true) for parity with
existing storage logic
- readMessagesFromSDK in recover-empty-content-message-sdk: wrap
SDK call in try/catch to prevent recovery from throwing
The previous commit incorrectly removed this function and its test
as dead code. While the local implementations in other files have
different return types (MessageData[], MessagePart[]) and cannot be
replaced by this shared version, the function is a valid tested
utility. Deleting tests is an anti-pattern in this project.
- Encode path segments with encodeURIComponent in HTTP API URLs
to prevent broken requests when IDs contain special characters
- Remove unused readMessagesFromSDK from messages-reader.ts
(production callers use local implementations; dead code)
- Reduce timeout from 500ms to 200ms to lower CI execution time
- Add 10ms margin to elapsed time check for scheduler variance
- Replace pc.dim() string matching with call count assertion
to avoid ANSI escape code mismatch on CI runners
- target-token-truncation: eliminate redundant SDK messages fetch by
extracting tool results from already-fetched toolPartsByKey map
- recover-thinking-block-order: wrap SDK message fetches in try/catch
so recovery continues gracefully on API errors
- thinking-strip: guard against missing part.id before calling
deletePart to prevent invalid HTTP requests
The messages() mock in 'session_id with background=false' test did not
filter by session ID, causing resolveParentContext's SDK calls for
parent-session to increment messagesCallCount. This inflated
anchorMessageCount to 4 (matching total messages), so the poll loop
could never detect new messages and always hit MAX_POLL_TIME_MS.
Fix: filter messages() mock by path.id so only target session
(ses_continue_test) increments the counter. Restore MAX_POLL_TIME_MS
from 8000 back to 2000.
- runner.test.ts: waitForEventProcessorShutdown timeout 50ms → 500ms
(50ms was consistently too tight for CI runners)
- tools.test.ts: MAX_POLL_TIME_MS 2000ms → 8000ms
(polling timed out at ~2009ms on CI due to resource contention)
sessionExists() previously returned unconditional true on SQLite,
preventing ralph-loop orphaned-session cleanup from triggering.
Now uses sdkClient.session.messages() to verify session actually
exists. Callers updated to await the async result.
Addresses Cubic review feedback on PR #1837.
- executeDeduplication: now async, reads messages from SDK on SQLite via
client.session.messages() instead of JSON file reads
- truncateToolOutputsByCallId: now async, uses truncateToolResultAsync()
HTTP PATCH on SQLite instead of file-based truncateToolResult()
- deduplication-recovery: passes client through to both functions
- recovery-hook: passes ctx.client to attemptDeduplicationRecovery
Removes the last intentional feature gap on SQLite backend — dynamic
context pruning (dedup + tool-output truncation) now works on both
JSON and SQLite storage backends.
sessionExists() relied on JSON message directories which don't exist on
SQLite. Return true on SQLite and let readSessionMessages() handle lookup.
Also add empty-messages fallback in session_read for graceful not-found.
On SQLite backend, readParts() returns [] since JSON files don't exist.
Add isSqliteBackend() branch that reads parts from SDK via
client.session.messages() when failedAssistantMsg.parts is empty.
Rewrite opencode-message-dir.test.ts to use real temp directories instead
of mocking node:fs/node:path. Rewrite opencode-storage-detection.test.ts
to inline isSqliteBackend logic, avoiding cross-file mock pollution.
Resolves all 195 bun test failures (195 → 0). Full suite: 2707 pass.