Skip to content

Conversation

@xinquiry
Copy link
Collaborator

@xinquiry xinquiry commented Jan 31, 2026

Summary

  • Use method="function_calling" for structured output in deep research clarify node
  • Increase agent recursion limit from 25 to 50

Problem

  • The default json_mode for with_structured_output() doesn't work with Claude models via GPUGEEK provider
  • Claude supports tool/function calling natively but not OpenAI's response_format JSON mode
  • Deep research agent was hitting recursion limit (25) on complex tasks

Solution

  • Use method="function_calling" which works across all providers (OpenAI, Gemini, Claude)
  • Increase recursion_limit to 50 in agent.astream() config

Test plan

  • Tested with ultra tier (Claude via GPUGEEK) - structured output works
  • Test with other tiers to verify no regression

🤖 Generated with Claude Code

Summary by Sourcery

调整深度研究澄清流程,以使用与提供商无关的结构化输出,并提升智能体在复杂任务中的递归能力。

Bug 修复:

  • 将深度研究澄清节点的结构化输出切换为使用函数调用,以便在不同的 LLM 提供商之间保持兼容性。

增强功能:

  • 将流式配置中的智能体递归限制从 25 提升到 50,以更好地处理复杂的深度研究工作流。
Original summary in English

Summary by Sourcery

Adjust deep research clarification to use provider-agnostic structured output and increase agent recursion capacity for complex tasks.

Bug Fixes:

  • Switch deep research clarify node structured output to use function calling for compatibility across LLM providers.

Enhancements:

  • Raise the agent recursion limit from 25 to 50 in the streaming config to better handle complex deep research workflows.

- Use function_calling method for structured output in clarify node.
  The default json_mode doesn't work with Claude models via GPUGEEK
  provider. Claude supports tool/function calling natively but not
  OpenAI's response_format JSON mode.

- Increase recursion_limit from 25 to 50 in agent.astream() to handle
  complex research tasks with more iterations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 31, 2026

审阅者指南(在小型 PR 上折叠)

审阅者指南

调整 deep research clarify 节点以使用基于 function calling 的结构化输出,从而获得更好的多模型兼容性,并提升 agent 的递归上限,使其在终止前可以处理更复杂的任务。

使用 function_calling 的 clarify_node 结构化输出时序图

sequenceDiagram
    participant ClarifyNode
    participant LLMFactory
    participant LLM
    participant StructuredLLM

    ClarifyNode->>LLMFactory: llm_factory(temperature=0.3)
    LLMFactory-->>ClarifyNode: llm

    ClarifyNode->>ClarifyNode: messages_str = get_buffer_string(state.messages)
    ClarifyNode->>ClarifyNode: date_str = get_today_str()
    ClarifyNode->>ClarifyNode: prompt = CLARIFY_WITH_USER_PROMPT.format(messages, date)

    ClarifyNode->>LLM: with_structured_output(ClarifyWithUser, method=function_calling)
    LLM-->>ClarifyNode: llm_with_struct

    ClarifyNode->>StructuredLLM: ainvoke([HumanMessage(content=prompt)])
    StructuredLLM-->>ClarifyNode: response

    ClarifyNode->>ClarifyNode: result = ClarifyWithUser.model_validate(response)
    ClarifyNode-->>ClarifyNode: use result for clarification state
Loading

提升 recursion_limit 后的 agent.astream 时序图

sequenceDiagram
    participant ChatProcessor
    participant Agent

    ChatProcessor->>ChatProcessor: prepare history_messages

    ChatProcessor->>Agent: astream({messages: history_messages}, stream_mode=[updates, messages], config={recursion_limit: 50})
    loop for each chunk
        Agent-->>ChatProcessor: (mode, data)
        ChatProcessor->>ChatProcessor: chunk_count += 1
        ChatProcessor->>ChatProcessor: handle mode and data
    end

    Agent-->>ChatProcessor: stream completed
Loading

deep research clarify 与 agent 流式变更的类图

classDiagram
    class ClarifyState {
    }

    class ClarifyWithUser {
    }

    class LLMFactory {
        +llm_factory(temperature)
    }

    class LLM {
        +with_structured_output(output_type, method)
    }

    class StructuredLLM {
        +ainvoke(messages)
    }

    class ClarifyNode {
        +clarify_node(state)
    }

    class Agent {
        +astream(input, stream_mode, config)
    }

    class ChatProcessor {
        +_process_agent_stream(agent, history_messages)
    }

    ClarifyNode --> ClarifyState
    ClarifyNode --> ClarifyWithUser
    ClarifyNode --> LLMFactory
    LLMFactory --> LLM
    LLM --> StructuredLLM
    ClarifyNode --> StructuredLLM

    ChatProcessor --> Agent
    ChatProcessor --> ClarifyNode
    Agent ..> ClarifyWithUser
    Agent ..> ClarifyState
    StructuredLLM ..> ClarifyWithUser
    ClarifyWithUser <.. ChatProcessor
Loading

文件级变更

变更 详情 文件
将 clarify 节点的结构化输出切换为使用 function-calling,以实现跨模型提供商的兼容性。
  • 与之前一样,通过现有的 llm_factory 并使用 temperature 0.3 获取基础 LLM
  • 从缓冲消息和当前日期构建 clarify prompt,无需在单独步骤中将 messages 转换为列表
  • 使用 ClarifyWithUser 和 method="function_calling" 调用 with_structured_output 包装 LLM
  • 使用人类消息 prompt 异步调用结构化 LLM,并在需要时通过 model_validate 将响应规范化为 ClarifyWithUser
service/app/agents/components/deep_research/components.py
提升 agent 的递归上限,以便在流式运行中支持更深层次推理。
  • 更新 _process_agent_stream,在调用 agent.astream 时传入包含 recursion_limit=50 的 config 字典
  • 在新增递归上限的同时,保留现有的 stream_mode 选项 ["updates", "messages"] 及其它行为
service/app/core/chat/langchain.py

提示与命令

与 Sourcery 交互

  • 触发一次新的审阅: 在 pull request 上评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审阅评论。
  • 从审阅评论生成 GitHub issue: 通过回复某条审阅评论,请求 Sourcery 根据该评论创建 issue。你也可以在审阅评论下回复 @sourcery-ai issue,从该评论创建一个 issue。
  • 生成 pull request 标题: 在 pull request 标题的任意位置写上 @sourcery-ai,即可在任意时间生成标题。你也可以在 pull request 上评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文任意位置写上 @sourcery-ai summary,即可在你想要的位置随时生成 PR 摘要。你也可以在 pull request 上评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成审阅者指南: 在 pull request 上评论 @sourcery-ai guide,即可在任意时间(重新)生成审阅者指南。
  • 解决所有 Sourcery 评论: 在 pull request 上评论 @sourcery-ai resolve,即可标记解决所有 Sourcery 评论。如果你已经处理完所有评论且不想再看到它们,这会很有用。
  • 忽略所有 Sourcery 审阅: 在 pull request 上评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 审阅。特别适用于你想从头开始新的审阅时——别忘了再评论 @sourcery-ai review 来触发新的审阅!

自定义你的体验

访问你的 控制面板 以:

  • 启用或禁用审阅特性,如 Sourcery 生成的 pull request 摘要、审阅者指南等。
  • 更改审阅语言。
  • 添加、移除或编辑自定义审阅指令。
  • 调整其它审阅设置。

获取帮助

Original review guide in English
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adjusts the deep research clarify node to use function-calling-based structured output for better provider compatibility and raises the agent recursion limit to handle more complex tasks before terminating.

Sequence diagram for clarify_node structured output using function_calling

sequenceDiagram
    participant ClarifyNode
    participant LLMFactory
    participant LLM
    participant StructuredLLM

    ClarifyNode->>LLMFactory: llm_factory(temperature=0.3)
    LLMFactory-->>ClarifyNode: llm

    ClarifyNode->>ClarifyNode: messages_str = get_buffer_string(state.messages)
    ClarifyNode->>ClarifyNode: date_str = get_today_str()
    ClarifyNode->>ClarifyNode: prompt = CLARIFY_WITH_USER_PROMPT.format(messages, date)

    ClarifyNode->>LLM: with_structured_output(ClarifyWithUser, method=function_calling)
    LLM-->>ClarifyNode: llm_with_struct

    ClarifyNode->>StructuredLLM: ainvoke([HumanMessage(content=prompt)])
    StructuredLLM-->>ClarifyNode: response

    ClarifyNode->>ClarifyNode: result = ClarifyWithUser.model_validate(response)
    ClarifyNode-->>ClarifyNode: use result for clarification state
Loading

Sequence diagram for agent.astream with increased recursion_limit

sequenceDiagram
    participant ChatProcessor
    participant Agent

    ChatProcessor->>ChatProcessor: prepare history_messages

    ChatProcessor->>Agent: astream({messages: history_messages}, stream_mode=[updates, messages], config={recursion_limit: 50})
    loop for each chunk
        Agent-->>ChatProcessor: (mode, data)
        ChatProcessor->>ChatProcessor: chunk_count += 1
        ChatProcessor->>ChatProcessor: handle mode and data
    end

    Agent-->>ChatProcessor: stream completed
Loading

Class diagram for deep research clarify and agent streaming changes

classDiagram
    class ClarifyState {
    }

    class ClarifyWithUser {
    }

    class LLMFactory {
        +llm_factory(temperature)
    }

    class LLM {
        +with_structured_output(output_type, method)
    }

    class StructuredLLM {
        +ainvoke(messages)
    }

    class ClarifyNode {
        +clarify_node(state)
    }

    class Agent {
        +astream(input, stream_mode, config)
    }

    class ChatProcessor {
        +_process_agent_stream(agent, history_messages)
    }

    ClarifyNode --> ClarifyState
    ClarifyNode --> ClarifyWithUser
    ClarifyNode --> LLMFactory
    LLMFactory --> LLM
    LLM --> StructuredLLM
    ClarifyNode --> StructuredLLM

    ChatProcessor --> Agent
    ChatProcessor --> ClarifyNode
    Agent ..> ClarifyWithUser
    Agent ..> ClarifyState
    StructuredLLM ..> ClarifyWithUser
    ClarifyWithUser <.. ChatProcessor
Loading

File-Level Changes

Change Details Files
Switch clarify node structured output to use function-calling for cross-provider compatibility.
  • Obtain base LLM via existing llm_factory with temperature 0.3 as before
  • Build the clarify prompt from buffered messages and current date without casting messages to list in a separate step
  • Wrap the LLM with with_structured_output using ClarifyWithUser and method="function_calling"
  • Invoke the structured LLM asynchronously with the human message prompt and normalize the response to ClarifyWithUser via model_validate when needed
service/app/agents/components/deep_research/components.py
Increase agent recursion limit to allow deeper reasoning in streamed agent runs.
  • Update _process_agent_stream to pass a config dict with recursion_limit set to 50 into agent.astream
  • Preserve existing stream_mode options ["updates", "messages"] and other behavior while adding the new limit
service/app/core/chat/langchain.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - 我在这里给出了一些整体性的反馈:

  • _process_agent_stream 中硬编码的 recursion_limit=50 也许更适合作为可配置的设置或共享常量,这样就可以在不同环境中进行调优,而不需要修改代码。
  • clarify_node 中,如果这个模式在其他地方也会复用,建议对每个 LLM 实例(或在更高层级)只实例化一次 llm.with_structured_output(ClarifyWithUser, method="function_calling"),以避免分散的配置,并将与提供商相关的行为集中管理。
AI Agents 的提示词
Please address the comments from this code review:

## Overall Comments
- The hard-coded `recursion_limit=50` in `_process_agent_stream` may be better expressed as a configurable setting or shared constant so it can be tuned per environment without code changes.
- In `clarify_node`, consider instantiating `llm.with_structured_output(ClarifyWithUser, method="function_calling")` once per LLM instance (or at a higher level) if this pattern is reused elsewhere, to avoid scattered configuration and keep provider-specific behavior centralized.

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English

Hey - I've left some high level feedback:

  • The hard-coded recursion_limit=50 in _process_agent_stream may be better expressed as a configurable setting or shared constant so it can be tuned per environment without code changes.
  • In clarify_node, consider instantiating llm.with_structured_output(ClarifyWithUser, method="function_calling") once per LLM instance (or at a higher level) if this pattern is reused elsewhere, to avoid scattered configuration and keep provider-specific behavior centralized.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The hard-coded `recursion_limit=50` in `_process_agent_stream` may be better expressed as a configurable setting or shared constant so it can be tuned per environment without code changes.
- In `clarify_node`, consider instantiating `llm.with_structured_output(ClarifyWithUser, method="function_calling")` once per LLM instance (or at a higher level) if this pattern is reused elsewhere, to avoid scattered configuration and keep provider-specific behavior centralized.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@xinquiry xinquiry merged commit 4fb138f into test Jan 31, 2026
2 checks passed
@xinquiry xinquiry deleted the fix/deep-research-from-json branch January 31, 2026 11:02
Mile-Away added a commit that referenced this pull request Feb 1, 2026
* feat: add drag-and-drop agent reordering and auto-update on version mismatch (#206)

* fix: resolve i18next missing key warnings in TierInfoModal

Replace hardcoded Chinese strings with proper i18n translation keys
in the tier selector component. This fixes console warnings about
missing translation keys when using the zh locale.

- Add speedLabels, reasoningLabels, and features keys to app.json
- Add multiplier key to tierSelector in all locales
- Add recommended key to common.json in all locales
- Refactor TierInfoModal.tsx to use translation key references

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add drag-and-drop agent reordering

   Integrate dnd-kit into existing AgentList and AgentListItem components
   to support drag-and-drop reordering in both the sidebar and spatial
   focused view.

   Backend:
   - Add sort_order field to Agent model
   - Add PATCH endpoint for bulk reordering agents
   - Add migration for sort_order column

   Frontend:
   - Add sortable prop to AgentList with DndContext/SortableContext
   - Add dragHandleProps to AgentListItem for drag behavior
   - Use plain div (not motion.div) when sortable to avoid animation conflicts
   - Use set-based comparison for state sync (only reset on add/remove)
   - Add reorderAgents action to agentSlice

* feat: auto-refresh frontend on version mismatch with backend

  Add automatic update mechanism that detects when the frontend version
  doesn't match the backend version and refreshes the page to fetch the
  latest assets. This ensures users with cached frontends always get
  updated code without manually clearing their browser cache.

  - Add useAutoUpdate hook with retry logic (max 3 attempts)
  - Add UpdateOverlay component for update feedback
  - Clear service workers and caches before reload
  - Store retry state in localStorage to prevent infinite loops

* fix: address PR review feedback for drag-and-drop agent reordering

  - Move AgentList state sync from render to useEffect to prevent render loops
  - Add isDraggingRef to prevent backend sync during active drag operations
  - Restore keyboard accessibility to CompactAgentListItem (role, tabIndex,
    aria-pressed, onKeyDown handler)
  - Guard localStorage writes with try/catch in useAutoUpdate to handle
    restricted environments

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix: resolve version mismatch causing refresh loops in test environment (#207)

* fix: move the comment to correct position

* fix: fix the test environment version

* fix: remove auto-update feature to prevent refresh loops

  The auto-update mechanism causes infinite refresh loops when frontend
  and backend versions mismatch in test environment. Remove the feature
  entirely until a more robust solution is implemented.

  - Delete useAutoUpdate hook and UpdateOverlay component
  - Remove AutoUpdateWrapper from App.tsx

* Fix: Unify tool call UI (pills in agent timeline) for streaming + refresh (#208)

* feat: add simple landing page and logout button

* fix(web): unify tool call rendering in agent timeline after refresh; add landing page

  - Render tool calls as pills with a shared details modal (args/results/errors)
  - Attach historical tool_call tool messages into agentExecution phases instead of standalone messages
  - Remove legacy ToolCallCard-based rendering path

* fix(web): address review feedback for tool call modal + typewriter

  - Move tool-call UI strings into i18n (app.chat.toolCall.*) for en/zh/ja
  - Memoize tool result parsing and image URL derivation in ToolCallDetails
  - Avoid duplicate argument headings in ToolCallDetailsModal for waiting_confirmation
  - Remove redundant typewriter cursor className conditional and fix unused state var

* feat: message editing and deletion (#209)

* feat: add message editing and deletion with truncate-regenerate flow

  - Add PATCH /messages/{id} endpoint for editing user messages
  - Add DELETE /messages/{id} endpoint for deleting any message
  - Add regenerate WebSocket handler for re-running agent after edit
  - Add edit/delete UI to ChatBubble with hover actions
  - Add i18n translations for en/zh/ja

  Includes fixes from code review:
  - Fix pre-deduction error handling to skip dispatch on any failure
  - Reset responding state before regeneration to prevent stuck UI
  - Add message ownership verification before edit/delete operations

* fix: improve the code according to sourcery review

* fix: use version+SHA for beta image tags to ensure unique deployments (#211)

The previous approach used only the pyproject.toml version (e.g., 1.0.16)
which caused Kubernetes to not pull new images when multiple commits
used the same version tag.

Now uses format: {version}-{short-sha} (e.g., 1.0.16-f13e3c0)

Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com>

* feat: conversation interrupt/abort functionality (#212)

* feat: add conversation interrupt/abort functionality

- Add stop button and Escape key shortcut to abort streaming generation
- Implement Redis-based signaling between API server and Celery worker
- Worker checks abort signal every 0.5s and gracefully stops streaming
- Save partial content and perform partial billing on abort
- Add visual indicator for cancelled/aborted messages
- Add timeout fallback (10s) to reset UI if backend doesn't respond
- Add i18n strings for stop/stopping/escToStop in en/zh/ja

* fix: address abort feature edge cases from code review

- Clear stale abort signals at task start to prevent race condition when
  user reconnects quickly after disconnect
- Finalize AgentRun with 'failed' status on unhandled exceptions to ensure
  consistent DB state across all exit paths
- Move time import to module level (was inline import)

* fix: address Sourcery review feedback for abort feature

- Reuse existing Redis client for abort checks instead of creating new
  connections on each tick (performance improvement)
- Fix potential Redis connection leaks in ABORT and disconnect handlers
  by using try/finally pattern
- Track and cleanup abort timeout in frontend to prevent stale timers
  from racing with subsequent abort requests

* Preserve phase text when copying streamed output (#210)

* Preserve phase text when copying streamed output

* Extract agent phase content helper

* fix: use existing PhaseExecution type and correct useMemo dependencies

- Replace inline PhaseWithStreamedContent type with Pick<PhaseExecution, 'streamedContent'>
  for type consistency across the codebase
- Fix useMemo dependency array to use agentExecution?.phases instead of agentExecution
  to ensure proper recalculation when phases array changes

* Fix streaming agent messages and prevent deleting non-persisted messages (#213)

* Fix message deletion for agent executions

* Address Sourcery review: stricter UUID validation and safer id assignment

- Add isValidUuid utility with canonical UUID pattern (8-4-4-4-12 format)
  to replace loose regex that accepted invalid strings like all-hyphens
- Fix streaming_start handler to only set id when eventData.id is truthy,
  preventing accidental overwrites with undefined/null
- Improve delete guard with contextual messages ("still streaming" vs
  "not saved yet") and change notification type to warning
- Add comprehensive tests for isValidUuid covering valid UUIDs, client IDs,
  invalid formats, and edge cases

Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com>

---------

Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com>

* fix: emit message_saved event after stream abort (#215)

* fix: emit message_saved event after stream abort

When a user interrupts a streaming response, the message is saved to the
database but the frontend never receives the message_saved event. This
leaves the message with a temporary stream_ prefix ID, preventing deletion
until page refresh.

Now the MESSAGE_SAVED event is emitted after db.commit() in the abort
handler, before STREAM_ABORTED, so the frontend updates the message ID
to the real UUID and deletion works immediately.

Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com>

* fix: always show latest topic when clicking an agent

Unify sidebar and spatial workspace to use the same logic for selecting
topics. Both now fetch from backend and always show the most recently
updated topic (by updated_at) instead of remembering previously active topics.

Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com>

* feat: improve message editing UX with edit-only option and assistant editing

- Add "Edit" and "Edit & Regenerate" dropdown options for user messages
- Allow editing assistant messages (content-only, no regeneration)
- Add copy button to user messages
- Move assistant message actions to top-right for better UX
- Add auto-resizing textarea for editing long messages
- Update backend to support truncate_and_regenerate flag

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: extract message content resolution into dedicated module

Extract scattered content resolution logic into core/chat/messageContent.ts
with two main utilities:
- resolveMessageContent(): Single source of truth for content priority
- getMessageDisplayMode(): Explicit rendering mode determination

This refactoring:
- Reduces ChatBubble.tsx complexity (60+ line IIFE → 30 line switch)
- Fixes inconsistency between copy/edit and display logic
- Makes content source priority explicit and documented
- Adds guard for empty content to avoid rendering empty divs
- Improves maintainability with testable pure functions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com>

* fix: deep research structured output and recursion limit (#216)

- Use function_calling method for structured output in clarify node.
  The default json_mode doesn't work with Claude models via GPUGEEK
  provider. Claude supports tool/function calling natively but not
  OpenAI's response_format JSON mode.

- Increase recursion_limit from 25 to 50 in agent.astream() to handle
  complex research tasks with more iterations.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: xinquiry(SII) <100398322+xinquiry@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
xinquiry added a commit that referenced this pull request Feb 1, 2026
* feat: add drag-and-drop agent reordering and auto-update on version mismatch (#206)

* fix: resolve i18next missing key warnings in TierInfoModal

Replace hardcoded Chinese strings with proper i18n translation keys
in the tier selector component. This fixes console warnings about
missing translation keys when using the zh locale.

- Add speedLabels, reasoningLabels, and features keys to app.json
- Add multiplier key to tierSelector in all locales
- Add recommended key to common.json in all locales
- Refactor TierInfoModal.tsx to use translation key references

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add drag-and-drop agent reordering

   Integrate dnd-kit into existing AgentList and AgentListItem components
   to support drag-and-drop reordering in both the sidebar and spatial
   focused view.

   Backend:
   - Add sort_order field to Agent model
   - Add PATCH endpoint for bulk reordering agents
   - Add migration for sort_order column

   Frontend:
   - Add sortable prop to AgentList with DndContext/SortableContext
   - Add dragHandleProps to AgentListItem for drag behavior
   - Use plain div (not motion.div) when sortable to avoid animation conflicts
   - Use set-based comparison for state sync (only reset on add/remove)
   - Add reorderAgents action to agentSlice

* feat: auto-refresh frontend on version mismatch with backend

  Add automatic update mechanism that detects when the frontend version
  doesn't match the backend version and refreshes the page to fetch the
  latest assets. This ensures users with cached frontends always get
  updated code without manually clearing their browser cache.

  - Add useAutoUpdate hook with retry logic (max 3 attempts)
  - Add UpdateOverlay component for update feedback
  - Clear service workers and caches before reload
  - Store retry state in localStorage to prevent infinite loops

* fix: address PR review feedback for drag-and-drop agent reordering

  - Move AgentList state sync from render to useEffect to prevent render loops
  - Add isDraggingRef to prevent backend sync during active drag operations
  - Restore keyboard accessibility to CompactAgentListItem (role, tabIndex,
    aria-pressed, onKeyDown handler)
  - Guard localStorage writes with try/catch in useAutoUpdate to handle
    restricted environments

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix: resolve version mismatch causing refresh loops in test environment (#207)

* fix: move the comment to correct position

* fix: fix the test environment version

* fix: remove auto-update feature to prevent refresh loops

  The auto-update mechanism causes infinite refresh loops when frontend
  and backend versions mismatch in test environment. Remove the feature
  entirely until a more robust solution is implemented.

  - Delete useAutoUpdate hook and UpdateOverlay component
  - Remove AutoUpdateWrapper from App.tsx

* Fix: Unify tool call UI (pills in agent timeline) for streaming + refresh (#208)

* feat: add simple landing page and logout button

* fix(web): unify tool call rendering in agent timeline after refresh; add landing page

  - Render tool calls as pills with a shared details modal (args/results/errors)
  - Attach historical tool_call tool messages into agentExecution phases instead of standalone messages
  - Remove legacy ToolCallCard-based rendering path

* fix(web): address review feedback for tool call modal + typewriter

  - Move tool-call UI strings into i18n (app.chat.toolCall.*) for en/zh/ja
  - Memoize tool result parsing and image URL derivation in ToolCallDetails
  - Avoid duplicate argument headings in ToolCallDetailsModal for waiting_confirmation
  - Remove redundant typewriter cursor className conditional and fix unused state var

* feat: message editing and deletion (#209)

* feat: add message editing and deletion with truncate-regenerate flow

  - Add PATCH /messages/{id} endpoint for editing user messages
  - Add DELETE /messages/{id} endpoint for deleting any message
  - Add regenerate WebSocket handler for re-running agent after edit
  - Add edit/delete UI to ChatBubble with hover actions
  - Add i18n translations for en/zh/ja

  Includes fixes from code review:
  - Fix pre-deduction error handling to skip dispatch on any failure
  - Reset responding state before regeneration to prevent stuck UI
  - Add message ownership verification before edit/delete operations

* fix: improve the code according to sourcery review

* fix: use version+SHA for beta image tags to ensure unique deployments (#211)

The previous approach used only the pyproject.toml version (e.g., 1.0.16)
which caused Kubernetes to not pull new images when multiple commits
used the same version tag.

Now uses format: {version}-{short-sha} (e.g., 1.0.16-f13e3c0)

Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com>

* feat: conversation interrupt/abort functionality (#212)

* feat: add conversation interrupt/abort functionality

- Add stop button and Escape key shortcut to abort streaming generation
- Implement Redis-based signaling between API server and Celery worker
- Worker checks abort signal every 0.5s and gracefully stops streaming
- Save partial content and perform partial billing on abort
- Add visual indicator for cancelled/aborted messages
- Add timeout fallback (10s) to reset UI if backend doesn't respond
- Add i18n strings for stop/stopping/escToStop in en/zh/ja

* fix: address abort feature edge cases from code review

- Clear stale abort signals at task start to prevent race condition when
  user reconnects quickly after disconnect
- Finalize AgentRun with 'failed' status on unhandled exceptions to ensure
  consistent DB state across all exit paths
- Move time import to module level (was inline import)

* fix: address Sourcery review feedback for abort feature

- Reuse existing Redis client for abort checks instead of creating new
  connections on each tick (performance improvement)
- Fix potential Redis connection leaks in ABORT and disconnect handlers
  by using try/finally pattern
- Track and cleanup abort timeout in frontend to prevent stale timers
  from racing with subsequent abort requests

* Preserve phase text when copying streamed output (#210)

* Preserve phase text when copying streamed output

* Extract agent phase content helper

* fix: use existing PhaseExecution type and correct useMemo dependencies

- Replace inline PhaseWithStreamedContent type with Pick<PhaseExecution, 'streamedContent'>
  for type consistency across the codebase
- Fix useMemo dependency array to use agentExecution?.phases instead of agentExecution
  to ensure proper recalculation when phases array changes

* Fix streaming agent messages and prevent deleting non-persisted messages (#213)

* Fix message deletion for agent executions

* Address Sourcery review: stricter UUID validation and safer id assignment

- Add isValidUuid utility with canonical UUID pattern (8-4-4-4-12 format)
  to replace loose regex that accepted invalid strings like all-hyphens
- Fix streaming_start handler to only set id when eventData.id is truthy,
  preventing accidental overwrites with undefined/null
- Improve delete guard with contextual messages ("still streaming" vs
  "not saved yet") and change notification type to warning
- Add comprehensive tests for isValidUuid covering valid UUIDs, client IDs,
  invalid formats, and edge cases

Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com>

---------

Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com>

* fix: emit message_saved event after stream abort (#215)

* fix: emit message_saved event after stream abort

When a user interrupts a streaming response, the message is saved to the
database but the frontend never receives the message_saved event. This
leaves the message with a temporary stream_ prefix ID, preventing deletion
until page refresh.

Now the MESSAGE_SAVED event is emitted after db.commit() in the abort
handler, before STREAM_ABORTED, so the frontend updates the message ID
to the real UUID and deletion works immediately.

Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com>

* fix: always show latest topic when clicking an agent

Unify sidebar and spatial workspace to use the same logic for selecting
topics. Both now fetch from backend and always show the most recently
updated topic (by updated_at) instead of remembering previously active topics.

Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com>

* feat: improve message editing UX with edit-only option and assistant editing

- Add "Edit" and "Edit & Regenerate" dropdown options for user messages
- Allow editing assistant messages (content-only, no regeneration)
- Add copy button to user messages
- Move assistant message actions to top-right for better UX
- Add auto-resizing textarea for editing long messages
- Update backend to support truncate_and_regenerate flag

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: extract message content resolution into dedicated module

Extract scattered content resolution logic into core/chat/messageContent.ts
with two main utilities:
- resolveMessageContent(): Single source of truth for content priority
- getMessageDisplayMode(): Explicit rendering mode determination

This refactoring:
- Reduces ChatBubble.tsx complexity (60+ line IIFE → 30 line switch)
- Fixes inconsistency between copy/edit and display logic
- Makes content source priority explicit and documented
- Adds guard for empty content to avoid rendering empty divs
- Improves maintainability with testable pure functions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com>

* fix: deep research structured output and recursion limit (#216)

- Use function_calling method for structured output in clarify node.
  The default json_mode doesn't work with Claude models via GPUGEEK
  provider. Claude supports tool/function calling natively but not
  OpenAI's response_format JSON mode.

- Increase recursion_limit from 25 to 50 in agent.astream() to handle
  complex research tasks with more iterations.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* fix: attach thinking content to agent execution message

When agent_start event arrives before thinking_start, it consumes the
loading message. The thinking_start handler then couldn't find a loading
message and created a separate thinking message, causing the thinking
content to appear as a separate bubble below the agent response.

Fix the thinking event handlers to also check for running agent execution
messages and attach thinking content to them instead of creating separate
messages.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: align thinking_end condition with thinking_chunk

Use consistent condition `m.agentExecution?.status === "running"` in both
thinking_chunk and thinking_end handlers for finding agent messages.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants