Skip to content

feat(web-shell): add task auth and goal workflows#4856

Open
ytahdn wants to merge 17 commits into
QwenLM:daemon_mode_b_mainfrom
chiga0:feat/tasks_auth_goal_completion
Open

feat(web-shell): add task auth and goal workflows#4856
ytahdn wants to merge 17 commits into
QwenLM:daemon_mode_b_mainfrom
chiga0:feat/tasks_auth_goal_completion

Conversation

@ytahdn

@ytahdn ytahdn commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds web-shell support for richer daemon-driven workflows around tasks, auth, and goals. The web shell can now surface task status with structured rendering, expose auth and goal status panels, and consume the new daemon/session APIs needed for those workflows. It also hardens related task and goal interactions, keeps task status rendering shared, and restores the transcript block hook required by pending permission extraction.

Why it's needed

These workflows were already available through the daemon/CLI side but were awkward or incomplete in the web-shell experience. Bringing them into web-shell makes task visibility, authentication state, and goal progress available without falling back to textual command output or separate terminal flows.

Reviewer Test Plan

How to verify

Start the daemon and web-shell against this branch, then verify that /tasks, /auth, and /goal render their corresponding interactive/status surfaces. Trigger a background task and confirm task status updates are shown in the footer/status UI and the /tasks command output uses the same structured rendering. Open a session with pending permissions and confirm the composer hides correctly instead of crashing from a missing transcript blocks hook.

Evidence (Before & After)

Before: tasks/auth/goal workflows were not fully represented in web-shell and the task command duplicated text formatting. After: web-shell surfaces these workflows through structured panels/messages and shared task status rendering.

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

Verified locally with npm run build in packages/web-shell.

Risk & Scope

  • Main risk or tradeoff: The web-shell UI now has more daemon workflow surfaces, so regressions would most likely appear around command routing, status rendering, or pending permission display.
  • Not validated / out of scope: Full cross-platform manual UI validation was not performed.
  • Breaking changes / migration notes: None expected.

Linked Issues

N/A

中文说明

What this PR does

为 web-shell 增加围绕 tasks、auth 和 goal 的更完整 daemon 工作流支持。web-shell 现在可以用结构化方式展示任务状态,展示 auth 和 goal 状态面板,并消费这些工作流需要的 daemon/session API。本 PR 也加固了相关 task 和 goal 交互,复用 task 状态渲染逻辑,并恢复 pending permission 提取所需的 transcript blocks hook。

Why it's needed

这些工作流在 daemon/CLI 侧已经具备,但 web-shell 体验里还不完整,用户需要依赖文本命令输出或额外终端流程。把它们补到 web-shell 后,任务可见性、认证状态和 goal 进度都可以直接在 web-shell 中查看和操作。

Reviewer Test Plan

How to verify

基于该分支启动 daemon 和 web-shell,然后验证 /tasks/auth/goal 能展示对应的交互或状态界面。触发一个后台任务,确认 footer/status UI 会展示任务状态,并且 /tasks 命令输出复用同一套结构化渲染。打开带 pending permission 的 session,确认 composer 会正确隐藏,而不是因为缺少 transcript blocks hook 崩溃。

Evidence (Before & After)

Before:tasks/auth/goal 工作流在 web-shell 中没有完整结构化展示,task 命令也有重复的文本格式化逻辑。After:web-shell 通过结构化 panel/message 展示这些工作流,并复用 task 状态渲染。

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

本地在 packages/web-shell 下执行 npm run build 验证通过。

Risk & Scope

  • Main risk or tradeoff:web-shell 增加了更多 daemon 工作流入口,潜在回归主要会出现在命令路由、状态渲染或 pending permission 展示。
  • Not validated / out of scope:没有做完整跨平台手动 UI 验证。
  • Breaking changes / migration notes:预期没有。

Linked Issues

N/A

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @ytahdn!

Template looks good ✓ — all required sections present and filled out, including bilingual summary and test plan.

On direction: web-shell surfacing of daemon task/auth/goal workflows is clearly aligned with the daemon_mode_b_main branch goals. Claude Code has analogous features (task panels, background agent management, etc.), confirming this is a relevant area. The three features share daemon session API infrastructure, so bundling them on a feature branch makes sense.

On approach: +4393/-254 across 48 files is a big PR, but the three features (tasks panel, auth wizard, goal status) are genuinely coupled through the daemon REST endpoints and session types they add. The layering looks right — bridge types → CLI handlers → SDK client → web-shell UI. One thought: the auth provider install flow (/workspace/auth/providers + POST /workspace/auth/provider) is nearly self-contained and could have been its own PR, but splitting at this point would create merge churn for marginal benefit on a feature branch.

The background task notification queue in Session.ts (~180 lines of async drain/poll logic with 100ms polling and 1s timeout) is the most complex piece. It seems necessary for surfacing background task completions into the conversation, but worth keeping an eye on during review — that kind of async state machine is where subtle bugs hide.

Moving on to code review. 🔍

中文说明

感谢贡献,@ytahdn

模板完整 ✓ — 所有必需章节均已填写,包括双语摘要和测试计划。

方向:web-shell 对 daemon tasks/auth/goal 工作流的支持与 daemon_mode_b_main 分支目标一致。Claude Code 也有类似功能(任务面板、后台 agent 管理等),说明这个方向是有价值的。三个功能共享 daemon session API 基础设施,在 feature branch 上合并提交是合理的。

方案:+4393/-254 跨 48 个文件是一个大型 PR,但三个功能(任务面板、认证向导、goal 状态)通过 daemon REST 端点和 session 类型紧密耦合。分层结构正确 — bridge types → CLI handlers → SDK client → web-shell UI。一个想法:auth provider 安装流程(/workspace/auth/providers + POST /workspace/auth/provider)几乎可以独立成 PR,但在 feature branch 上拆分只会增加合并冲突,收益有限。

Session.ts 中的后台任务通知队列(约 180 行异步 drain/poll 逻辑,100ms 轮询和 1s 超时)是最复杂的部分。这对于将后台任务完成情况呈现到对话中似乎是必要的,但在审查期间值得关注 — 这类异步状态机是隐藏 bug 的地方。

进入代码审查 🔍

Qwen Code · qwen3.7-max

Comment thread packages/web-shell/client/App.tsx Outdated
Comment thread packages/web-shell/client/components/messages/TasksStatusMessage.tsx Outdated
Comment thread packages/web-shell/client/components/StatusBar.tsx
Comment thread packages/cli/src/acp-integration/session/Session.ts
Comment thread packages/cli/src/acp-integration/session/Session.ts
Comment thread packages/web-shell/client/components/messages/GoalStatusMessage.tsx
Comment thread packages/cli/src/serve/server.ts Outdated
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/web-shell/client/utils/tasksCommand.ts
@ytahdn ytahdn requested review from chiga0, doudouOUC and yiliang114 June 8, 2026 15:13

@DragonnZhang DragonnZhang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review Summary

This PR adds task auth and goal workflows to the web-shell, including interactive panels for /auth, /tasks, and /goal commands, background task notification delivery, and a StatusBar task pill. The overall architecture is sound and the code follows existing patterns well.

Positive observations:

  • Auth flow correctly uses type="password" for API key input and maskApiKey() in the review step
  • Settings mutations are protected by withSettingsLock
  • New HTTP endpoints use mutate() middleware consistent with existing routes
  • Input validation in parseAuthProviderInstallRequest is thorough
  • The task cancel flow has proper double-press confirmation for foreground agents
  • Background sub-agent classification correctly excludes from the floating agent panel
  • The withSettingsLock serialization prevents concurrent auth install races

Findings (2 issues, 0 critical):

See inline comments for details.

Comment thread packages/web-shell/client/components/Editor.tsx Outdated
Comment thread packages/cli/src/acp-integration/session/Session.ts Outdated
@tanzhenxin tanzhenxin added the type/feature-request New feature or enhancement request label Jun 9, 2026

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Notification drain deadlock cycle (Session.ts:650)

When a background task finishes during #executePrompt, the notification callback schedules #processBackgroundTaskNotifications which awaits pendingPromptCompletion. After #executePrompt, prompt() calls #drainBackgroundTaskNotifications which awaits notificationProcessing. But pendingPromptCompletion is only resolved after the drain completes → deadlock cycle.

The 1-second BACKGROUND_TASK_NOTIFICATION_DRAIN_TIMEOUT_MS timeout breaks the cycle eventually but causes a guaranteed 1-second stall per affected prompt.

Fix options:

  • Move resolveCompletion() before the drain call
  • Remove await this.pendingPromptCompletion inside #processBackgroundTaskNotifications (serialization is already ensured by the drain)
  • Add a shorter timeout to await this.notificationProcessing

(Posted as body-level comment because this line overlaps with an existing R1 comment about a different concern.)

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/serve/server.ts Outdated
Comment thread packages/cli/src/acp-integration/session/Session.ts
Comment thread packages/cli/src/serve/server.ts Outdated
Comment thread packages/cli/src/acp-integration/session/Session.ts
Comment thread packages/web-shell/client/App.tsx Outdated
Comment thread packages/acp-bridge/src/bridgeTypes.ts
@ytahdn

ytahdn commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the body-level notification drain deadlock review as well. The notification callback now only queues completions while a user prompt is active, so it does not start a background processor that waits on pendingPromptCompletion while prompt() is waiting for the drain. The prompt drain remains the single consumer for notifications produced during the prompt. Also verified the related notification path with Session.test.ts and the full Session test file now passes.

@ytahdn ytahdn force-pushed the feat/tasks_auth_goal_completion branch from e98d9e0 to d0764af Compare June 9, 2026 11:28
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/acp-integration/session/Session.test.ts Outdated
Comment thread packages/web-shell/client/App.tsx

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Test failure: auth_provider_install was added to SERVE_CAPABILITY_REGISTRY but EXPECTED_STAGE1_FEATURES / EXPECTED_REGISTERED_FEATURES in server.test.ts were not updated, causing 3 test failures ("returns a fresh ordered registered feature list", "advertises current-protocol features", "marks every current feature with its historical v1 origin").

Add 'auth_provider_install' to both expected arrays at the correct position.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/serve/server.ts Outdated
Comment thread packages/cli/src/acp-integration/session/Session.ts Outdated
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx Outdated
Comment thread packages/web-shell/client/components/StatusBar.tsx

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] 11 test failures block merge

(1) 3 tests fail in server.test.ts: auth_provider_install was added to SERVE_CAPABILITY_REGISTRY but EXPECTED_STAGE1_FEATURES / EXPECTED_REGISTERED_FEATURES were not updated — the registry now has 61 features but the test expects 57.

(2) 8 runQwenServe integration tests timeout (Error: Hook timed out in 10000ms): the new startup path triggers ACP preheat failed, will retry on first session: AcpSessionBridge initialize timed out after 10000ms on every server start in test mode. The new runQwenServe startup sequence needs a test-friendly fast-path that skips ACP preheat when no bridge is configured.

— qwen3.7-max via Qwen Code /review

Comment thread packages/web-shell/client/App.tsx Outdated
Comment thread packages/cli/src/acp-integration/session/Session.ts Outdated
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/serve/capabilities.ts
Comment thread packages/cli/src/serve/runQwenServe.ts
Comment thread packages/cli/src/acp-integration/session/emitters/MessageEmitter.ts
Comment thread packages/web-shell/client/components/StatusBar.tsx
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/web-shell/client/App.tsx Outdated
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully: Qwen review timed out after 55 minutes. See workflow logs.

Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/cli/src/acp-integration/session/Session.ts Outdated
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/serve/runQwenServe.ts
Comment thread packages/cli/src/acp-integration/session/Session.ts Outdated
@wenshao wenshao linked an issue Jun 9, 2026 that may be closed by this pull request
Comment thread packages/web-shell/client/components/MessageList.tsx Outdated
Comment thread packages/web-shell/client/components/messages/AuthMessage.tsx
Comment thread packages/web-shell/client/components/messages/AuthMessage.tsx
Comment thread packages/web-shell/client/components/messages/GoalStatusMessage.tsx Outdated
Comment thread packages/cli/src/acp-integration/session/Session.ts
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/web-shell/client/components/messages/AuthMessage.tsx
Comment thread packages/web-shell/client/components/messages/TasksStatusMessage.tsx Outdated
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully: Qwen review timed out after 55 minutes. See workflow logs.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Still unaddressed from previous review rounds — verified still present at 9692796 (kept out of inline comments to avoid duplicating the existing open threads):

  • taskKind (bridge: bridgeTypes.ts:384, bridge.ts:3410) vs kind (REST body server.ts:2016, SDK, data model) — same field renamed mid-pipeline
  • Goal-status sentinel 'web-shell:goal-status:v1:' duplicated producer/consumer (DaemonSessionProvider.tsx:1097 / GoalStatusMessage.tsx:29) with the producer payload typed Record<string, unknown>
  • Dead default: break + trailing return { cancelled: false } after the exhaustive switch (acpAgent.ts:3137-3140)
  • Unnecessary cast (connection as { displayName?: string }) (App.tsx:769) — the PR itself added displayName to DaemonConnectionState
  • Dead .model CSS class (StatusBar.module.css:74)
  • Duplicate independent 3s task polling (StatusBar.tsx:212 + TasksStatusMessage.tsx:219)
  • Observability set: sessionTaskCancel zero logging (acpAgent.ts:3084); notification tool-call warning logs only a count, no tool names/task context (Session.ts:1818); provider install rewrites settings.json with no audit log (runQwenServe.ts:948); drain timeout logged at debug level (Session.ts:1626); install error log lacks providerId (server.ts:1654); goal set dispatched optimistically with no rollback on sendPrompt failure (App.tsx:1296)
  • Untested contracts: the task-cancel chain (route strict gate, three kinds, paused case) and the POST /workspace/auth/provider error envelope (501/400 codes, numeric-validation bounds)

Pre-existing on base (informational, not attributable to this PR) — verified by running the suites against the merge base: the capability suite is already red on daemon_mode_b_main (3 stale baseline entries), the runQwenServe describe block has a flaky 10s afterEach close timeout, and acpAgent.test.ts has 2 pre-existing failures (settings.forScope) plus 2 teardown unhandled rejections.

— claude-fable-5 via Claude Code /qreview

Comment thread packages/cli/src/serve/capabilities.ts
Comment thread packages/cli/src/acp-integration/session/Session.ts
Comment thread packages/cli/src/acp-integration/session/Session.ts Outdated
Comment thread packages/cli/src/acp-integration/session/Session.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/web-shell/client/components/messages/TasksStatusMessage.tsx Outdated
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/acp-integration/session/Session.test.ts Outdated
Comment thread packages/cli/src/serve/server.test.ts
Comment thread packages/web-shell/client/components/messages/GoalStatusMessage.tsx
Comment thread packages/web-shell/client/App.tsx Outdated
Comment thread packages/web-shell/client/App.tsx
Comment thread packages/cli/src/acp-integration/session/Session.ts
params.prompt,
);
if (isGoalCommand) {
this.#installGoalTerminalObserver();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Goal terminal observer only installed for /goal slash commands

#installGoalTerminalObserver() is called exclusively here, gated by isGoalCommand (line 835: /^\/goal(?:\s|$)/i). Goals set through other paths — daemon API, session restore, or internal mechanisms — will never have the observer installed.

Without the observer, goal terminal events (achieved/failed/aborted) won't reach the web-shell UI, causing the goal status display to remain stale. The daemon continues evaluating the goal, but the user sees no progress updates.

Impact: Users who set goals via the daemon API (or whose goals persist across session restarts) will see a broken goal status UI — the goal pill shows "active" indefinitely even after the goal reaches a terminal state.

Suggested fix: Install the observer whenever the session has an active goal, not just on /goal commands. Consider checking getActiveGoal(this.sessionId) at session initialization and installing the observer if a goal exists. Alternatively, install it in #executePrompt whenever getActiveGoal() returns a non-null value.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

先不修。Impact is low — only affects ACP session restore with an active goal, which is a narrow path. The goal loop itself works correctly; only the UI status card is missing. Deferred to follow-up PR.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

后续补充,session restore 路径的 goal observer 安装需要更完整的方案。

Comment thread packages/cli/src/serve/server.ts

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ CI is failing (review-pr). Review completed despite CI status.

Review Summary

This round found 3 unique issues not covered by the 31 existing inline comments from prior rounds:

Suggestion

  1. Shell task cancel returns cancelled: true while process still runningacpAgent.ts sessionTaskCancel shell case: requestCancel(taskId) is asynchronous (signals but doesn't wait for termination), but the handler immediately returns { cancelled: true, status: 'running' }. Callers see a misleading success response — the task may still be consuming CPU/memory. Return { cancelled: true, status: 'cancelling' } or document that cancelled does not guarantee immediate termination.

  2. Goal terminal observer never cleaned up (memory leak)Session.ts:#installGoalTerminalObserver: the observer is registered in a global Map<string, GoalTerminalObserver> but clearGoalTerminalObserver(this.sessionId) is never called. Over the daemon lifecycle, stale observers accumulate and prevent GC of Session instances. If the daemon reuses a sessionId, old observers could fire on the new session's SSE channel. Call clearGoalTerminalObserver in cancelPendingPrompt() and any session-dispose path.

  3. Cron prompt doesn't install notification callbackSession.ts:#executeCronPrompt: #installBackgroundTaskNotificationCallback() is only called in #executePrompt, not in the cron path. Background task notifications triggered during cron execution are silently lost — users may never learn about tasks that complete while a cron job runs. Install and tear down the callback in #executeCronPrompt as well.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

@ytahdn ytahdn left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed review findings — see individual thread replies for details.

Comment thread packages/cli/src/acp-integration/session/Session.ts Outdated
Comment thread packages/cli/src/serve/server.ts Outdated
Comment thread packages/cli/src/serve/server.ts Outdated
Comment thread packages/acp-bridge/src/bridgeTypes.ts
Comment thread packages/cli/src/acp-integration/session/Session.ts
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/web-shell/client/components/messages/TasksStatusMessage.tsx Outdated
Comment thread packages/web-shell/client/components/messages/AuthMessage.tsx
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully: Qwen review timed out after 55 minutes. See workflow logs.

ytahdn added 5 commits June 10, 2026 22:24
- Add /auth command with interactive authentication panel for
  daemon serve mode, supporting login/logout/refresh flows
- Add /tasks command with interactive background tasks panel
  aligned with CLI's BackgroundTasksDialog (list/detail views,
  keyboard navigation, cancel/stop with double-press confirm)
- Add daemon-side task cancel endpoint and SDK client method
- Fix background agent notification delivery in ACP Session
  so completed agents trigger new model turns via SSE stream
- Add task status polling with 2s auto-refresh while panel open
- Support dynamic hints based on selected task state
- Classify background sub-agent tool calls to exclude from
  floating agent panel
…ate prompt errors

- Add recentActivities, stats, prompt fields to agent task data chain
  (acp-bridge types → CLI serialization → SDK types → web-shell UI)
- Fix broadcastTurnError extracting "[object Object]" from JSON-RPC
  error objects by reading data.details for the actual error message
- Fix duplicate error display in web-shell by marking errors already
  dispatched by sendPrompt and skipping them in reportError
- Remove "Prompt failed" prefix from prompt error messages
- Add StatusBar task pill, tasks command enhancements, i18n additions
ytahdn added 10 commits June 10, 2026 22:28
- Add POST /session/:id/goal/clear API so /goal clear during active
  generation no longer destroys in-progress work (bypasses cancel+sendPrompt)
- Snapshot/restore chat history around notification prompts to prevent
  polluting shared conversation context
- Null pendingPrompt in finally block to prevent stale controller on error
- Wrap notification .finally() body in try/catch to prevent unhandled rejection
- Add identity guard to dispatchGoalCleared to prevent race with new goal set
- Strip trailing dot from hostname in SSRF blocklist check
- Suppress per-iteration goal checking events from transcript
- Validate goal status kind against known union members
- Clean up goal hook on session close to prevent observer leak
- Show actionError in task detail view
- Cross-reference duplicated GOAL_CLEAR_KEYWORDS constant
@ytahdn ytahdn force-pushed the feat/tasks_auth_goal_completion branch from 8452769 to 5e9c305 Compare June 10, 2026 14:42
ytahdn added 2 commits June 10, 2026 23:01
Add deadline check inside inner notification drain loop to prevent
unbounded processing when new notifications arrive during drain.
@ytahdn ytahdn force-pushed the feat/tasks_auth_goal_completion branch from 6467f6a to 2e20612 Compare June 10, 2026 15:33

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] dispose() incomplete — old notification state not cleaned up

dispose() aborts notificationAbortController, clears notificationQueue, and resets boolean notificationProcessing — these are the base branch's notification fields. But the PR adds a parallel set of fields (notificationAbort, notificationDraining, notificationResolve, notificationProcessing: Promise<void> | null at lines ~399-412) that are not cleaned up here.

If a background task notification is mid-processing when dispose() runs (via /clear or session reload):

  • notificationAbort is never aborted → orphaned model call continues consuming API tokens
  • notificationResolve is never called → drain loop may hang or fire on disposed state
  • notificationDraining is never reset → .finally() at ~line 1904 may reschedule processing because notificationQueue.length > 0 is checked without a this.disposed guard

Add cleanup for all PR-introduced notification state, and add if (this.disposed) return; guards to #scheduleBackgroundTaskNotificationProcessing(), #processBackgroundTaskNotifications(), and #drainBackgroundTaskNotifications().

[Suggestion] SSRF blocklist omits 100.64.0.0/10 Shared Address Space (RFC 6598)

isBlockedAuthProviderHost at server.ts:368-407 checks RFC 1918 (10/8, 172.16/12, 192.168/16), 127/8, 169.254/16, and 0/8, but not 100.64/10 (CGN range). This range is routable inside AWS VPCs, GCP networks, and Kubernetes clusters. An authenticated user can install an auth provider with baseUrl: "http://100.64.x.x/..." to SSRF cloud-internal services. Fix: add (a === 100 && b !== undefined && b >= 64 && b <= 127) to the IPv4 return expression.

private readonly createdAt: number = Date.now();
private readonly runtimeBaseDir: string;

// Background task notification state

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Duplicate property declarations with incompatible types — two notification state machines collide

The PR adds new notification fields here (notificationQueue: Array<{ displayText: string; modelText: string }>, notificationProcessing: Promise<void> | null), but the base branch (daemon_mode_b_main) already declares the same names at lines ~428-431 with incompatible types:

Field PR (here) Base branch (line ~428)
notificationQueue Array<{ displayText, modelText }> BackgroundNotificationQueueItem[] (has taskId, status, kind)
notificationProcessing Promise<void> | null boolean

This produces TS2717/TS2300 compilation errors and means the two notification subsystems — the PR's background-task drain and the base branch's cron-style processor — are silently fighting over the same state. The PR's new code paths that reference this.notificationAbort, this.notificationDraining, this.notificationResolve, and this.notificationCounter operate on a separate set of fields that are not declared in the base, so they either reference undefined or shadow the base fields depending on declaration order.

Additionally, GoalTerminalEvent is imported twice: as a type at line ~28 and again as type GoalTerminalEvent at line ~92. With verbatimModuleSyntax, this may produce TS2300.

Suggested change
// Background task notification state
// Unify the two notification systems into one set of fields.
// Option A: Remove the PR's duplicate declarations and adapt the new drain
// logic to use the existing BackgroundNotificationQueueItem type and the
// existing notificationAbortController/notificationCompletion fields.
// Option B: Rename the PR's fields (e.g., taskNotificationQueue,
// taskNotificationAbort) to avoid collision, and register separate callbacks.
// Also: remove the duplicate GoalTerminalEvent import (keep only the one at line ~92).

— qwen3.7-max via Qwen Code /review

});
}

async #processBackgroundTaskNotifications(): Promise<void> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Notification processor TOCTOU race can stall user prompts

#processBackgroundTaskNotifications checks this.pendingPrompt before scheduling, but does NOT re-check it after await pendingPromptCompletion. A user prompt that arrives in the same microtask window will find this.notificationAbort === null (not yet assigned by the processor), making the abort in sendPrompt a no-op. The user prompt then blocks on await this.notificationProcessing for the full duration of the notification's model round-trip (potentially 30+ seconds).

In a daemon with active monitors producing rapid output, each monitor event triggers a sequential model call that blocks the next user prompt. This can be sustained as a denial-of-service by writing to a monitored file.

Fix: after await pendingPromptCompletion, add if (this.pendingPrompt) return;. Also have sendPrompt set this.notificationAbort before await this.notificationProcessing.

— qwen3.7-max via Qwen Code /review

ToolConfirmationOutcome.ProceedOnce,
);
if (approvalMode === ApprovalMode.AUTO) {
const before = this.config.getAutoModeDenialState();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Auto mode denial state preservation is a no-op

const before = this.config.getAutoModeDenialState();
const after = before;
// ...
this.config.setAutoModeDenialState(after);

getAutoModeDenialState() returns a reference to the internal object, so after === before and setAutoModeDenialState(after) writes back the identical reference. The debugLogger.warn call is the only side effect, producing a misleading warn-level log on every hook approval in AUTO mode.

If the intent is to prevent denial counters from being reset after hook approval, the actual reset-prevention logic is missing. Either implement the intended transformation or remove the block and demote the log to debug level.

— qwen3.7-max via Qwen Code /review

);

app.post(
'/session/:id/goal/clear',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] POST /session/:id/goal/clear and sessionGoalClear ext method — zero test coverage

This new route (~24 lines) calls bridge.clearSessionGoal(sessionId) but server.test.ts has no test cases for it, and acpAgent.test.ts has no tests for the sessionGoalClear ext method. The fakeBridge in server.test.ts does not implement clearSessionGoal.

Untested paths: happy path (200 + { cleared: true }), SessionNotFoundError propagation, missing sessionId param guard, cleared: false when no goal hook is registered.

Similarly, the ~276 lines of new notification queue/drain code in Session.ts (#drainBackgroundTaskNotifications, #processBackgroundTaskNotifications, #executeNotificationPrompt, #installBackgroundTaskNotificationCallback) have zero dedicated test coverage.

— qwen3.7-max via Qwen Code /review

private readonly runtimeBaseDir: string;

// Background task notification state
private notificationQueue: Array<{ displayText: string; modelText: string }> =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Duplicate class member declarations — build-breaking

The PR adds a new notification system (lines 398–408) with fields notificationQueue, notificationProcessing, notificationAbort, and notificationDraining. But a pre-existing notification system already declares notificationQueue (line 423: BackgroundNotificationQueueItem[]), notificationProcessing (line 424: boolean), and notificationAbortController (line 425).

The two systems have incompatible types:

Field New (line 398/406) Pre-existing (line 423/424)
notificationQueue Array<{ displayText; modelText }> BackgroundNotificationQueueItem[]
notificationProcessing Promise<void> | null boolean

npx tsc --noEmit reports 13 compilation errors (TS2300 duplicate identifier, TS2717 type mismatch, TS2322 boolean-not-assignable-to-Promise, TS2339 property missing). The package cannot build.

This looks like a merge artifact — two branches each implemented a background notification system, and both sets of declarations survived the merge. One system needs to be removed or they must be unified.

— qwen3.7-plus via Qwen Code /review

* overwrite the active prompt's completion tracker.
*/
private notificationProcessing: Promise<void> | null = null;
private notificationAbort: AbortController | null = null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] isIdle() does not check new notification system fields

isIdle() (line 487) checks !this.notificationProcessing and !this.notificationAbortController — both from the pre-existing notification system. But this new notification system uses different fields:

  • this.notificationAbort (this line) — NOT checked in isIdle()
  • this.notificationDraining (line 408) — NOT checked in isIdle()

When the new notification system is actively draining or processing, isIdle() returns true incorrectly. Any caller gating on isIdle() — session lifecycle management, rewind/restore guards, session deletion — will believe the session is idle while a notification prompt is mid-stream.

Fix: add !this.notificationAbort && !this.notificationDraining to the isIdle() return expression.

— qwen3.7-plus via Qwen Code /review

);
const advancedConfig = rawAdvanced
? {
...(typeof rawAdvanced['enableThinking'] === 'boolean'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] POST /session/:id/goal/clear endpoint has zero test coverage

Unlike POST /session/:id/tasks/:taskId/cancel (which has 3+ tests covering happy path, invalid params, and bridge error mapping), the new goal/clear endpoint has no tests in server.test.ts. Missing scenarios:

  1. Happy path: bridge.clearSessionGoal() returns { cleared: true } → 200 response
  2. SessionNotFoundError from bridge → 404 mapping
  3. Missing or invalid sessionId route parameter → 400 response

Similarly, the sessionGoalClear case in acpAgent.ts (the SERVE_CONTROL_EXT_METHODS handler) has no test coverage — compare with sessionTaskCancel which has 5 tests.

— qwen3.7-plus via Qwen Code /review

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review Summary

7 Session.test.ts failures (3 auto-compress drain + 4 AUTO-mode review) and a paused-agent cancel bug block merge. The duplicate property declarations in Session.ts suggest a merge-conflict artifact that should be cleaned up.

7 test failures in Session.test.ts:

  • 3 auto-compress drain tests fail — #drainMidTurnUserMessages() was removed from the auto-compress retry path
  • 4 AUTO-mode review tests fail — L5 approval logic restructuring broke classifier/dispatch expectations

Test coverage gaps: POST /session/:id/goal/clear route and sessionGoalClear ext method have no tests.

— qwen3.7-max via Qwen Code /review

@@ -389,6 +396,20 @@ export class Session implements SessionContext {
private readonly createdAt: number = Date.now();
private readonly runtimeBaseDir: string;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Duplicate property declarations — merge-conflict artifact

The class declares notificationQueue and notificationProcessing twice with incompatible types:

Field Line 398 (new, PR) Line 425 (pre-existing)
notificationQueue Array<{displayText, modelText}> BackgroundNotificationQueueItem[]
notificationProcessing Promise<void> | null boolean

Additionally, GoalTerminalEvent is imported twice — value import at line 28 and type import at line 90.

TypeScript allows duplicate class properties (last declaration wins), but this is clearly a merge-conflict artifact. The first set (lines 398-408) appears to be dead code superseded by the pre-existing notification drain subsystem.

Suggested change
// Background task notification state — removed: superseded by the
// notification drain subsystem below (lines 425-428).

Also remove the duplicate GoalTerminalEvent import at line 28 (keep only line 90).

— qwen3.7-max via Qwen Code /review

);
return { cancelled: false, reason, status: task?.status };
}
config.getBackgroundTaskRegistry().cancel(taskId);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Paused agent task cancel is a no-op — cancel() ignores paused tasks

BackgroundTaskRegistry.cancel() (background-tasks.ts:455) returns immediately for non-running entries: if (!entry || entry.status !== 'running') return;. Yet this guard allows paused tasks through and calls cancel(), which silently no-ops. The handler then returns { cancelled: true, status: 'paused' }, falsely claiming success.

The correct method for paused tasks is abandon() (background-tasks.ts:502), which sets status to cancelled and marks notified = true.

Suggested change
config.getBackgroundTaskRegistry().cancel(taskId);
if (task.status === 'paused') {
config.getBackgroundTaskRegistry().abandon(taskId);
} else {
config.getBackgroundTaskRegistry().cancel(taskId);
}

— qwen3.7-max via Qwen Code /review

await confirmationDetails.onConfirm(
ToolConfirmationOutcome.ProceedOnce,
);
if (approvalMode === ApprovalMode.AUTO) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Auto mode denial state "preservation" is a no-op

This block reads the denial state after onConfirm has already been called (line 3260), aliases it with const after = before (same reference), and writes it back unchanged. The log message says "preserved" but no preservation actually occurs.

If the intent is to prevent onConfirm(ProceedOnce) from mutating the denial counters, the snapshot must be taken before onConfirm:

Suggested change
if (approvalMode === ApprovalMode.AUTO) {
if (approvalMode === ApprovalMode.AUTO) {
const snapshot = { ...this.config.getAutoModeDenialState() };
await confirmationDetails.onConfirm(
ToolConfirmationOutcome.ProceedOnce,
);
this.config.setAutoModeDenialState(snapshot);
debugLogger.warn(
`Auto mode denial counters preserved after hook approval: ` +
`${formatDenialStateLog(snapshot)}`,
);
}

If this was dead scaffolding from a refactor, remove the entire block.

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully: Failed to determine state for PR #4856. See workflow logs.

const [a, b] = parts;
return (
a === 0 ||
a === 10 ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

SSRF bypass: missing 100.64.0.0/10 (CGNAT) in isBlockedAuthProviderHost

The IPv4 block list in isBlockedAuthProviderHost covers the standard RFC 1918 private ranges plus link-local and loopback, but omits 100.64.0.0/10 (Shared Address Space, RFC 6598). This range is widely used by cloud providers for internal services (e.g., AWS/GCP internal endpoints, carrier-grade NAT, VPC-internal routes).

An authenticated client can POST to /workspace/auth/provider with baseUrl: "http://100.64.1.1/secret-api" and the SSRF guard will allow it, since 100.x doesn't match any of the existing block conditions.

Suggested fix — add the CGNAT range to the block list:

return (
  a === 0 ||
  a === 10 ||
  a === 127 ||
  (a === 100 && b !== undefined && b >= 64 && b <= 127) || // 100.64.0.0/10
  (a === 169 && b === 254) ||
  (a === 172 && b !== undefined && b >= 16 && b <= 31) ||
  (a === 192 && b === 168)
);

Severity: Medium — requires bearer auth to reach the endpoint, and --allow-private-auth-base-url defaults to false (so the block list IS enforced in production). But when the block list is enforced, this is a genuine gap.

} from '@qwen-code/qwen-code-core';
import { NOT_CURRENTLY_GENERATING_CANCEL_MESSAGE } from '@qwen-code/acp-bridge/bridgeErrors';
import {
GoalTerminalEvent,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] All imports from @qwen-code/qwen-code-core collapsed into a single import type block

The base branch had two separate import blocks: import type { ... } for pure types and import { ... } for runtime values (functions, enums, constants). This PR merged them into one import type { ... } block, which converts ~60 runtime symbols — including createDebugLogger, ApprovalMode, ToolConfirmationOutcome, StreamEventType, Storage, DiscoveredMCPTool, firePreToolUseHook, firePostToolUseHook, convertToFunctionResponse, readManyFiles, clampInlineMediaPart, and dozens more — into type-only imports.

TypeScript erases import type at compile time, so every value usage produces TS1361: 'X' cannot be used as a value because it was imported using 'import type'. This causes 454 compilation errors in packages/cli and prevents the package from building.

Additionally, GoalTerminalEvent appears twice: once as a regular member (this line) and once as type GoalTerminalEvent (line 90), producing TS2300: Duplicate identifier.

Suggested change
GoalTerminalEvent,
import type {
Content,
FunctionCall,
GenerateContentResponseUsageMetadata,
Part,
} from '@google/genai';
import type {
Config,
GeminiChat,
ToolCallConfirmationDetails,
ToolResult,
ChatRecord,
AgentEventEmitter,
StopHookOutput,
HookExecutionRequest,
HookExecutionResponse,
MessageBus,
StreamEvent,
ChatCompressionInfo,
AutoModeDecision,
AutoModeOutcome,
GoalTerminalEvent,
ConversationFinishedEvent,
PromptSuggestionEvent,
UserPromptEvent,
} from '@qwen-code/qwen-code-core';
import {
AuthType,
ApprovalMode,
CompressionStatus,
convertToFunctionResponse,
createDebugLogger,
DiscoveredMCPTool,
StreamEventType,
ToolConfirmationOutcome,
generatePromptSuggestion,
logPromptSuggestion,
logToolCall,
logUserPrompt,
getErrorStatus,
readManyFiles,
clampInlineMediaPart,
Storage,
ToolNames,
fireNotificationHook,
firePermissionRequestHook,
firePreToolUseHook,
firePostToolUseHook,
firePostToolUseFailureHook,
injectPermissionRulesIfMissing,
NotificationType,
persistPermissionOutcome,
createHookOutput,
generateToolUseId,
MessageBusType,
getPlanModeSystemReminder,
getArenaSystemReminder,
getStartupContextLength,
isSystemReminderContent,
evaluatePermissionFlow,
needsConfirmation,
isPlanModeBlocked,
abortGoalForStopHookCap,
formatStopHookBlockingCapWarning,
applyAutoModeDecision,
evaluateAutoMode,
getAutoModePermissionDeniedReason,
isApproveOutcome,
MAX_TRANSCRIPT_MESSAGES,
formatDenialStateLog,
recordAllow,
recordFallbackApprove,
shouldFallback,
shouldFirePermissionDeniedForAutoMode,
shouldRunAutoModeForCall,
extractDaemonTraceContext,
withInteractionSpan,
startToolSpan,
endToolSpan,
runInToolSpanContext,
startToolExecutionSpan,
endToolExecutionSpan,
logConversationFinishedEvent,
acquireSleepInhibitor,
setGoalTerminalObserver,
} from '@qwen-code/qwen-code-core';

— qwen3.7-plus via Qwen Code /review

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

Labels

type/feature-request New feature or enhancement request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

任务会卡住

5 participants