Conversation
📝 WalkthroughWalkthroughAdds Subagent Orchestrator V1: new MCP tool Changes
Sequence DiagramsequenceDiagram
actor User
participant Parent as Parent Session (Presenter)
participant Tool as SubagentOrchestratorTool
participant Runtime as AgentToolRuntimePort
participant Child as Child Session(s)
participant Events as DeepChatInternalSessionEvents
participant UI as Renderer
User->>Parent: invoke subagent_orchestrator tool
Parent->>Tool: call(request, { onProgress, signal })
Tool->>Runtime: createSubagentSession(...)
Tool->>Runtime: sendConversationMessage(childId, handoff)
par child runs (parallel) / chain (sequential)
Child->>Events: emit updates (blocks/status/progress)
Events->>Parent: update event (preview/response/waitingInteraction)
Parent->>Parent: update tool_call block (extra.subagentProgress / extra.subagentFinal)
Parent->>Tool: onProgress callback (aggregated)
Parent->>UI: emitDeepChatInternalSessionUpdate -> renderer updates subagent cards
end
Tool->>Parent: return final aggregated markdown + rawData
Parent->>Parent: persist extra.subagentFinal / message updates
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly Related PRs
Suggested Labels
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (10)
test/renderer/components/ChatSidePanel.test.ts (1)
79-81: Strengthen session-scoping coverage in this test.Line 79 validates the matching session payload, but Line 81 only checks total call count.
Consider adding a mismatch case to ensure a differentsessionIddoes not open the browser panel.🧪 Suggested test hardening
handler?.({}, { windowId: 7, sessionId: 'session-1' }) expect(sidepanelStore.openBrowser).toHaveBeenCalledTimes(1) + + handler?.({}, { windowId: 7, sessionId: 'session-2' }) + expect(sidepanelStore.openBrowser).toHaveBeenCalledTimes(1)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/renderer/components/ChatSidePanel.test.ts` around lines 79 - 81, Add a negative test case that calls handler with a non-matching sessionId and assert it does not trigger sidepanelStore.openBrowser again: after the existing call handler?.({}, { windowId: 7, sessionId: 'session-1' }) and expect(sidepanelStore.openBrowser).toHaveBeenCalledTimes(1), invoke handler?.({}, { windowId: 7, sessionId: 'other-session' }) and assert expect(sidepanelStore.openBrowser).toHaveBeenCalledTimes(1) (or assert not called with the mismatched payload); use the existing handler and sidepanelStore.openBrowser mocks to validate session-scoping behavior.test/main/presenter/newAgentPresenter/sessionManager.test.ts (1)
79-82: Add one non-default mapping test for new subagent fields.Current assertions validate defaults well; add a case where DB fields are explicitly populated to protect mapping logic from regressions.
🧪 Suggested test addition
+ it('maps explicit subagent fields from DB record', () => { + sqlitePresenter.newSessionsTable.get.mockReturnValue({ + id: 's2', + agent_id: 'deepchat', + title: 'Child', + project_dir: '/tmp/proj', + is_pinned: 0, + is_draft: 0, + session_kind: 'subagent', + parent_session_id: 'p1', + subagent_enabled: 1, + subagent_meta_json: '{"slotId":"slot-1"}', + created_at: 1, + updated_at: 2 + }) + + const record = manager.get('s2') + expect(record?.sessionKind).toBe('subagent') + expect(record?.parentSessionId).toBe('p1') + expect(record?.subagentEnabled).toBe(true) + expect(record?.subagentMeta).toEqual({ slotId: 'slot-1' }) + })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/main/presenter/newAgentPresenter/sessionManager.test.ts` around lines 79 - 82, Add a new unit test in sessionManager.test.ts that covers a non-default mapping of the new subagent fields: construct a DB session object with sessionKind: 'regular', parentSessionId set to a non-null value, subagentEnabled: true, and subagentMeta populated with a sample object, then call the same mapping function used in the existing tests (the presenter/session mapping helper used in this file) and assert the mapped result exposes the non-default values for parentSessionId, subagentEnabled, and subagentMeta; name the test something like "maps non-default subagent fields" and place it alongside the other mapping tests to guard against regressions.src/main/presenter/sqlitePresenter/tables/newSessions.ts (1)
67-73: Add an index for parent-child session lookups.The new
parent_session_idfilter will otherwise scan and sort the fullnew_sessionstable whenever the subagents view refreshes. An index onparent_session_id— ideally paired withupdated_at— will keep this query predictable as session history grows. You'll want the sameCREATE INDEXin the migration path too, not just fresh-table creation.📈 Suggested index
CREATE TABLE IF NOT EXISTS new_sessions ( ${columns.join(',\n ')} ); CREATE INDEX IF NOT EXISTS idx_new_sessions_agent ON new_sessions(agent_id); + CREATE INDEX IF NOT EXISTS idx_new_sessions_parent_updated + ON new_sessions(parent_session_id, updated_at DESC); CREATE INDEX IF NOT EXISTS idx_new_sessions_updated ON new_sessions(updated_at DESC);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/presenter/sqlitePresenter/tables/newSessions.ts` around lines 67 - 73, Add a composite index on parent_session_id (and include updated_at for ordering) to the new_sessions schema: update the CREATE TABLE/INDEX block that returns the SQL in newSessions (the template that currently creates idx_new_sessions_agent and idx_new_sessions_updated) to also CREATE INDEX IF NOT EXISTS idx_new_sessions_parent_updated ON new_sessions(parent_session_id, updated_at DESC); and mirror that same CREATE INDEX statement in the migration path that applies schema changes so existing databases get the new index as well.src/renderer/src/pages/NewThreadPage.vue (1)
365-377: Normalize bothresolveDeepChatAgentConfig()branches.Only the fallback branch currently passes through
normalizeDeepChatSubagentConfig(). Keeping the presenter branch raw makes this page depend on another layer already having expanded the new subagent fields, which is easy for mocks and alternate implementations to miss.♻️ Keep a single config invariant here
const resolveDeepChatAgentConfig = async (agentId: string): Promise<DeepChatAgentConfig> => { if (typeof configPresenter.resolveDeepChatAgentConfig === 'function') { - return configPresenter.resolveDeepChatAgentConfig(agentId) + return normalizeDeepChatSubagentConfig( + await configPresenter.resolveDeepChatAgentConfig(agentId) + ) } const systemPrompt = await configPresenter.getSetting?.('default_system_prompt')🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/pages/NewThreadPage.vue` around lines 365 - 377, The two code paths in resolveDeepChatAgentConfig must return the same normalized shape: when configPresenter.resolveDeepChatAgentConfig exists, await its result and pass it through normalizeDeepChatSubagentConfig before returning (instead of returning the raw presenter value), so both branches produce a DeepChatAgentConfig; keep the fallback branch as-is (using getSetting) and ensure you preserve typing/async behavior and any default fields when calling normalizeDeepChatSubagentConfig.src/renderer/settings/components/DeepChatAgentsSettings.vue (1)
440-452: Consider using a consistent Select component instead of native<select>.The subagent target dropdown uses a native
<select>element while other dropdowns in this component use shadcn'sDropdownMenuorPopoverwithSelect. This creates visual inconsistency in the UI.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/settings/components/DeepChatAgentsSettings.vue` around lines 440 - 452, The native <select> for subagent targets should be replaced with the project's consistent Select component (used elsewhere) to match visual style; locate the block rendering getSubagentTargetValue(slot), handleSubagentTargetChange(slot, $event) and iterating subagentTargetOptions, and render those options via the Select/SelectTrigger/SelectContent pattern used in this file (keeping the same value binding from getSubagentTargetValue and invoking handleSubagentTargetChange with the selected value), ensure keys use agentOption.value and labels use agentOption.label so behavior and data flow remain identical.src/renderer/src/components/sidepanel/WorkspacePanel.vue (1)
100-125: The highlight conditionchild.id === props.sessionIdis always false.In
childSessions, all sessions are filtered to haveparentSessionId === props.sessionId. Therefore, no child session'sidcan equalprops.sessionId, making the highlight conditional always evaluate to the "not selected" branch. This is likely copy-paste from similar patterns.Consider removing the dead highlight logic or replacing it with actual selection tracking if child selection highlighting is intended.
♻️ Simplify by removing unused highlight logic
<button v-for="child in childSessions" :key="child.id" class="flex w-full items-start gap-2 px-3 py-2 text-left text-xs transition-colors" - :class=" - child.id === props.sessionId - ? 'bg-accent text-accent-foreground' - : 'text-muted-foreground hover:bg-accent/60 hover:text-foreground' - " + class="text-muted-foreground hover:bg-accent/60 hover:text-foreground" type="button" `@click`="handleSubagentOpen(child.id)" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/components/sidepanel/WorkspacePanel.vue` around lines 100 - 125, The highlight conditional child.id === props.sessionId is dead because childSessions are filtered by parentSessionId === props.sessionId; remove or replace it: either drop the ternary and always apply the "not selected" class, or implement proper child selection tracking (introduce a selectedChildId ref/prop or use existing selected state) and change the condition to child.id === selectedChildId; update the click handler (handleSubagentOpen) to set selectedChildId if you choose selection tracking so the button highlights the clicked child correctly.src/renderer/src/components/message/MessageBlockToolCall.vue (1)
351-362: Consider adding basic property validation after JSON parsing.The
parseSubagentProgressfunction casts the parsed JSON directly toSubagentProgressPayloadwith only anArray.isArray(parsed?.tasks)check. If the JSON structure doesn't fully match the expected type (e.g.,modeis missing or invalid), downstream code accessingprogress.modecould produce unexpected results.♻️ Optional: Add minimal schema validation
const parseSubagentProgress = (value: unknown): SubagentProgressPayload | null => { if (typeof value !== 'string' || !value.trim()) { return null } try { const parsed = JSON.parse(value) as SubagentProgressPayload - return Array.isArray(parsed?.tasks) ? parsed : null + if (!Array.isArray(parsed?.tasks)) { + return null + } + if (parsed.mode !== 'parallel' && parsed.mode !== 'chain') { + return null + } + return parsed } catch { return null } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/components/message/MessageBlockToolCall.vue` around lines 351 - 362, The parseSubagentProgress function currently only checks that parsed?.tasks is an array; update it to perform minimal schema validation after JSON.parse: ensure parsed is an object, parsed.mode exists and is a string (and optionally one of the allowed mode values used elsewhere), parsed.tasks is an array, and each task item has the expected basic properties (e.g., id and status as strings or the minimal types your code expects). If any of these checks fail return null; keep the existing try/catch around JSON.parse and only cast/return the parsed object when all validations pass. Use the function name parseSubagentProgress and the SubagentProgressPayload shape to guide which fields to validate.docs/specs/subagent-orchestrator/spec.md (1)
78-83: Add an index forparent_session_idin the migration plan.The workspace list, restart recovery, and parent→child lookup paths will all query by this field. Without an index, those flows degrade to full scans as
new_sessionsgrows.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/specs/subagent-orchestrator/spec.md` around lines 78 - 83, Add a DB index on parent_session_id to avoid full-table scans: in the migration that alters the new_sessions table (where you added subagent_enabled, session_kind, parent_session_id, subagent_meta_json) add an index creation for parent_session_id (e.g. CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_new_sessions_parent_session_id ON new_sessions(parent_session_id)) in the up path and drop it in the down path; ensure the migration uses CONCURRENTLY if supported and update any schema/verification tests or CI migration checks to expect the new index.src/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.ts (1)
70-80: Deferred promise pattern works but could use standard approach.The pattern is functional, but the empty initial
resolvefunction assignment could be confusing. Consider using TypeScript's non-null assertion or a well-known deferred utility.Alternative using non-null assertion
const createDeferred = (): MutableTaskState['completion'] => { - let resolve = () => {} + let resolve!: () => void const promise = new Promise<void>((innerResolve) => { resolve = innerResolve }) return { promise, resolve } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.ts` around lines 70 - 80, The createDeferred function currently initializes resolve to a no-op which is confusing; change it to use TypeScript's non-null assertion so resolve is declared but assigned inside the Promise: replace "let resolve = () => {}" with "let resolve!: () => void" (keeping the Promise initializer that assigns innerResolve to resolve) so MutableTaskState['completion'] returns a properly-typed promise and resolve without a placeholder no-op; alternatively import/use a shared deferred utility and return its completion shape to avoid manual boilerplate.src/main/presenter/newAgentPresenter/index.ts (1)
421-427: Unsafe cast afterbuildSessionWithState.
buildSessionWithStatecan throw ifresolveAgentImplementationfails (e.g., agent not found). While the session record check is present, the subsequent call isn't wrapped in try-catch, so any error propagates. Consider usingtryBuildSessionWithStatewith a null guard or keeping the current pattern but documenting that errors propagate intentionally.Optional: Use try-catch for consistent error messaging
const record = this.sessionManager.get(sessionId) if (!record) { throw new Error(`Subagent session not found after creation: ${sessionId}`) } - return (await this.buildSessionWithState(record)) as SessionWithState + try { + return await this.buildSessionWithState(record) + } catch (error) { + throw new Error(`Failed to build subagent session state: ${sessionId}`) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/presenter/newAgentPresenter/index.ts` around lines 421 - 427, The code unsafely casts the result of buildSessionWithState to SessionWithState without handling errors that buildSessionWithState may throw (e.g., from resolveAgentImplementation); wrap the call in a try-catch and rethrow or return a clearer error, or call tryBuildSessionWithState and guard the null result before casting, while keeping the existing sessionManager.get check; specifically update the block that uses sessionManager.get(sessionId) so it calls tryBuildSessionWithState(record) and checks for null (or wrap await this.buildSessionWithState(record) in try-catch) and produce a consistent error message referencing sessionId and the underlying failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/presenter/deepchatAgentPresenter/dispatch.ts`:
- Around line 784-795: The current guard only updates the UI when
extractSubagentToolState returns subagentProgress, so a final-only payload
(subagentFinal) is dropped; change the conditional in dispatch.ts to run
updateSubagentToolCallBlock whenever either subagentState.subagentProgress or
subagentState.subagentFinal is present (e.g., if (subagentState.subagentProgress
|| subagentState.subagentFinal)), keep the same content conversion logic using
toolResponseToText for non-string content, and pass both
subagentState.subagentProgress and subagentState.subagentFinal to
updateSubagentToolCallBlock so final-only updates are persisted.
In `@src/main/presenter/deepchatAgentPresenter/index.ts`:
- Around line 2941-2959: The call to this.toolPresenter.callTool is using
this.getAbortSignalForSession(sessionId) which is often undefined because the
paused-generation controller was cleared; create and register a dedicated
AbortController for this deferred subagent tool run before invoking callTool
(keyed by sessionId and toolCall.id), pass its signal into callTool, and ensure
you unregister/abort that controller in the finally/cleanup path so
cancelGeneration(sessionId) (and cancel logic) can find and cancel this running
subagent_orchestrator; updateSubagentToolCallProgress remains unchanged but use
toolCall.id when registering/removing the controller so cancelGeneration can
reference it.
In `@src/main/presenter/deepchatAgentPresenter/internalSessionEvents.ts`:
- Around line 72-106: extractWaitingInteraction currently scans blocks backwards
and returns the last pending action, but respondToolInteraction expects the
earliest unresolved action for a message; change the scan order to iterate from
the start (index 0 to blocks.length-1) so the function returns the earliest
pending interaction. Keep the same matching conditions (block.type !== 'action',
block.status !== 'pending', block.extra?.needsUserAction !== true,
!block.tool_call?.id) and return shapes (type 'permission' or 'question',
messageId, toolCallId, actionBlock as JSON clone) when the first matching block
is found so the resume path and respondToolInteraction receive the same pending
entry.
- Around line 51-61: The current toDisplayLines used by
buildAssistantResponseMarkdown trims each line and removes blank lines, which
destroys leading indentation and empty-line formatting (breaking fenced code,
nested lists, YAML) when mirroring child responses; change toDisplayLines to
only split on CRLF/newline and return lines as-is (do not .trim() and do not
.filter(Boolean)) so extractBlockText -> toDisplayLines ->
buildAssistantResponseMarkdown preserves original whitespace and blank lines.
- Around line 109-117: The emitDeepChatInternalSessionUpdate function currently
calls emitter.emit('update', update) synchronously which lets exceptions thrown
by subscribers bubble up; modify emitDeepChatInternalSessionUpdate to wrap the
emitter.emit call in a try-catch so subscriber failures are isolated, catch any
thrown error and log it (use the existing logger if available or console.error)
and swallow or handle the error so it does not propagate to callers like
setSessionStatus or emitMessageRefresh; keep
subscribeDeepChatInternalSessionUpdates unchanged and reference emitter.emit and
emitDeepChatInternalSessionUpdate when making the change.
In `@src/main/presenter/newAgentPresenter/index.ts`:
- Around line 1229-1251: The method setSessionSubagentEnabled currently
force-casts the result of tryBuildSessionWithState to SessionWithState even
though tryBuildSessionWithState can return null; change this by removing the
unsafe cast, capturing the result into a variable, checking if it's null, and
throwing a descriptive error (e.g., "Failed to build session state for
sessionId") before returning; ensure this check happens after
sessionManager.update and emitSessionListUpdated so callers never receive a null
value from setSessionSubagentEnabled.
In `@src/main/presenter/sqlitePresenter/tables/newSessions.ts`:
- Around line 209-215: The current logic adds "session_kind = 'regular'"
whenever filters.includeSubagents !== true, which unintentionally excludes child
sessions when filters.parentSessionId is provided; change the condition so that
session_kind='regular' is only added when includeSubagents is explicitly false
(or when parentSessionId is not provided). Concretely, update the check around
conditions.push("session_kind = 'regular'") to skip adding that condition if
filters.parentSessionId is set (or use filters.includeSubagents === false
instead of !== true), so that queries with filters.parentSessionId return child
sessions as expected; adjust any affected params handling accordingly
(references: filters.includeSubagents, filters.parentSessionId,
conditions.push("session_kind = 'regular'")).
In `@src/renderer/src/components/chat/ChatStatusBar.vue`:
- Around line 982-992: The computed showSubagentToggle currently allows the
toggle for any non-ACP active session with sessionKind === 'regular'; update the
active-session branch to also require the active session's agentType to be
'deepchat' so the toggle is only shown for DeepChat sessions. Specifically, in
the computed showing logic (showSubagentToggle) change the hasActiveSession
branch to return true only when sessionStore.activeSession?.sessionKind ===
'regular' AND sessionStore.activeSession?.agentType === 'deepchat' (keep
existing isAcpAgent and selectedAgentType checks and use null-safe access).
In `@src/renderer/src/i18n/ru-RU/chat.json`:
- Line 172: Translate the English value for the key workspace.sections.artifacts
in the ru-RU bundle to Russian (replace "Artifacts" with an appropriate Russian
translation like "Артефакты") so the ru-RU chat.json no longer contains
mixed-language UI; update the value for the key "artifacts" in the existing
object entry to the Russian string.
In `@src/renderer/src/i18n/zh-HK/settings.json`:
- Line 1630: The value for the i18n key subagentsTitle is still in English;
update the value for "subagentsTitle" to a Traditional Chinese translation
(e.g., "子代理") so it matches the surrounding zh-HK strings, keeping the key name
unchanged and preserving JSON formatting in settings.json.
In
`@test/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.test.ts`:
- Around line 43-55: The test's workdir source is ambiguous because
resolveConversationWorkdir, parentSession.projectDir, and
childSession.projectDir currently all equal the same path, so the assertion
doesn't confirm the tool actually uses the resolver; update the fixture so these
three values differ (e.g., give parentSession.projectDir and
childSession.projectDir distinct paths and have
resolveConversationWorkdir.mockResolvedValue return the expected handoff path)
and assert that SubagentOrchestratorTool uses resolveConversationWorkdir (the
mocked function) rather than copying either session.projectDir; change the
buildSessionInfo calls for parentSession and childSession to set unique
projectDir values and adjust the resolveConversationWorkdir mock return
accordingly.
---
Nitpick comments:
In `@docs/specs/subagent-orchestrator/spec.md`:
- Around line 78-83: Add a DB index on parent_session_id to avoid full-table
scans: in the migration that alters the new_sessions table (where you added
subagent_enabled, session_kind, parent_session_id, subagent_meta_json) add an
index creation for parent_session_id (e.g. CREATE INDEX CONCURRENTLY IF NOT
EXISTS idx_new_sessions_parent_session_id ON new_sessions(parent_session_id)) in
the up path and drop it in the down path; ensure the migration uses CONCURRENTLY
if supported and update any schema/verification tests or CI migration checks to
expect the new index.
In `@src/main/presenter/newAgentPresenter/index.ts`:
- Around line 421-427: The code unsafely casts the result of
buildSessionWithState to SessionWithState without handling errors that
buildSessionWithState may throw (e.g., from resolveAgentImplementation); wrap
the call in a try-catch and rethrow or return a clearer error, or call
tryBuildSessionWithState and guard the null result before casting, while keeping
the existing sessionManager.get check; specifically update the block that uses
sessionManager.get(sessionId) so it calls tryBuildSessionWithState(record) and
checks for null (or wrap await this.buildSessionWithState(record) in try-catch)
and produce a consistent error message referencing sessionId and the underlying
failure.
In `@src/main/presenter/sqlitePresenter/tables/newSessions.ts`:
- Around line 67-73: Add a composite index on parent_session_id (and include
updated_at for ordering) to the new_sessions schema: update the CREATE
TABLE/INDEX block that returns the SQL in newSessions (the template that
currently creates idx_new_sessions_agent and idx_new_sessions_updated) to also
CREATE INDEX IF NOT EXISTS idx_new_sessions_parent_updated ON
new_sessions(parent_session_id, updated_at DESC); and mirror that same CREATE
INDEX statement in the migration path that applies schema changes so existing
databases get the new index as well.
In `@src/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.ts`:
- Around line 70-80: The createDeferred function currently initializes resolve
to a no-op which is confusing; change it to use TypeScript's non-null assertion
so resolve is declared but assigned inside the Promise: replace "let resolve =
() => {}" with "let resolve!: () => void" (keeping the Promise initializer that
assigns innerResolve to resolve) so MutableTaskState['completion'] returns a
properly-typed promise and resolve without a placeholder no-op; alternatively
import/use a shared deferred utility and return its completion shape to avoid
manual boilerplate.
In `@src/renderer/settings/components/DeepChatAgentsSettings.vue`:
- Around line 440-452: The native <select> for subagent targets should be
replaced with the project's consistent Select component (used elsewhere) to
match visual style; locate the block rendering getSubagentTargetValue(slot),
handleSubagentTargetChange(slot, $event) and iterating subagentTargetOptions,
and render those options via the Select/SelectTrigger/SelectContent pattern used
in this file (keeping the same value binding from getSubagentTargetValue and
invoking handleSubagentTargetChange with the selected value), ensure keys use
agentOption.value and labels use agentOption.label so behavior and data flow
remain identical.
In `@src/renderer/src/components/message/MessageBlockToolCall.vue`:
- Around line 351-362: The parseSubagentProgress function currently only checks
that parsed?.tasks is an array; update it to perform minimal schema validation
after JSON.parse: ensure parsed is an object, parsed.mode exists and is a string
(and optionally one of the allowed mode values used elsewhere), parsed.tasks is
an array, and each task item has the expected basic properties (e.g., id and
status as strings or the minimal types your code expects). If any of these
checks fail return null; keep the existing try/catch around JSON.parse and only
cast/return the parsed object when all validations pass. Use the function name
parseSubagentProgress and the SubagentProgressPayload shape to guide which
fields to validate.
In `@src/renderer/src/components/sidepanel/WorkspacePanel.vue`:
- Around line 100-125: The highlight conditional child.id === props.sessionId is
dead because childSessions are filtered by parentSessionId === props.sessionId;
remove or replace it: either drop the ternary and always apply the "not
selected" class, or implement proper child selection tracking (introduce a
selectedChildId ref/prop or use existing selected state) and change the
condition to child.id === selectedChildId; update the click handler
(handleSubagentOpen) to set selectedChildId if you choose selection tracking so
the button highlights the clicked child correctly.
In `@src/renderer/src/pages/NewThreadPage.vue`:
- Around line 365-377: The two code paths in resolveDeepChatAgentConfig must
return the same normalized shape: when
configPresenter.resolveDeepChatAgentConfig exists, await its result and pass it
through normalizeDeepChatSubagentConfig before returning (instead of returning
the raw presenter value), so both branches produce a DeepChatAgentConfig; keep
the fallback branch as-is (using getSetting) and ensure you preserve
typing/async behavior and any default fields when calling
normalizeDeepChatSubagentConfig.
In `@test/main/presenter/newAgentPresenter/sessionManager.test.ts`:
- Around line 79-82: Add a new unit test in sessionManager.test.ts that covers a
non-default mapping of the new subagent fields: construct a DB session object
with sessionKind: 'regular', parentSessionId set to a non-null value,
subagentEnabled: true, and subagentMeta populated with a sample object, then
call the same mapping function used in the existing tests (the presenter/session
mapping helper used in this file) and assert the mapped result exposes the
non-default values for parentSessionId, subagentEnabled, and subagentMeta; name
the test something like "maps non-default subagent fields" and place it
alongside the other mapping tests to guard against regressions.
In `@test/renderer/components/ChatSidePanel.test.ts`:
- Around line 79-81: Add a negative test case that calls handler with a
non-matching sessionId and assert it does not trigger sidepanelStore.openBrowser
again: after the existing call handler?.({}, { windowId: 7, sessionId:
'session-1' }) and expect(sidepanelStore.openBrowser).toHaveBeenCalledTimes(1),
invoke handler?.({}, { windowId: 7, sessionId: 'other-session' }) and assert
expect(sidepanelStore.openBrowser).toHaveBeenCalledTimes(1) (or assert not
called with the mismatched payload); use the existing handler and
sidepanelStore.openBrowser mocks to validate session-scoping behavior.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: ba710e02-f220-4cd4-a6b6-df9b1719ce30
📒 Files selected for processing (82)
docs/specs/subagent-orchestrator/plan.mddocs/specs/subagent-orchestrator/spec.mddocs/specs/subagent-orchestrator/tasks.mdsrc/main/presenter/agentRepository/index.tssrc/main/presenter/configPresenter/index.tssrc/main/presenter/deepchatAgentPresenter/dispatch.tssrc/main/presenter/deepchatAgentPresenter/index.tssrc/main/presenter/deepchatAgentPresenter/internalSessionEvents.tssrc/main/presenter/index.tssrc/main/presenter/newAgentPresenter/index.tssrc/main/presenter/newAgentPresenter/sessionManager.tssrc/main/presenter/sqlitePresenter/tables/baseTable.tssrc/main/presenter/sqlitePresenter/tables/newSessions.tssrc/main/presenter/toolPresenter/agentTools/agentToolManager.tssrc/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.tssrc/main/presenter/toolPresenter/index.tssrc/main/presenter/toolPresenter/runtimePorts.tssrc/renderer/settings/components/DeepChatAgentsSettings.vuesrc/renderer/src/components/chat-input/McpIndicator.vuesrc/renderer/src/components/chat/ChatStatusBar.vuesrc/renderer/src/components/chat/ChatTopBar.vuesrc/renderer/src/components/chat/messageListItems.tssrc/renderer/src/components/message/MessageBlockToolCall.vuesrc/renderer/src/components/sidepanel/WorkspacePanel.vuesrc/renderer/src/i18n/da-DK/chat.jsonsrc/renderer/src/i18n/da-DK/common.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/en-US/chat.jsonsrc/renderer/src/i18n/en-US/common.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/fa-IR/chat.jsonsrc/renderer/src/i18n/fa-IR/common.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/fr-FR/chat.jsonsrc/renderer/src/i18n/fr-FR/common.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/he-IL/chat.jsonsrc/renderer/src/i18n/he-IL/common.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/chat.jsonsrc/renderer/src/i18n/ja-JP/common.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/chat.jsonsrc/renderer/src/i18n/ko-KR/common.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/pt-BR/chat.jsonsrc/renderer/src/i18n/pt-BR/common.jsonsrc/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/src/i18n/ru-RU/chat.jsonsrc/renderer/src/i18n/ru-RU/common.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/zh-CN/chat.jsonsrc/renderer/src/i18n/zh-CN/common.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/zh-HK/chat.jsonsrc/renderer/src/i18n/zh-HK/common.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/zh-TW/chat.jsonsrc/renderer/src/i18n/zh-TW/common.jsonsrc/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/pages/ChatPage.vuesrc/renderer/src/pages/NewThreadPage.vuesrc/renderer/src/stores/ui/draft.tssrc/renderer/src/stores/ui/session.tssrc/renderer/src/stores/ui/sidepanel.tssrc/shared/chat.d.tssrc/shared/lib/deepchatSubagents.tssrc/shared/types/agent-interface.d.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/shared/types/presenters/new-agent.presenter.d.tssrc/shared/types/presenters/tool.presenter.d.tssrc/shared/types/presenters/workspace.d.tstest/main/presenter/newAgentPresenter/newAgentPresenter.test.tstest/main/presenter/newAgentPresenter/sessionManager.test.tstest/main/presenter/sqlitePresenter.test.tstest/main/presenter/toolPresenter/agentTools/agentToolManagerSettings.test.tstest/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.test.tstest/renderer/components/ChatSidePanel.test.tstest/renderer/components/ChatStatusBar.test.tstest/renderer/components/DeepChatAgentsSettings.test.tstest/renderer/components/McpIndicator.test.tstest/renderer/components/message/MessageBlockToolCall.test.ts
src/main/presenter/deepchatAgentPresenter/internalSessionEvents.ts
Outdated
Show resolved
Hide resolved
test/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.test.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/presenter/deepchatAgentPresenter/index.ts`:
- Around line 641-642: respondToolInteraction() currently awaits
executeDeferredToolCall() to completion, which blocks the renderer; change it to
start executeDeferredToolCall(sessionId, messageId, toolCall) in a
non-blocking/background way (e.g., fire-and-forget or spawn a detached promise)
and return immediately after wiring up onProgress handlers so waitingInteraction
events can be delivered while the subagent continues running; ensure onProgress
and the execution promise still report terminalError asynchronously (e.g., via
logger or a follow-up RPC) but do not await the full execution in
respondToolInteraction().
- Around line 2847-2881: updateSubagentToolCallProgress is overwriting a stale
snapshot of message.content; instead of parsing the earlier-captured message,
re-read the latest message from storage inside the try block (call
this.messageStore.getMessage(messageId) again), parse that current
message.content to blocks, find the tool_block by tool_call.id === toolCallId,
mutate only that block's tool_call.response/status/extra, then call
this.messageStore.updateAssistantContent(messageId, blocks) and
emitMessageRefresh; reference updateSubagentToolCallProgress,
this.messageStore.getMessage, this.messageStore.updateAssistantContent and
ensure this change prevents clobbering locally-added blocks (e.g. those added by
executeDeferredToolCall).
- Around line 3019-3034: The code only updates subagent state when
subagentProgress exists, dropping cases where only subagentFinal is returned;
add a branch that when subagentProgress is not a string but subagentFinal is a
string it still calls updateSubagentToolCallProgress with the same identifiers
(sessionId, messageId, toolCall.id || '', toolContentToText(rawData.content))
and passes undefined for progress and the subagentFinal string for the final
parameter so final-only deferred subagent snapshots are persisted; keep existing
branch for when subagentProgress is present.
In `@src/renderer/src/components/message/MessageBlockToolCall.vue`:
- Around line 55-58: Normalize the subagent task payload in the computed
property subagentTasks so each task always has stable identifiers and labels:
ensure task.taskId falls back to slotId (e.g., taskId = task.taskId ||
task.slotId), ensure targetAgentName falls back to displayName or a safe
placeholder, and compute a label (used in render) as displayName || slotId ||
"Unnamed Task"; then update the template v-for key to use the normalized id
(e.g., task.taskId or task.normalizedId) and use the normalized label where
labels are rendered. Apply the same normalization everywhere subagentTasks is
used (the other occurrences around lines 76-78, 322-333, 378-383) so keys never
become undefined and labels never render empty.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4e39e66f-77fd-4676-87e7-d7db8c9fd0ee
📒 Files selected for processing (29)
src/main/presenter/deepchatAgentPresenter/dispatch.tssrc/main/presenter/deepchatAgentPresenter/index.tssrc/main/presenter/deepchatAgentPresenter/internalSessionEvents.tssrc/main/presenter/newAgentPresenter/index.tssrc/main/presenter/sqlitePresenter/tables/newSessions.tssrc/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.tssrc/renderer/src/components/chat/ChatStatusBar.vuesrc/renderer/src/components/chat/ChatTopBar.vuesrc/renderer/src/components/chat/MessageList.vuesrc/renderer/src/components/message/MessageBlockAction.vuesrc/renderer/src/components/message/MessageBlockToolCall.vuesrc/renderer/src/components/message/MessageItemAssistant.vuesrc/renderer/src/components/message/MessageItemUser.vuesrc/renderer/src/components/message/MessageToolbar.vuesrc/renderer/src/i18n/ru-RU/chat.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/pages/ChatPage.vuetest/main/presenter/deepchatAgentPresenter/deepchatAgentPresenter.test.tstest/main/presenter/deepchatAgentPresenter/dispatch.test.tstest/main/presenter/deepchatAgentPresenter/internalSessionEvents.test.tstest/main/presenter/newAgentPresenter/newAgentPresenter.test.tstest/main/presenter/sqlitePresenter.test.tstest/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.test.tstest/renderer/components/ChatPage.test.tstest/renderer/components/ChatStatusBar.test.tstest/renderer/components/MessageList.test.tstest/renderer/components/WorkspacePanel.test.tstest/renderer/components/message/MessageBlockToolCall.test.tstest/renderer/components/message/MessageToolbar.trace.test.ts
✅ Files skipped from review due to trivial changes (2)
- test/renderer/components/WorkspacePanel.test.ts
- test/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.test.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- src/renderer/src/i18n/zh-HK/settings.json
- test/main/presenter/newAgentPresenter/newAgentPresenter.test.ts
- test/main/presenter/sqlitePresenter.test.ts
- test/renderer/components/message/MessageBlockToolCall.test.ts
- src/renderer/src/components/chat/ChatStatusBar.vue
- src/main/presenter/deepchatAgentPresenter/internalSessionEvents.ts
- src/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.ts
- src/main/presenter/newAgentPresenter/index.ts
| const execution = await this.executeDeferredToolCall(sessionId, messageId, toolCall) | ||
| if (execution.terminalError) { |
There was a problem hiding this comment.
Don't block respondToolInteraction() on the full resumed subagent run.
executeDeferredToolCall() now surfaces child waits through onProgress, but this branch still awaits the whole tool execution before returning. The renderer keeps a page-wide busy flag while awaiting this RPC, so a child waitingInteraction emitted mid-run cannot be answered until the orchestrator exits.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/presenter/deepchatAgentPresenter/index.ts` around lines 641 - 642,
respondToolInteraction() currently awaits executeDeferredToolCall() to
completion, which blocks the renderer; change it to start
executeDeferredToolCall(sessionId, messageId, toolCall) in a
non-blocking/background way (e.g., fire-and-forget or spawn a detached promise)
and return immediately after wiring up onProgress handlers so waitingInteraction
events can be delivered while the subagent continues running; ensure onProgress
and the execution promise still report terminalError asynchronously (e.g., via
logger or a follow-up RPC) but do not await the full execution in
respondToolInteraction().
| private updateSubagentToolCallProgress( | ||
| sessionId: string, | ||
| messageId: string, | ||
| toolCallId: string, | ||
| responseMarkdown: string, | ||
| progressJson: string, | ||
| finalJson?: string | ||
| ): void { | ||
| const message = this.messageStore.getMessage(messageId) | ||
| if (!message || message.role !== 'assistant') { | ||
| return | ||
| } | ||
|
|
||
| try { | ||
| const blocks = JSON.parse(message.content) as AssistantMessageBlock[] | ||
| const toolBlock = blocks.find( | ||
| (block) => block.type === 'tool_call' && block.tool_call?.id === toolCallId | ||
| ) | ||
| if (!toolBlock?.tool_call) { | ||
| return | ||
| } | ||
|
|
||
| toolBlock.tool_call.response = responseMarkdown | ||
| toolBlock.status = finalJson ? 'success' : 'loading' | ||
| toolBlock.extra = { | ||
| ...toolBlock.extra, | ||
| subagentProgress: progressJson, | ||
| ...(finalJson ? { subagentFinal: finalJson } : {}) | ||
| } | ||
| this.messageStore.updateAssistantContent(messageId, blocks) | ||
| this.emitMessageRefresh(sessionId, messageId) | ||
| } catch (error) { | ||
| console.warn('[DeepChatAgent] Failed to persist subagent tool progress:', error) | ||
| } | ||
| } |
There was a problem hiding this comment.
Progress writes are based on a stale message snapshot.
This helper rereads message.content from storage and overwrites it. During respondToolInteraction(), the granted/replied actionBlock is still only in the local blocks array until after executeDeferredToolCall() returns, so the first progress callback can write the pre-resolution version of the message back and keep the original prompt pending in the UI.
💡 One way to avoid the stale overwrite
this.dispatchHook('PreToolUse', {
sessionId,
messageId,
providerId: state?.providerId,
modelId: state?.modelId,
projectDir,
tool: {
callId: toolCall.id,
name: toolCall.name,
params: toolCall.params
}
})
+ this.messageStore.updateAssistantContent(messageId, blocks)
+ this.emitMessageRefresh(sessionId, messageId)
const execution = await this.executeDeferredToolCall(sessionId, messageId, toolCall)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/presenter/deepchatAgentPresenter/index.ts` around lines 2847 - 2881,
updateSubagentToolCallProgress is overwriting a stale snapshot of
message.content; instead of parsing the earlier-captured message, re-read the
latest message from storage inside the try block (call
this.messageStore.getMessage(messageId) again), parse that current
message.content to blocks, find the tool_block by tool_call.id === toolCallId,
mutate only that block's tool_call.response/status/extra, then call
this.messageStore.updateAssistantContent(messageId, blocks) and
emitMessageRefresh; reference updateSubagentToolCallProgress,
this.messageStore.getMessage, this.messageStore.updateAssistantContent and
ensure this change prevents clobbering locally-added blocks (e.g. those added by
executeDeferredToolCall).
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 `@src/renderer/src/components/message/MessageBlockToolCall.vue`:
- Around line 393-415: Replace the hardcoded fallback 'Unnamed Task' with a
vue-i18n lookup: call the existing i18n translator (t) with a new key (e.g.,
"chat.toolCall.subagents.unnamedTask") in the mapping that builds each task (the
mapping that uses normalizeOptionalText, normalizedId, label, title, slotId,
sessionId, targetAgentId, targetAgentName, status); also add the matching
key/value to the renderer i18n files under src/renderer/src/i18n so the label
uses t('chat.toolCall.subagents.unnamedTask') instead of the literal string.
In `@test/main/presenter/deepchatAgentPresenter/deepchatAgentPresenter.test.ts`:
- Around line 3008-3088: The test currently calls
updateSubagentToolCallProgress() directly; instead drive the flow through
executeDeferredToolCall(sessionId, messageId, toolCall) so the new
deferred-progress path is exercised: mock toolPresenter.callTool to call the
provided options.onProgress(...) with the same progress payloads (including the
updated summary and subagentProgress JSON) and ensure
sqlitePresenter.deepchatMessagesTable.get returns the stale then latest rows as
before; then assert sqlitePresenter.deepchatMessagesTable.updateContent received
the merged blocks and that executeDeferredToolCall triggers
emitMessageRefresh('s1','m1') as in the original expectations.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 93526712-c170-4f3f-ae27-7c1481f8a02c
📒 Files selected for processing (4)
src/main/presenter/deepchatAgentPresenter/index.tssrc/renderer/src/components/message/MessageBlockToolCall.vuetest/main/presenter/deepchatAgentPresenter/deepchatAgentPresenter.test.tstest/renderer/components/message/MessageBlockToolCall.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/presenter/deepchatAgentPresenter/index.ts
test/main/presenter/deepchatAgentPresenter/deepchatAgentPresenter.test.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
🧹 Nitpick comments (2)
test/renderer/components/message/MessageBlockToolCall.test.ts (1)
710-761: Consider adding a negative test for disabled task clicks.The test verifies normalization and fallbacks well, but doesn't explicitly assert that clicking a task with
sessionId: nulldoes not callselectSession. While the button is disabled, an explicit assertion would strengthen the test.💡 Suggested addition
// After line 760 await tasks[1].trigger('click') expect(selectSessionMock).toHaveBeenCalledTimes(1) // Still only called once (from tasks[0])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/renderer/components/message/MessageBlockToolCall.test.ts` around lines 710 - 761, Add an explicit negative assertion that clicking a disabled task (sessionId: null) does not call selectSession; after mounting MessageBlockToolCall and triggering the enabled task (which calls selectSessionMock), trigger a disabled task element found by '[data-testid="subagent-task-trigger"]' (e.g., tasks[1] and optionally tasks[2]) and assert expect(selectSessionMock).toHaveBeenCalledTimes(1) so the mock call count hasn't increased; reference selectSessionMock, MessageBlockToolCall, and the data-testid selector to locate where to insert the extra triggers and assertion.src/renderer/src/components/message/MessageBlockToolCall.vue (1)
695-701: Consider adding user feedback when session navigation fails.Per the relevant code snippet from
session.ts,selectSessiondoes not validate that the session exists before attempting to activate it. If a child session ID hasn't been registered in the store yet (e.g., due to a race condition), the user could be left in a broken state with no visible feedback.Consider adding a try-catch or checking session existence before navigation:
💡 Suggested improvement
const handleSubagentSessionOpen = (task: SubagentProgressTask) => { if (!task.sessionId) { return } - void sessionStore.selectSession(task.sessionId) + sessionStore.selectSession(task.sessionId).catch((err) => { + console.warn('[MessageBlockToolCall] Failed to open subagent session:', err) + }) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/components/message/MessageBlockToolCall.vue` around lines 695 - 701, The handler handleSubagentSessionOpen currently calls sessionStore.selectSession(task.sessionId) without verifying the session exists or handling failures; modify it to first check for the session (e.g., via sessionStore.getSession / sessionStore.hasSession or equivalent) and only call selectSession when present, and if not present or if selectSession rejects, surface user feedback (toast/notification or error state) so the user knows navigation failed; alternatively wrap the selectSession call in try-catch to show an error message referencing task.sessionId when an exception occurs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/renderer/src/components/message/MessageBlockToolCall.vue`:
- Around line 695-701: The handler handleSubagentSessionOpen currently calls
sessionStore.selectSession(task.sessionId) without verifying the session exists
or handling failures; modify it to first check for the session (e.g., via
sessionStore.getSession / sessionStore.hasSession or equivalent) and only call
selectSession when present, and if not present or if selectSession rejects,
surface user feedback (toast/notification or error state) so the user knows
navigation failed; alternatively wrap the selectSession call in try-catch to
show an error message referencing task.sessionId when an exception occurs.
In `@test/renderer/components/message/MessageBlockToolCall.test.ts`:
- Around line 710-761: Add an explicit negative assertion that clicking a
disabled task (sessionId: null) does not call selectSession; after mounting
MessageBlockToolCall and triggering the enabled task (which calls
selectSessionMock), trigger a disabled task element found by
'[data-testid="subagent-task-trigger"]' (e.g., tasks[1] and optionally tasks[2])
and assert expect(selectSessionMock).toHaveBeenCalledTimes(1) so the mock call
count hasn't increased; reference selectSessionMock, MessageBlockToolCall, and
the data-testid selector to locate where to insert the extra triggers and
assertion.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1be7dc12-cee6-45ed-b091-66426ce0f751
📒 Files selected for processing (15)
src/renderer/src/components/message/MessageBlockToolCall.vuesrc/renderer/src/i18n/da-DK/chat.jsonsrc/renderer/src/i18n/en-US/chat.jsonsrc/renderer/src/i18n/fa-IR/chat.jsonsrc/renderer/src/i18n/fr-FR/chat.jsonsrc/renderer/src/i18n/he-IL/chat.jsonsrc/renderer/src/i18n/ja-JP/chat.jsonsrc/renderer/src/i18n/ko-KR/chat.jsonsrc/renderer/src/i18n/pt-BR/chat.jsonsrc/renderer/src/i18n/ru-RU/chat.jsonsrc/renderer/src/i18n/zh-CN/chat.jsonsrc/renderer/src/i18n/zh-HK/chat.jsonsrc/renderer/src/i18n/zh-TW/chat.jsontest/main/presenter/deepchatAgentPresenter/deepchatAgentPresenter.test.tstest/renderer/components/message/MessageBlockToolCall.test.ts
✅ Files skipped from review due to trivial changes (4)
- src/renderer/src/i18n/pt-BR/chat.json
- src/renderer/src/i18n/en-US/chat.json
- src/renderer/src/i18n/fa-IR/chat.json
- src/renderer/src/i18n/ru-RU/chat.json
🚧 Files skipped from review as they are similar to previous changes (6)
- src/renderer/src/i18n/zh-TW/chat.json
- test/main/presenter/deepchatAgentPresenter/deepchatAgentPresenter.test.ts
- src/renderer/src/i18n/zh-HK/chat.json
- src/renderer/src/i18n/ja-JP/chat.json
- src/renderer/src/i18n/fr-FR/chat.json
- src/renderer/src/i18n/ko-KR/chat.json
Summary by CodeRabbit
New Features
Localization