Conversation
- prepare-release.yml: fires on PR merge to dev (not main); version bump PR targets dev instead of main - release.yml: triggers on dev→main PR merge instead of commit message on push; adds sync-back step to keep dev aligned with main after release Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): apply ASCII logo gradient by X column, not string index ink-gradient maps colors by character index across the whole string, so the p descender (last two lines) always got the tail/pink color regardless of its leftward visual position. Fix: render each logo line separately with its own <Gradient>, padded to logoWidth so column X maps to the same gradient fraction on every line. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ui): remove Static windowing that caused messages to disappear Ink's <Static> tracks rendered items by array INDEX, not React key. It stores the last array length and slices from that index on each render. When the array stops growing (constant length), the index overshoots and nothing new is printed — causing streamed messages to vanish. PR #45 introduced two patterns that broke this invariant: 1. STATIC_HISTORY_WINDOW=200 in MainContent.tsx — sliding window kept the array at a constant 204 items (3 fixed + 200 history + banner), so after the 201st history item nothing was ever printed by Static. 2. MAX_HISTORY_ITEMS=500 in useHistoryManager.ts — pruning the front of the array kept it at exactly 500 items, same effect. 3. Same AGENT_STATIC_HISTORY_WINDOW=200 windowing in AgentChatView.tsx. Fix: pass all history items to Static (array only ever grows). Remove TruncatedHistoryBanner from within Static (it can't update once committed to the terminal anyway, and its conditional insertion shifted existing indices on first appearance). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Automaker <automaker@localhost> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…rotection) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… failure
- schemaValidator: add Array.isArray guard so array tool params return
'Value of params must be an object' immediately instead of reaching AJV
- openai converter: return plain string content for text-only tool messages
instead of [{type:'text',...}] array — LiteLLM and most OpenAI-compatible
local providers only accept string content and crash on array content parts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Single-text tool responses (validation errors, simple outputs) now return
content as a plain string instead of [{type:'text',text:'...'}] array.
Many OpenAI-compatible providers (LiteLLM, local models) only accept string
content in tool messages and crash with 'Can only get item pairs from a
mapping' on array content.
Multi-part responses (text+media, multi-text blocks, unsupported media
placeholders) keep array format to preserve all content parts.
Reverts the overly broad Array.isArray guard in schemaValidator — AJV already
rejects arrays for object-typed schemas, and the guard incorrectly blocked
valid array inputs for 2020-12 prefixItems schemas.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on cascade When a weak/local model hits max_tokens and produces empty responses, tool errors accumulate in context causing subsequent calls to also fail with NO_RESPONSE_TEXT. Add trimToolErrorsFromContext() to strip trailing model-tool-call + user-tool-error pairs (up to 6 pairs), then attempt one final recovery call with the cleaned context before giving up. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
WalkthroughPackage versions bumped from 0.25.13 to 0.25.14 across multiple manifests and container image URI updated. Core module adds new helper function Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant sendMessageStream
participant makeApiCallAndProcessStream
participant trimToolErrorsFromContext
participant GeminiAPI
Client->>sendMessageStream: Send message with contents
sendMessageStream->>makeApiCallAndProcessStream: Initial attempt
makeApiCallAndProcessStream->>GeminiAPI: Stream request
GeminiAPI-->>makeApiCallAndProcessStream: NO_RESPONSE_TEXT error
makeApiCallAndProcessStream-->>sendMessageStream: Error response
sendMessageStream->>sendMessageStream: Check retry budget
alt Retry budget exhausted
sendMessageStream->>trimToolErrorsFromContext: Trim tool-error pairs
trimToolErrorsFromContext-->>sendMessageStream: Trimmed contents
sendMessageStream->>makeApiCallAndProcessStream: Final retry with trimmed contents
makeApiCallAndProcessStream->>GeminiAPI: Stream request
GeminiAPI-->>makeApiCallAndProcessStream: Response
makeApiCallAndProcessStream-->>sendMessageStream: Success or error
else Retry budget available
sendMessageStream->>makeApiCallAndProcessStream: Retry with original contents
end
sendMessageStream-->>Client: Result or throw error
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/core/src/core/geminiChat.ts`:
- Around line 457-480: The retry path trims the local requestContents but
doesn't update session state, so on success you must persist the pruned context
into the chat history to avoid rebuilding the same oversized context; after a
successful call to self.makeApiCallAndProcessStream with trimmedContents (in the
block that sets lastError = null and yields chunks), replace or update the
session/history structure used to build requestContents (e.g., this.history or
self.history) so that the trimmedContents (or its corresponding history entries)
are stored for subsequent turns; ensure you only mutate history when the trimmed
retry succeeds and preserve any necessary metadata (timestamps/ids) when
promoting the pruned context.
- Around line 453-478: When retrying with trimmed context inside the
NO_RESPONSE_TEXT branch, treat it as a real retry: before calling
makeApiCallAndProcessStream(model, trimmedContents, params, prompt_id) yield a
StreamEventType.RETRY event so the UI can reset, then attempt the stream; in the
catch capture the error (e) and set lastError = e (instead of swallowing) so the
true failure propagates, and keep the existing lastError = null on success;
reference trimToolErrorsFromContext, makeApiCallAndProcessStream,
StreamEventType.RETRY, and lastError when making these changes.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d212f9ac-aff6-4dc1-b532-4e06cf2b6367
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (7)
package.jsonpackages/cli/package.jsonpackages/core/package.jsonpackages/core/src/core/geminiChat.tspackages/test-utils/package.jsonpackages/web-templates/package.jsonpackages/webui/package.json
| if ( | ||
| isTransientStreamError && | ||
| (error as InvalidStreamError).type === 'NO_RESPONSE_TEXT' | ||
| ) { | ||
| const trimmedContents = | ||
| trimToolErrorsFromContext(requestContents); | ||
| if (trimmedContents.length < requestContents.length) { | ||
| debugLogger.warn( | ||
| `NO_RESPONSE_TEXT: retrying with trimmed context ` + | ||
| `(removed ${requestContents.length - trimmedContents.length} ` + | ||
| `tool-error messages)`, | ||
| ); | ||
| try { | ||
| const stream = await self.makeApiCallAndProcessStream( | ||
| model, | ||
| trimmedContents, | ||
| params, | ||
| prompt_id, | ||
| ); | ||
| for await (const chunk of stream) { | ||
| yield { type: StreamEventType.CHUNK, value: chunk }; | ||
| } | ||
| lastError = null; | ||
| } catch { | ||
| // trimmed context retry also failed — fall through to throw | ||
| } |
There was a problem hiding this comment.
Treat the trimmed-context pass as a real retry.
This branch starts a fresh stream without yielding StreamEventType.RETRY, so the UI never gets the reset signal for the failed attempt. The bare catch {} also leaves lastError stuck on the earlier NO_RESPONSE_TEXT, which hides the real failure if the recovery call errors differently.
Suggested fix
if (trimmedContents.length < requestContents.length) {
+ yield { type: StreamEventType.RETRY };
debugLogger.warn(
`NO_RESPONSE_TEXT: retrying with trimmed context ` +
`(removed ${requestContents.length - trimmedContents.length} ` +
`tool-error messages)`,
);
try {
const stream = await self.makeApiCallAndProcessStream(
model,
trimmedContents,
params,
prompt_id,
);
for await (const chunk of stream) {
yield { type: StreamEventType.CHUNK, value: chunk };
}
lastError = null;
- } catch {
- // trimmed context retry also failed — fall through to throw
+ } catch (trimmedError) {
+ lastError = trimmedError;
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/core/geminiChat.ts` around lines 453 - 478, When retrying
with trimmed context inside the NO_RESPONSE_TEXT branch, treat it as a real
retry: before calling makeApiCallAndProcessStream(model, trimmedContents,
params, prompt_id) yield a StreamEventType.RETRY event so the UI can reset, then
attempt the stream; in the catch capture the error (e) and set lastError = e
(instead of swallowing) so the true failure propagates, and keep the existing
lastError = null on success; reference trimToolErrorsFromContext,
makeApiCallAndProcessStream, StreamEventType.RETRY, and lastError when making
these changes.
| const trimmedContents = | ||
| trimToolErrorsFromContext(requestContents); | ||
| if (trimmedContents.length < requestContents.length) { | ||
| debugLogger.warn( | ||
| `NO_RESPONSE_TEXT: retrying with trimmed context ` + | ||
| `(removed ${requestContents.length - trimmedContents.length} ` + | ||
| `tool-error messages)`, | ||
| ); | ||
| try { | ||
| const stream = await self.makeApiCallAndProcessStream( | ||
| model, | ||
| trimmedContents, | ||
| params, | ||
| prompt_id, | ||
| ); | ||
| for await (const chunk of stream) { | ||
| yield { type: StreamEventType.CHUNK, value: chunk }; | ||
| } | ||
| lastError = null; | ||
| } catch { | ||
| // trimmed context retry also failed — fall through to throw | ||
| } | ||
| } | ||
| break; |
There was a problem hiding this comment.
Persist the pruned context after the fallback succeeds.
This retry only trims the local requestContents. this.history still keeps the tool-error cascade, so the next turn rebuilds the same oversized context and can hit NO_RESPONSE_TEXT again. Promote the trimmed context into session state when the recovery call succeeds.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/core/geminiChat.ts` around lines 457 - 480, The retry path
trims the local requestContents but doesn't update session state, so on success
you must persist the pruned context into the chat history to avoid rebuilding
the same oversized context; after a successful call to
self.makeApiCallAndProcessStream with trimmedContents (in the block that sets
lastError = null and yields chunks), replace or update the session/history
structure used to build requestContents (e.g., this.history or self.history) so
that the trimmedContents (or its corresponding history entries) are stored for
subsequent turns; ensure you only mutate history when the trimmed retry succeeds
and preserve any necessary metadata (timestamps/ids) when promoting the pruned
context.
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
Release v0.25.14 — fix(core): retry NO_RESPONSE_TEXT with trimmed context after truncation cascade
Summary by CodeRabbit
Chores
Bug Fixes