Skip to content

Feat:Migration of tiny robot version to 0.4.0#1804

Open
lichunn wants to merge 4 commits into
opentiny:developfrom
lichunn:feat/robot-330
Open

Feat:Migration of tiny robot version to 0.4.0#1804
lichunn wants to merge 4 commits into
opentiny:developfrom
lichunn:feat/robot-330

Conversation

@lichunn
Copy link
Copy Markdown
Collaborator

@lichunn lichunn commented May 14, 2026

English | 简体中文

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Built its own designer, fully self-validated

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Background and solution

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features

    • Voice input (speech-to-text) and single-image upload in chat.
    • Aborted conversations now show an “已中止” indicator.
  • Improvements

    • Enhanced message rendering and streaming with more reliable partial merging.
    • Refined tool-call execution and state handling.
    • Better request interruption/management and schema update robustness.
  • Updates

    • Robot plugin dependencies bumped to v0.4.0.

Review Change Stack

@github-actions github-actions Bot added the enhancement New feature or request label May 14, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Walkthrough

Migrate the plugin from AIClient to OpenAICompatibleProvider; rebuild conversation adapter with an async response provider and plugin-based finish handling; add STATUS/message-state primitives; coordinate stream/tool-call flows and versioned page-schema updates; update UI renderers and props to consume new shapes.

Changes

Provider Migration & Conversation Refactor

Layer / File(s) Summary
Status constants and dependencies foundation
packages/plugins/robot/src/constants/status.ts, packages/plugins/robot/src/constants/index.ts, packages/plugins/robot/package.json
Add STATUS enum, GeneratingStatus, MessageState; re-export status; bump tiny-robot packages to 0.4.0.
AI provider module refactoring
packages/plugins/robot/src/services/aiClient.ts
Remove AIClient instantiation; export an OpenAICompatibleProvider instance and retain getClientConfig/updateClientConfig.
Conversation adapter architecture rewrite
packages/plugins/robot/src/composables/core/useConversation.ts
Reimplement adapter to use createResponseProvider async generator wrapping provider.chatStream, update message metadata per chunk, use adapter plugin for finish/error, and return wrapped apis with localStorage strategy.
Stream delta and tool-call processing
packages/plugins/robot/src/composables/core/useMessageStream.ts, packages/plugins/robot/src/composables/features/useToolCalls.ts
Add "already merged" flags to delta handlers; switch tool-call continuation to provider.chatStream, mark toolsHandled, and insert assistant placeholder before continuing streaming.
Chat mode and agent mode behaviors
packages/plugins/robot/src/composables/modes/useChatMode.ts, packages/plugins/robot/src/composables/modes/useAgentMode.ts, packages/plugins/robot/src/composables/useChat.ts
Coordinate tool-call state + render content updates; add request interruption helpers; expose mappedStatus; reset/consume page-schema update state in agent flows.
Versioned page schema updater
packages/plugins/robot/src/composables/core/pageUpdater.ts
Introduce schemaUpdateVersion/lastSuccessfulSchema, async setSchema, _updatePageSchema with version guard, validation via isValidSchemaChildren, throttled wrapper, and reset/get APIs.
Schema utilities and JSON extraction
packages/plugins/robot/src/utils/schema.utils.ts
Export isPlainObject, add isValidSchemaChildren, improve fenced JSON extraction and tolerant JSON parse with jsonrepair fallback.
UI components: Chat, Header, Markdown, Agent renderer
packages/plugins/robot/src/Main.vue, packages/plugins/robot/src/components/chat/RobotChat.vue, packages/plugins/robot/src/components/header-extension/History.vue, packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue, packages/plugins/robot/src/components/renderers/AgentRenderer.vue
Bind mappedStatus in Main; RobotChat switches to matcher-based renderer selection, adds aborted footer and sender footer controls; History uses ConversationInfo; MarkdownRenderer accepts message object prop; AgentRenderer resolves display from message.renderContent.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped from client to streaming shore,

provider whispers, chunks pour,
Status flags and schema care,
Renderers match, voice buttons share,
Conversations saved — a tidy chore.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately reflects the primary change: migrating the tiny-robot plugin dependencies and associated codebase from version 0.3.1 to 0.4.0, which is evident across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/plugins/robot/src/composables/useChat.ts (3)

95-122: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard against lastMessage being undefined.

messages.at(-1) returns ChatMessage | undefined, but Line 98 (lastMessage.content) and Line 120 (lastMessage.content ?? '') both dereference it directly. If messages is ever empty when finish handling runs (e.g. an aborted request before any assistant message lands), this throws a TypeError. Add optional chaining or an early return.

🛡️ Proposed fix
 const lastMessage = messages.at(-1)
+ if (!lastMessage) return
 
 delete abortControllerMap.main
 await onRequestEnd(finishReason, lastMessage.content, messages) // 本次请求结束
🤖 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 `@packages/plugins/robot/src/composables/useChat.ts` around lines 95 - 122, The
code assumes lastMessage = messages.at(-1) is always defined but it can be
undefined; update the endRequest/finalization logic to guard against that by
early-returning when lastMessage is falsy or using optional chaining when
accessing lastMessage.content and lastMessage.tool_calls; specifically adjust
the usages in this block (references: lastMessage, messages.at, onRequestEnd,
handleToolCall, onMessageProcessed, messageState, chatStatus) so you call
onRequestEnd with a safe string (or return early), skip tool handling and state
transitions when lastMessage is undefined, and avoid direct dereferences like
lastMessage.content or lastMessage.tool_calls without checks.

124-129: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Same messages.at(-1) undefined risk on Line 127.

messages.at(-1).content can throw when the array is empty (e.g. error fired before any message was appended). Mirror the optional-chaining fix from handleFinishRequest.

🛡️ Proposed fix
-  await onRequestEnd('error', messages.at(-1).content, messages, { error })
+  await onRequestEnd('error', messages.at(-1)?.content ?? '', messages, { error })
🤖 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 `@packages/plugins/robot/src/composables/useChat.ts` around lines 124 - 129, In
handleRequestError, avoid directly calling messages.at(-1).content which can
throw when messages is empty; mirror the fix from handleFinishRequest by using
optional chaining and a safe fallback (e.g., messages.at(-1)?.content ?? '')
when calling onRequestEnd so the function won’t throw if there’s no last
message; update the call site in handleRequestError accordingly.

1-317: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Run pnpm build:plugin to validate the package build.

The robot plugin package has no local test script; run the repository-level pnpm build:plugin command to ensure the changes compile correctly and published package behavior is unaffected. This is required by coding guidelines when build output or published behavior may change.

🤖 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 `@packages/plugins/robot/src/composables/useChat.ts` around lines 1 - 317, Run
the repository-level build command `pnpm build:plugin` to reproduce the
compilation errors for the robot plugin and fix whatever TypeScript/build
problems surface in the package (packages/plugins/robot). Focus on correcting
imports/exports and types referenced in useChat.ts—check symbols like
initChatClient, beforeRequest, createStreamDataHandler, createToolCallHandler,
useConversationAdapter, createConversationWithMode and sendUserMessage for
missing/incorrect types or broken imports; iterate until `pnpm build:plugin`
completes successfully, then commit the fixes.
packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue (1)

30-43: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix incorrect prop structure and unsafe default in MarkdownRenderer.

The message prop is misnamed and incorrectly typed. This renderer should receive a content prop (a markdown string to render), not a message object, consistent with other renderers like ImgRenderer and LoadingRenderer. The current implementation accesses props.message.content (line 69), which:

  1. Uses the wrong prop name and type (Options is markdown-it's config type, not a message type)
  2. Has an unsafe default: with default: () => ({}), props.message.content is undefined, and markdownIt.render(undefined) throws at runtime

Change the message prop to content (string), make it required or default to an empty string, and access it directly:

🛡️ Proposed fix
 const props = defineProps({
-  message: {
-    type: Object as () => Options,
-    default: () => ({})
+  content: {
+    type: String,
+    required: true
   },
   theme: {
     type: String as () => 'light' | 'dark',
     default: 'light'
   },
   options: {
     type: Object as () => Options,
     default: () => ({})
   }
 })

 const renderContent = computed(() => {
-  return DOMPurify.sanitize(markdownIt.render(props.message.content))
+  return DOMPurify.sanitize(markdownIt.render(props.content))
 })

Also applies to: line 69

🤖 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 `@packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue` around
lines 30 - 43, The prop definition in MarkdownRenderer (defineProps) is wrong:
replace the current message prop (typed as Options with default {}) with a
content prop of type String that is either required or has a safe default (e.g.,
empty string), keep theme and options as-is, and update all usages that read
props.message.content (e.g., where markdownIt.render is called) to use
props.content directly so markdownIt.render never receives undefined.
🤖 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 `@packages/plugins/robot/src/components/chat/RobotChat.vue`:
- Around line 153-185: The issue is that the markdown and image matchers in
contentRendererMatches inspect message.content instead of the per-item content
parameter, causing wrong matches; update the Markdown matcher (currently using
MarkdownRenderer) to use the signature find: (message: any, content: any) =>
!message.loading && content && content.type !== 'img' && content.type !==
'image' && content.type !== 'tool' (or similar check that content exists and is
not an image/tool), and update the Img matcher (currently using ImgRenderer) to
use find: (message: any, content: any) => content?.type === 'img' ||
content?.type === 'image' so both follow the same per-item content pattern as
Tools, Reasoning, and custom renderers.

In `@packages/plugins/robot/src/composables/modes/useChatMode.ts`:
- Around line 41-47: The updateToolCallRenderContent function lacks the same
guard as updateToolCallState and may match or create entries with an undefined
tool.id; add an early return at the top of updateToolCallRenderContent when
tool.id is falsy (same behavior as in updateToolCallState) so you never call
renderContent.find(... toolCallId === tool.id) or push a new entry with
toolCallId: undefined; reference the function name updateToolCallRenderContent
and the symbol tool.id/toolCallId when making the change.

In `@packages/plugins/robot/src/composables/useChat.ts`:
- Around line 198-214: abortRequest currently calls the async onRequestEnd
fire-and-forget and coerces message content with "as string", causing race
conditions, possible NPEs, and unhandled rejections; make abortRequest async,
synchronously capture a safe snapshot of the last message content and the
messages array (or await onRequestEnd before allowing conversation switch),
replace the unsafe "as string" cast with a guarded nullable value (e.g., check
existence before passing), and wrap the onRequestEnd invocation in try/catch (or
await it and propagate errors) so interruptActiveRequest can await abortRequest
and avoid appending the "aborted" block to the wrong conversation; reference
abortRequest, interruptActiveRequest, onRequestEnd, and messageManager.messages
when making these changes.

---

Outside diff comments:
In `@packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue`:
- Around line 30-43: The prop definition in MarkdownRenderer (defineProps) is
wrong: replace the current message prop (typed as Options with default {}) with
a content prop of type String that is either required or has a safe default
(e.g., empty string), keep theme and options as-is, and update all usages that
read props.message.content (e.g., where markdownIt.render is called) to use
props.content directly so markdownIt.render never receives undefined.

In `@packages/plugins/robot/src/composables/useChat.ts`:
- Around line 95-122: The code assumes lastMessage = messages.at(-1) is always
defined but it can be undefined; update the endRequest/finalization logic to
guard against that by early-returning when lastMessage is falsy or using
optional chaining when accessing lastMessage.content and lastMessage.tool_calls;
specifically adjust the usages in this block (references: lastMessage,
messages.at, onRequestEnd, handleToolCall, onMessageProcessed, messageState,
chatStatus) so you call onRequestEnd with a safe string (or return early), skip
tool handling and state transitions when lastMessage is undefined, and avoid
direct dereferences like lastMessage.content or lastMessage.tool_calls without
checks.
- Around line 124-129: In handleRequestError, avoid directly calling
messages.at(-1).content which can throw when messages is empty; mirror the fix
from handleFinishRequest by using optional chaining and a safe fallback (e.g.,
messages.at(-1)?.content ?? '') when calling onRequestEnd so the function won’t
throw if there’s no last message; update the call site in handleRequestError
accordingly.
- Around line 1-317: Run the repository-level build command `pnpm build:plugin`
to reproduce the compilation errors for the robot plugin and fix whatever
TypeScript/build problems surface in the package (packages/plugins/robot). Focus
on correcting imports/exports and types referenced in useChat.ts—check symbols
like initChatClient, beforeRequest, createStreamDataHandler,
createToolCallHandler, useConversationAdapter, createConversationWithMode and
sendUserMessage for missing/incorrect types or broken imports; iterate until
`pnpm build:plugin` completes successfully, then commit the fixes.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 714a8ea4-34b4-405d-8683-fbea959acc47

📥 Commits

Reviewing files that changed from the base of the PR and between 59e8548 and e3a7d22.

📒 Files selected for processing (14)
  • packages/plugins/robot/package.json
  • packages/plugins/robot/src/Main.vue
  • packages/plugins/robot/src/components/chat/RobotChat.vue
  • packages/plugins/robot/src/components/header-extension/History.vue
  • packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue
  • packages/plugins/robot/src/composables/core/useConversation.ts
  • packages/plugins/robot/src/composables/core/useMessageStream.ts
  • packages/plugins/robot/src/composables/features/useToolCalls.ts
  • packages/plugins/robot/src/composables/modes/useAgentMode.ts
  • packages/plugins/robot/src/composables/modes/useChatMode.ts
  • packages/plugins/robot/src/composables/useChat.ts
  • packages/plugins/robot/src/constants/index.ts
  • packages/plugins/robot/src/constants/status.ts
  • packages/plugins/robot/src/services/aiClient.ts

Comment thread packages/plugins/robot/src/components/chat/RobotChat.vue
Comment thread packages/plugins/robot/src/composables/modes/useChatMode.ts
Comment thread packages/plugins/robot/src/composables/useChat.ts
@lichunn lichunn changed the title Feat:Migration of tiny robot version to v0.4.0 Feat:Migration of tiny robot version to 0.4.0 May 22, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/plugins/robot/src/composables/core/useConversation.ts (2)

46-49: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stop draining buffered chunks after cancel.

handleAbort() only flips finished, but the loop on Lines 79-88 still yields until queue.length reaches 0. Any chunks buffered just before cancel will still be merged into the assistant message after the user aborted, which can leak extra output into a canceled or newly switched conversation.

Suggested fix
     const handleAbort = () => {
+      queue.length = 0
       finished = true
       notify()
     }
@@
-        yield queue.shift() as ChatCompletion
+        if (abortSignal.aborted) {
+          queue.length = 0
+          break
+        }
+        yield queue.shift() as ChatCompletion

Also applies to: 79-88

🤖 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 `@packages/plugins/robot/src/composables/core/useConversation.ts` around lines
46 - 49, The abort handler currently only sets finished and notifies, but
buffered chunks in queue continue to be drained by the consumer loop (the logic
that iterates over queue and merges chunks), so update handleAbort to both set
finished and immediately discard any buffered data to prevent leaking output:
inside handleAbort, clear or reset the shared queue (e.g., queue.length = 0 or
queue = []) and any partial buffer/state used to merge assistant chunks, then
call notify(); ensure the consumer/merging code (the loop that reads from queue)
treats finished and an empty queue as terminal so no further chunks are appended
after abort.

126-136: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset the processing gate when finish hooks fail.

statusManager.setProcessing() is sticky here. If onFinishRequest() rejects, isProcessing() stays true and later onAfterRequest() calls return early at Line 127, so subsequent responses never run their finish path.

Suggested fix
     async onAfterRequest({ messages, lastChoice }) {
       if (statusManager.isProcessing()) {
         return
       }
 
       statusManager.setProcessing()
       const finishReason = lastChoice?.finish_reason || 'stop'
       const contextMessages = toRaw(messages.slice(0, -1))
 
-      await onFinishRequest(finishReason, messages, contextMessages, messageState)
+      try {
+        await onFinishRequest(finishReason, messages, contextMessages, messageState)
+      } catch (error) {
+        statusManager.resetProcessing()
+        messageState.status = STATUS.ERROR
+        messageState.errorMsg = error
+        throw error
+      }
     },
🤖 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 `@packages/plugins/robot/src/composables/core/useConversation.ts` around lines
126 - 136, The processing gate set by statusManager.setProcessing() in
onAfterRequest can remain true if onFinishRequest(...) throws; wrap the await
onFinishRequest(...) call in a try/finally (or try/catch/finally) and ensure you
call the complementary statusManager.clearProcessing() (or reset method) in the
finally block so isProcessing() is always cleared; preserve rethrowing the error
or handle it after clearing so subsequent onAfterRequest calls are not
short-circuited.
packages/plugins/robot/src/components/chat/RobotChat.vue (1)

33-35: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

The aborted footer is wired to a property nothing sets.

The cancel flow in packages/plugins/robot/src/composables/useChat.ts updates messageState.status = STATUS.ABORTED, but none of the supplied code annotates a message with aborted: true. As written, this condition stays false and the "已中止" footer never renders.

🤖 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 `@packages/plugins/robot/src/components/chat/RobotChat.vue` around lines 33 -
35, The template checks messages[0]?.aborted but the cancel flow only sets
messageState.status = STATUS.ABORTED in useChat.ts, so either annotate the
message object when aborting or change the template to check status. Fix by
updating the cancel/abort logic in the composable (where messageState.status is
set to STATUS.ABORTED) to also set messageState.aborted = true (or
messages[index].aborted = true on the actual message object being rendered),
ensuring the UI condition in RobotChat.vue's template `#content-footer` sees
aborted:true; reference messageState and STATUS.ABORTED in useChat.ts and the
messages array used by RobotChat.vue.
♻️ Duplicate comments (2)
packages/plugins/robot/src/composables/useChat.ts (1)

201-217: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

abortRequest() still races conversation switches.

This is still fire-and-forget against live messageManager.messages.value. createConversation() and switchConversation() call it synchronously right before swapping conversations, so the async cleanup can append aborted state into the wrong conversation or throw when there is no last message.

packages/plugins/robot/src/components/chat/RobotChat.vue (1)

177-180: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Image bubbles still won't match renderContent items.

The matcher still reads message.content[0], but the upload path here creates image messages with content: '' and renderContent: [{ type: 'img', ... }]. Those messages never hit ImgRenderer, so uploaded images fall through to an empty bubble.

Suggested fix
   {
     priority: BubbleRendererMatchPriority.NORMAL,
-    find: (message: any) => message?.content?.[0]?.type === 'img' || message?.content?.[0]?.type === 'image',
+    find: (_message: any, content: any) => content?.type === 'img' || content?.type === 'image',
     renderer: ImgRenderer
   }

Also applies to: 280-289

🤖 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 `@packages/plugins/robot/src/components/chat/RobotChat.vue` around lines 177 -
180, The matcher for image bubbles (the object with priority:
BubbleRendererMatchPriority.NORMAL and renderer: ImgRenderer) only checks
message.content[0] so messages that carry images in renderContent (e.g.
renderContent: [{ type: 'img' | 'image', ... }]) never match; update the find
function used for ImgRenderer (and the similar matcher at lines ~280-289) to
also inspect message.renderContent[0] (or prefer renderContent if present) and
return true when either content[0] or renderContent[0] has type 'img' or
'image', ensuring uploaded images route to ImgRenderer.
🧹 Nitpick comments (1)
packages/plugins/robot/src/composables/useChat.ts (1)

142-165: Please run the robot package tests and plugin build before merge.

This refactor changes published streaming, tool-call, and renderer-facing behavior in packages/plugins/robot. Please run the package-local test script if it exists and pnpm build:plugin so the built package still matches the runtime contracts these files now depend on. As per coding guidelines, packages/**/!(test|expected|output|fixtures)/**/*.{js,ts,jsx,tsx}: For published library packages under packages/**, run the package-local test script if one exists and run pnpm build:plugin when build output or published package behavior may be affected.

Also applies to: 230-318

🤖 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 `@packages/plugins/robot/src/composables/useChat.ts` around lines 142 - 165,
This change affects runtime behavior in useConversationAdapter and its callbacks
(notably onMessageProcessed, handleStreamData, handleFinishRequest) and state
variables messageManager.messageState, chatStatus, CHAT_STATUS, GeneratingStatus
and STATUS; before merging, run the package-local tests for
packages/plugins/robot (pnpm --filter ./packages/plugins/robot test or the
package's test script) and then build the plugin output (pnpm --filter
./packages/plugins/robot build:plugin or pnpm build:plugin in that package) to
ensure streaming, tool-call, and renderer contracts still match the built
artifact and update any failing tests or build issues found.
🤖 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 `@packages/plugins/robot/src/components/renderers/AgentRenderer.vue`:
- Around line 50-53: The computed resolvedContentType currently reads
contentItem.value.contentType or props.contentType but should use the render
item's type field; update the computed named resolvedContentType to use
contentItem.value.type || props.contentType (and similarly replace any other
computed/readers that reference contentItem.value.contentType in this file,
e.g., the second occurrence around the later computed block) so message-based
render items that provide type are handled correctly and the reasoning/terminal
state is preserved.

In `@packages/plugins/robot/src/composables/core/pageUpdater.ts`:
- Around line 125-127: The code currently calls resetPageSchemaUpdateState()
before applying the final chunk which clears lastSuccessfulSchema too early;
change the logic so that when isFinal is true you call
_updatePageSchema(streamContent, currentPageSchema, isFinal,
schemaUpdateVersion) first and only call resetPageSchemaUpdateState() after that
call succeeds (or in a successful completion path), and ensure any errors from
_updatePageSchema are allowed to propagate without clearing lastSuccessfulSchema
so getLastSuccessfulPageSchema() can still return the previous good schema;
reference functions: resetPageSchemaUpdateState(), _updatePageSchema(),
getLastSuccessfulPageSchema(), and setSchema() when implementing the
try/success/cleanup ordering.
- Around line 29-31: The current non-final update uses
Object.assign(pageState.pageSchema, schema) which never removes top-level keys
that were deleted from incoming schema; modify the update logic in
pageUpdater.ts so it first removes any keys present on pageState.pageSchema that
are not present on the incoming schema (e.g., iterate
Object.keys(pageState.pageSchema) and delete keys absent from schema), then copy
new/updated keys from schema into pageState.pageSchema (or replace the object
safely if references allow), and then call useMessage().publish({ topic:
'schemaChange', data: {} }) as before to notify listeners.

In `@packages/plugins/robot/src/utils/schema.utils.ts`:
- Around line 140-141: The current isValidSchemaChildren function incorrectly
accepts any non-array value; update it so it only returns true for actual arrays
whose elements are valid nodes by changing its logic to require
Array.isArray(children) && children.every(isValidSchemaNode). Locate the
isValidSchemaChildren export in schema.utils.ts and replace the negated
Array.isArray check with a direct Array.isArray(...) conjunction so objects,
strings, and other non-array payloads are rejected.

---

Outside diff comments:
In `@packages/plugins/robot/src/components/chat/RobotChat.vue`:
- Around line 33-35: The template checks messages[0]?.aborted but the cancel
flow only sets messageState.status = STATUS.ABORTED in useChat.ts, so either
annotate the message object when aborting or change the template to check
status. Fix by updating the cancel/abort logic in the composable (where
messageState.status is set to STATUS.ABORTED) to also set messageState.aborted =
true (or messages[index].aborted = true on the actual message object being
rendered), ensuring the UI condition in RobotChat.vue's template `#content-footer`
sees aborted:true; reference messageState and STATUS.ABORTED in useChat.ts and
the messages array used by RobotChat.vue.

In `@packages/plugins/robot/src/composables/core/useConversation.ts`:
- Around line 46-49: The abort handler currently only sets finished and
notifies, but buffered chunks in queue continue to be drained by the consumer
loop (the logic that iterates over queue and merges chunks), so update
handleAbort to both set finished and immediately discard any buffered data to
prevent leaking output: inside handleAbort, clear or reset the shared queue
(e.g., queue.length = 0 or queue = []) and any partial buffer/state used to
merge assistant chunks, then call notify(); ensure the consumer/merging code
(the loop that reads from queue) treats finished and an empty queue as terminal
so no further chunks are appended after abort.
- Around line 126-136: The processing gate set by statusManager.setProcessing()
in onAfterRequest can remain true if onFinishRequest(...) throws; wrap the await
onFinishRequest(...) call in a try/finally (or try/catch/finally) and ensure you
call the complementary statusManager.clearProcessing() (or reset method) in the
finally block so isProcessing() is always cleared; preserve rethrowing the error
or handle it after clearing so subsequent onAfterRequest calls are not
short-circuited.

---

Duplicate comments:
In `@packages/plugins/robot/src/components/chat/RobotChat.vue`:
- Around line 177-180: The matcher for image bubbles (the object with priority:
BubbleRendererMatchPriority.NORMAL and renderer: ImgRenderer) only checks
message.content[0] so messages that carry images in renderContent (e.g.
renderContent: [{ type: 'img' | 'image', ... }]) never match; update the find
function used for ImgRenderer (and the similar matcher at lines ~280-289) to
also inspect message.renderContent[0] (or prefer renderContent if present) and
return true when either content[0] or renderContent[0] has type 'img' or
'image', ensuring uploaded images route to ImgRenderer.

---

Nitpick comments:
In `@packages/plugins/robot/src/composables/useChat.ts`:
- Around line 142-165: This change affects runtime behavior in
useConversationAdapter and its callbacks (notably onMessageProcessed,
handleStreamData, handleFinishRequest) and state variables
messageManager.messageState, chatStatus, CHAT_STATUS, GeneratingStatus and
STATUS; before merging, run the package-local tests for packages/plugins/robot
(pnpm --filter ./packages/plugins/robot test or the package's test script) and
then build the plugin output (pnpm --filter ./packages/plugins/robot
build:plugin or pnpm build:plugin in that package) to ensure streaming,
tool-call, and renderer contracts still match the built artifact and update any
failing tests or build issues found.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 849c87ec-885c-43dd-9361-596685821b0a

📥 Commits

Reviewing files that changed from the base of the PR and between e3a7d22 and 02cfdb1.

📒 Files selected for processing (7)
  • packages/plugins/robot/src/components/chat/RobotChat.vue
  • packages/plugins/robot/src/components/renderers/AgentRenderer.vue
  • packages/plugins/robot/src/composables/core/pageUpdater.ts
  • packages/plugins/robot/src/composables/core/useConversation.ts
  • packages/plugins/robot/src/composables/modes/useAgentMode.ts
  • packages/plugins/robot/src/composables/useChat.ts
  • packages/plugins/robot/src/utils/schema.utils.ts

Comment on lines +50 to +53
const resolvedStatus = computed(() => contentItem.value.status || props.status || 'loading')
const resolvedContent = computed(() => contentItem.value.content ?? props.content)
const resolvedContentType = computed(() => contentItem.value.contentType || props.contentType)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the render item's type field here, not contentType.

contentItem comes from message.renderContent, and the render items built in this PR use type (packages/plugins/robot/src/components/chat/RobotChat.vue, packages/plugins/robot/src/composables/modes/useAgentMode.ts). Reading contentItem.value.contentType leaves resolvedContentType empty for message-based rendering, so reasoning entries miss the reasoning branch and can show the wrong terminal state.

Suggested fix
-    const resolvedContentType = computed(() => contentItem.value.contentType || props.contentType)
+    const resolvedContentType = computed(() => contentItem.value.type || props.contentType)

Also applies to: 82-89

🤖 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 `@packages/plugins/robot/src/components/renderers/AgentRenderer.vue` around
lines 50 - 53, The computed resolvedContentType currently reads
contentItem.value.contentType or props.contentType but should use the render
item's type field; update the computed named resolvedContentType to use
contentItem.value.type || props.contentType (and similarly replace any other
computed/readers that reference contentItem.value.contentType in this file,
e.g., the second occurrence around the later computed block) so message-based
render items that provide type are handled correctly and the reasoning/terminal
state is preserved.

Comment on lines +29 to +31
} else {
Object.assign(pageState.pageSchema, schema)
useMessage().publish({ topic: 'schemaChange', data: {} })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove dropped top-level keys during non-final schema updates.

Object.assign(pageState.pageSchema, schema) never deletes keys that disappeared from schema, so streamed remove patches on root fields keep stale data alive until a final import happens. If the stream is interrupted, the canvas can stay in the wrong state.

Proposed fix
   } else {
+    Object.keys(pageState.pageSchema).forEach((key) => {
+      if (!(key in (schema as Record<string, unknown>))) {
+        delete (pageState.pageSchema as Record<string, unknown>)[key]
+      }
+    })
     Object.assign(pageState.pageSchema, schema)
     useMessage().publish({ topic: 'schemaChange', data: {} })
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else {
Object.assign(pageState.pageSchema, schema)
useMessage().publish({ topic: 'schemaChange', data: {} })
} else {
Object.keys(pageState.pageSchema).forEach((key) => {
if (!(key in (schema as Record<string, unknown>))) {
delete (pageState.pageSchema as Record<string, unknown>)[key]
}
})
Object.assign(pageState.pageSchema, schema)
useMessage().publish({ topic: 'schemaChange', data: {} })
🤖 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 `@packages/plugins/robot/src/composables/core/pageUpdater.ts` around lines 29 -
31, The current non-final update uses Object.assign(pageState.pageSchema,
schema) which never removes top-level keys that were deleted from incoming
schema; modify the update logic in pageUpdater.ts so it first removes any keys
present on pageState.pageSchema that are not present on the incoming schema
(e.g., iterate Object.keys(pageState.pageSchema) and delete keys absent from
schema), then copy new/updated keys from schema into pageState.pageSchema (or
replace the object safely if references allow), and then call
useMessage().publish({ topic: 'schemaChange', data: {} }) as before to notify
listeners.

Comment on lines +125 to +127
if (isFinal) {
resetPageSchemaUpdateState()
return _updatePageSchema(streamContent, currentPageSchema, isFinal, schemaUpdateVersion)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't clear the last good schema before the final apply succeeds.

When isFinal is true, this resets lastSuccessfulSchema before parsing and applying the final chunk. If the final chunk is malformed or setSchema() throws, getLastSuccessfulPageSchema() has already lost the last valid streamed schema, so callers cannot fall back to it.

🤖 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 `@packages/plugins/robot/src/composables/core/pageUpdater.ts` around lines 125
- 127, The code currently calls resetPageSchemaUpdateState() before applying the
final chunk which clears lastSuccessfulSchema too early; change the logic so
that when isFinal is true you call _updatePageSchema(streamContent,
currentPageSchema, isFinal, schemaUpdateVersion) first and only call
resetPageSchemaUpdateState() after that call succeeds (or in a successful
completion path), and ensure any errors from _updatePageSchema are allowed to
propagate without clearing lastSuccessfulSchema so getLastSuccessfulPageSchema()
can still return the previous good schema; reference functions:
resetPageSchemaUpdateState(), _updatePageSchema(),
getLastSuccessfulPageSchema(), and setSchema() when implementing the
try/success/cleanup ordering.

Comment on lines +140 to +141
export const isValidSchemaChildren = (children: unknown): boolean =>
!Array.isArray(children) || children.every(isValidSchemaNode)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject non-array children payloads here.

isValidSchemaChildren() currently returns true for {}, strings, and any other non-array value. That means the new guard in pageUpdater.ts still accepts malformed children shapes even though the rest of the schema pipeline only handles arrays.

Proposed fix
 export const isValidSchemaChildren = (children: unknown): boolean =>
-  !Array.isArray(children) || children.every(isValidSchemaNode)
+  children == null || (Array.isArray(children) && children.every(isValidSchemaNode))
🤖 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 `@packages/plugins/robot/src/utils/schema.utils.ts` around lines 140 - 141, The
current isValidSchemaChildren function incorrectly accepts any non-array value;
update it so it only returns true for actual arrays whose elements are valid
nodes by changing its logic to require Array.isArray(children) &&
children.every(isValidSchemaNode). Locate the isValidSchemaChildren export in
schema.utils.ts and replace the negated Array.isArray check with a direct
Array.isArray(...) conjunction so objects, strings, and other non-array payloads
are rejected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant