-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(web-shell): defer assistant footer during background agent work #8787
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cda1434
108133f
118f6b4
756c3e4
0f9b25c
ca73a26
d3695c3
36095fd
d0f1a4b
1382924
04050df
0e38286
4c6e4a6
8f8bdd3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| /** | ||
| * @license | ||
| * Copyright 2025 Qwen Team | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| import { describe, expect, it } from 'vitest'; | ||
| import { | ||
| DaemonHttpError, | ||
| isSessionLevelNotFound, | ||
| isSubagentSessionNotFound, | ||
| } from '../../src/daemon/DaemonHttpError.js'; | ||
|
|
||
| const missingAgentBody = { | ||
| code: 'session_not_found', | ||
| sessionId: 'session-1', | ||
| toolCallId: 'call-1', | ||
| }; | ||
|
|
||
| describe('isSubagentSessionNotFound', () => { | ||
| it('matches a 404 whose body identifies the missing agent', () => { | ||
| expect( | ||
| isSubagentSessionNotFound( | ||
| new DaemonHttpError(404, missingAgentBody, 'not found'), | ||
| 'call-1', | ||
| ), | ||
| ).toBe(true); | ||
| }); | ||
|
|
||
| it('matches a session-level 404 when no toolCallId is required', () => { | ||
| expect( | ||
| isSubagentSessionNotFound( | ||
| new DaemonHttpError( | ||
| 404, | ||
| { code: 'session_not_found', sessionId: 'session-1' }, | ||
| 'not found', | ||
| ), | ||
| ), | ||
| ).toBe(true); | ||
| }); | ||
|
|
||
| it.each([ | ||
| ['non-DaemonHttpError', new Error('not found'), 'call-1'], | ||
| [ | ||
| 'non-404 status', | ||
| new DaemonHttpError(500, missingAgentBody, 'server error'), | ||
| 'call-1', | ||
| ], | ||
| [ | ||
| 'missing code', | ||
| new DaemonHttpError(404, { toolCallId: 'call-1' }, 'not found'), | ||
| 'call-1', | ||
| ], | ||
| [ | ||
| 'wrong code', | ||
| new DaemonHttpError( | ||
| 404, | ||
| { ...missingAgentBody, code: 'workspace_not_found' }, | ||
| 'not found', | ||
| ), | ||
| 'call-1', | ||
| ], | ||
| [ | ||
| 'missing toolCallId in body', | ||
| new DaemonHttpError( | ||
| 404, | ||
| { code: 'session_not_found', sessionId: 'session-1' }, | ||
| 'not found', | ||
| ), | ||
| 'call-1', | ||
| ], | ||
| [ | ||
| 'null toolCallId in body', | ||
| new DaemonHttpError( | ||
| 404, | ||
| { code: 'session_not_found', sessionId: 'session-1', toolCallId: null }, | ||
| 'not found', | ||
| ), | ||
| 'call-1', | ||
| ], | ||
| [ | ||
| 'mismatched toolCallId', | ||
| new DaemonHttpError(404, missingAgentBody, 'not found'), | ||
| 'call-other', | ||
| ], | ||
| ])('rejects %s', (_label, error, toolCallId) => { | ||
| expect(isSubagentSessionNotFound(error, toolCallId as string)).toBe(false); | ||
| }); | ||
|
|
||
| it('rejects non-object bodies', () => { | ||
| expect( | ||
| isSubagentSessionNotFound( | ||
| new DaemonHttpError(404, 'session_not_found', 'not found'), | ||
| 'call-1', | ||
| ), | ||
| ).toBe(false); | ||
| }); | ||
| }); | ||
|
|
||
| describe('isSessionLevelNotFound', () => { | ||
| it('matches a 404 whose body has no toolCallId', () => { | ||
| expect( | ||
| isSessionLevelNotFound( | ||
| new DaemonHttpError( | ||
| 404, | ||
| { code: 'session_not_found', sessionId: 'session-1' }, | ||
| 'not found', | ||
| ), | ||
| ), | ||
| ).toBe(true); | ||
| }); | ||
|
|
||
| it('matches a 404 whose body carries a null toolCallId', () => { | ||
| expect( | ||
| isSessionLevelNotFound( | ||
| new DaemonHttpError( | ||
| 404, | ||
| { | ||
| code: 'session_not_found', | ||
| sessionId: 'session-1', | ||
| toolCallId: null, | ||
| }, | ||
| 'not found', | ||
| ), | ||
| ), | ||
| ).toBe(true); | ||
| }); | ||
|
|
||
| it('rejects an agent-level 404', () => { | ||
| expect( | ||
| isSessionLevelNotFound( | ||
| new DaemonHttpError(404, missingAgentBody, 'not found'), | ||
| ), | ||
| ).toBe(false); | ||
| }); | ||
|
|
||
| it('rejects a 404 whose body carries a different code', () => { | ||
| expect( | ||
| isSessionLevelNotFound( | ||
| new DaemonHttpError( | ||
| 404, | ||
| { code: 'workspace_not_found', sessionId: 'session-1' }, | ||
| 'not found', | ||
| ), | ||
| ), | ||
| ).toBe(false); | ||
| }); | ||
|
|
||
| it('rejects non-404 and non-matching errors', () => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] expect(
isSessionLevelNotFound(
new DaemonHttpError(
404,
{ code: 'workspace_not_found', sessionId: 'session-1' },
'not found',
),
),
).toBe(false);中文说明
— qwen3.8-max via Qwen Code /review (v0.21.10) |
||
| expect( | ||
| isSessionLevelNotFound( | ||
| new DaemonHttpError( | ||
| 500, | ||
| { code: 'session_not_found', sessionId: 'session-1' }, | ||
| 'server error', | ||
| ), | ||
| ), | ||
| ).toBe(false); | ||
| expect(isSessionLevelNotFound(new Error('not found'))).toBe(false); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Suggestion]
getErrorBodyRecordduplicates existingisRecordtype guard in the same package — Concrete cost: the codebase now has two implementations of the same non-null-non-array-object check in the same module hierarchy. If the check ever needs updating, the privategetErrorBodyRecordinDaemonHttpError.tswill be missed.中文说明
getErrorBodyRecord是同一包中已有isRecord类型守卫的重复实现。建议删除getErrorBodyRecord并导入acpTransportUtils.ts中的isRecord。— deepseek-v4-flash via Qwen Code /review (v0.21.8)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declined: the duplication is deliberate.
DaemonHttpError.tsis an intentionally dependency-free leaf module (see its header comment) —RestSseTransportimports it, and it sits in the graph of both budgeted browser bundles thatpackages/sdk-typescript/scripts/build.jsbyte-limits (MAX_DAEMON_BROWSER_BUNDLE_BYTESand the opt-indaemon/transportsbundle). ImportingisRecordfromacpTransportUtils.jswould transitively pull in the ~1000-line ACP route table (acpTransportUtilsimportsROUTE_TABLEfromacpRouteTable.js), and theui/utils.jscopy would pull in the UI rendering helpers — each is excluded from one of those bundles today precisely to keep them out. A 4-line non-null-non-array-object check is stable enough that a local private copy is the bundle-correct choice; a comment ongetErrorBodyRecordnow records this constraint so the next person weighing dedup sees the trade-off.中文说明
拒绝:该重复是有意为之。
DaemonHttpError.ts是一个刻意保持零依赖的叶子模块(见其头部注释)——RestSseTransport导入它,且它位于两个受字节预算约束的浏览器 bundle 的模块图中(packages/sdk-typescript/scripts/build.js中的MAX_DAEMON_BROWSER_BUNDLE_BYTES与可选的daemon/transportsbundle)。从acpTransportUtils.js导入isRecord会连带引入约 1000 行的 ACP 路由表(acpTransportUtils从acpRouteTable.js导入ROUTE_TABLE);而ui/utils.js中的副本则会引入 UI 渲染辅助函数——这两者今天正是为了不进这些 bundle 而被隔离的。4 行的"非 null、非数组对象"判断足够稳定,本地私有副本是符合 bundle 预算的正确选择;现已在getErrorBodyRecord上添加注释记录该约束,便于后续考虑去重的人了解权衡。