Commit graph

893 commits

Author SHA1 Message Date
Dragon
4efe2c77bc
docs: add vertex-ai auth, missing commands, and qc-helper index entries (#5727)
* docs: add CLI subcommands section with qwen sessions list

- Add section 5 to commands.md for CLI-level subcommands
- Document qwen sessions list with --json and --limit flags
- Include output format, examples, and usage patterns

The sessions list command was added in commit 14e6ae8c2 but not documented.

* docs: add vertex-ai auth, missing commands, and qc-helper index entries

Audit docs/ against current codebase and fix high-impact drift:

- Add vertex-ai to auth.md, model-providers.md, and tos-privacy.md supported
  auth type tables (was missing from all three)
- Add missing slash commands to commands.md: /cd, /import-config, /workflows
- Add /doctor subcommands (memory, cpu-profile, rollback) and /extensions
  subcommands (list, manage, explore, install) to commands.md
- Add undocumented altNames: /clear→/reset,/new; /stats→/usage;
  /auth→/connect,/login; /resume→/continue; /compress→/summarize
- Add 8 missing feature entries to qc-helper SKILL.md topic index:
  code-review, followup-suggestions, tool-use-summaries, markdown-rendering,
  structured-output, dual-output, channels, tips
- Fix CLI binary name in contributing.md (qwen-code → qwen)

* docs: resolve review feedback on auth count, /workflows usage, GOOGLE_MODEL example

- auth.md: correct intro count from 'four' to 'three' methods (3 bullets + 3 Options; Vertex AI is a provider under API Key)
- commands.md: add '/workflows <runId>' to usage column to match argumentHint '[runId]'
- model-providers.md: add required GOOGLE_MODEL to the Vertex AI env example so it matches the prose and modelConfigUtils requirement

* docs: remove duplicate /workflows row introduced by main merge

main already documents /workflows (with the <runId> usage); the branch
merge kept both rows, leaving a duplicate in the Tool and Model
Management table. Drop the redundant row added by this PR.

* docs: resolve review feedback — /extensions explore source arg, CLI-name remnants

- commands.md: /extensions explore requires a <source> (exploreAction errors 'Unknown extensions source' without it)
- contributing.md: finish the qwen-code -> qwen CLI rename on the debug note (binary + .qwen config dir); repo-name references left intact

* docs: resolve review feedback — Vertex AI in tos-privacy, /doctor rollback clarity

- tos-privacy.md: propagate Vertex AI (which the header already counts as the 4th method) into the Data Collection list, FAQ Q1 and Q3, and add a '4. If you are using Vertex AI' section pointing to Google Cloud terms — fixes the four-vs-three internal inconsistency
- commands.md: clarify /doctor rollback rolls back the standalone CLI binary (standalone installs only) and disambiguate from /rewind's rollback alias (doctorCommand.ts gates on isStandalone)

* docs: resolve review feedback — clear semantics, doctor argHints, arrow spacing

- /clear: fix description to 'Clear conversation history and free up context' and drop the misleading '(shortcut: Ctrl+L)' grouping; Ctrl+L only clears the screen (clearScreen), it does not reset the session like /clear (clearCommand.ts)
- Ctrl/cmd+L keyboard row: clarify it clears the visible screen only, not 'Equivalent to /clear'
- /doctor memory and /doctor cpu-profile: surface the full argumentHints ([--sample] [--snapshot], [--duration <seconds>]) from doctorCommand.ts
- normalize section 1.4 arrow subcommands to spaced '→ ' style (approval-mode rows were the lone outliers)

* docs: resolve review feedback — enumerate /extensions explore sources

List the two valid sources (Gemini, ClaudeCode) from EXTENSION_EXPLORE_URL in extensionsCommand.ts so users can discover them without trial and error.

* docs: resolve review feedback — add extensions install security warning

/extensions install (extensionsCommand.ts) installs arbitrary git repos/paths with no confirmation prompt; add a warning that extensions run with full Qwen Code permissions and should only come from trusted sources.

* docs: resolve review feedback — add /stats subcommands, /auth aliases in 1.11

- commands.md: add /stats daily, /stats monthly, /stats export rows (registered in statsCommand.ts with day/month aliases and --format csv|json)
- section 1.11: note /auth's /connect and /login aliases (parallel to section 1.4)

* docs: resolve review feedback — /stats export full args, /summarize note

- /stats export: show the full argumentHint ([date|month] and [--output path]) from statsCommand.ts
- add a note disambiguating /summarize (alias of /compress, destructive) from /summary (project summary)

* docs: resolve review feedback — complete /arena /ide /directory /voice /mcp usage

Add the missing subcommands/arguments shown in the command sources:
- /arena: stop, select (arenaCommand.ts)
- /ide: enable, disable (ideCommand.ts)
- /directory: show (directoryCommand.tsx)
- /voice: hold, tap, off (voice-command.ts argumentHint)
- /mcp: nodesc, schema, auth, noauth (mcpCommand.ts argumentHint)

* docs: resolve review feedback — arena/stats aliases, trim /stats description

- /arena select: note alias 'choose' (arenaCommand.ts)
- /stats daily, /stats monthly: label day/month as aliases (statsCommand.ts)
- /stats: trim the description to a terse behavior-focused line (drop volatile tab names/keyboard shortcuts that belong in the dashboard help)

* docs: resolve review feedback — /copy args, /doctor memory --snapshot warning

- /copy: document language/latex/mermaid/index selection (copyCommand.ts argumentHint)
- add a warning that /doctor memory --snapshot writes a heap snapshot with sensitive data (matches doctorCommand.ts runtime warning)

* docs: resolve review feedback — mcp/approval-mode/copy accuracy, qwen privacy URL

- /mcp: drop deprecated auth/noauth (mcpCommand.ts argumentHint is now desc|nodesc|schema; auth/noauth are stubs)
- /approval-mode: drop nonexistent --project (mode is session-only), show actual invocations, add a safety warning for auto-edit/auto/yolo
- /copy: note N = Nth-last reply (copyCommand.ts)
- tos-privacy: unify Qwen Privacy Policy URL to qwen.ai/privacypolicy

* docs: resolve review feedback — import-config args + feature-gated commands note

- /import-config: show 'all' (default source) and enumerate --scope user|project (importConfigCommand.ts)
- add a note that /workflows, /lsp, /trust register only when their feature setting is enabled (BuiltinCommandLoader.ts gates them, default off)

* docs: fix feature-gating mechanisms + restore /stats tab names

- Correct the /workflows/lsp/trust note: actual gates are QWEN_CODE_ENABLE_WORKFLOWS=1 (env),
  --experimental-lsp (CLI flag), and security.folderTrust.enabled (setting) — the prior
  workflowsEnabled/lsp.enabled/folderTrust keys did not exist
- /stats: restore the Session/Activity/Efficiency tab names (dashboard contents are not
  documented elsewhere); keep volatile keyboard hints out per the earlier review

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-25 06:58:10 +00:00
顾盼
44b80da0db
feat(memory): confirm auto-generated skills before persisting (#5616)
* feat(memory): add memory.autoSkillConfirm setting schema

* feat(memory): add Config.getAutoSkillConfirmEnabled()

* feat(memory): wire memory.autoSkillConfirm through cli/acp/desktop settings

* feat(memory): add pending-skills staging helpers

* feat(memory): stage auto-skills for confirmation in runSkillReview

* feat(memory): pass autoSkillConfirm flag from client to skill review

* feat(memory): skill-review subscriptions + accept/reject pending APIs

* feat(cli): add skill-review dialog state to UI context

* feat(cli): add SkillReviewDialog component

* feat(cli): render SkillReviewDialog from DialogManager

* feat(cli): wire skill-review subscription and idle dialog routing

* feat(cli): show pending auto-skill review hint in footer

* feat(cli): add autoSkillConfirm toggle to /memory dialog

* docs(memory): document memory.autoSkillConfirm setting

* fix(cli): focus and Ctrl+C-close the skill-review dialog

* fix(memory): address review on auto-skill confirmation

- stage only newly-created skills, never agent-edited pre-existing ones, so
  Discard can't delete a skill the user already confirmed
- re-read pendingSkills after the await in resolvePendingSkill so concurrent
  Keep-all/Discard-all removes every entry, not just the last
- surface accept/reject fs failures (try/catch + log + .catch) instead of
  silently swallowing them
- remount SkillReviewDialog per task via key so its snapshot never goes stale
  across consecutive skill-review batches
- skip redundant skillReviewPending updates with a signature compare
- remove the unreachable openSkillReviewDialog action
- add debug logging to the pending-skills module
- ignore .qwen/pending-skills/ explicitly in .gitignore

* fix(memory): address round 2 review on auto-skill confirmation

- acceptPendingSkill: when the staged dir is gone, no-op only if the skill is
  already in the skills root; otherwise throw so resolvePendingSkill keeps it
  pending and logs, preventing silent data loss
- fall back to the agent's systemMessage for progress text when staging yields
  zero pending (a pre-existing-skill edit is still a durable change)
- log the no-task / no-target early returns in resolvePendingSkill
- replace internal tracker references in an AppContainer comment

* fix(memory): harden auto-skill confirmation for multi-batch and edge cases

- parseDescription: keep an empty description empty instead of spilling onto
  the next YAML line
- namespace staged dirs under the task id so a later same-named batch can't
  clobber a still-deferred earlier one
- track Esc-dismissed batches in a Set (not a single value) and only mark a
  batch dismissed on Esc, so a partially-failed Keep-all can reopen for the
  unresolved skills
- document the in-place updateRecord invariant the accept/reject race fix
  relies on
- add the missing license header to pending-skills.test.ts

* fix(memory): strip quoted descriptions; Ctrl+C defers skill-review dialog

- parseDescription: strip a matching pair of surrounding quotes so a
  `description: "..."` frontmatter value isn't rendered with literal quotes
- useDialogClose: Ctrl+C on the skill-review dialog now calls
  dismissSkillReviewDialog (records the batch as dismissed) instead of plain
  close, matching Esc — otherwise the idle effect immediately reopened it

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-25 00:47:16 +00:00
jinye
87ce3f47a3
feat(cli): Add skill usage stats (#5826)
* feat(cli): Add skill usage stats

Track live session skill invocations in daemon stats and expose /stats skills across interactive and non-interactive flows.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5826)

Add non-interactive skill stats tests for action rejection and prompt hook errors.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-25 00:10:55 +00:00
jinye
9254852211
feat(serve): Add daemon workspace voice and control APIs (#5765)
* feat(daemon): add setup-github route

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* feat(serve): add daemon workspace voice and control APIs

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5765)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address daemon voice review feedback (#5765)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5765)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address daemon voice review feedback (#5765)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): require auth for voice transcription

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): address voice persistence review feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5765)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: fix CI failure on PR #5765

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5765)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5765)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): address daemon voice review feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): ignore untrusted workspace proxy for setup-github

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): address daemon workspace review feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): address daemon voice review followups

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): address workspace voice review feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): address settings and git utility review feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(serve): align permission cwd expectation

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: address review feedback on settings logs and sdk types

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): Bound ACP workspace voice model input

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5765)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: fix CI failure on PR #5765

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5765)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5765)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5765)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-24 23:48:57 +00:00
jinye
76a57154b2
fix(core): add streaming inactivity timeout to the OpenAI pipeline (#5827)
* fix(core): add streaming inactivity timeout to the OpenAI pipeline

The OpenAI client `timeout` only bounds connect + first response. Once a
streaming request returns 200, inter-chunk inactivity is unbounded, so a
provider that accepts the request then streams nothing (observed with a
DashScope/Bailian endpoint returning 200 with no finish_reason) hangs
indefinitely — the only existing idle timer is telemetry-only and never aborts.

executeStream now wraps the raw chunk stream in an inactivity watchdog: if no
chunk arrives for streamIdleTimeoutMs (default 120s, configurable via
contentGenerator.streamIdleTimeoutMs; <= 0 disables), it aborts the per-request
controller (freeing the socket) and throws. A user AbortError is propagated when
the parent signal was cancelled; otherwise a synthetic ETIMEDOUT, which
classifyRetryError treats as a retryable transport error — identical to a real
socket read timeout — so the existing stream-transport retry recovers a
zero-chunk (first-byte) stall and surfaces a clear error after exhaustion. The
timer resets on every chunk (including thinking deltas), so active streams are
never interrupted.

Out of scope: surfacing a terminal turn_error to the UI on retry exhaustion
(separate change); the non-streaming path is already bounded by the SDK timeout.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(core): keep ETIMEDOUT code on stream inactivity timeout (bypass error handler)

Audit found the inactivity timeout could not auto-retry: the OpenAI error
handler detects code 'ETIMEDOUT' as a timeout and re-throws a generic Error
WITHOUT the code, so classifyRetryError no longer saw a retryable transport
error. (The original tests missed this because the mock error handler is a
pass-through, not the real EnhancedErrorHandler.)

Make the inactivity timeout a dedicated StreamInactivityTimeoutError that
processStreamWithLogging rethrows directly — the same bypass StreamContentError
already uses — so the ETIMEDOUT code survives to classifyRetryError and the
stream-transport retry recovers a stalled first-byte stream.

Adds a regression test with an error handler that faithfully replicates the
code-stripping, asserting the code survives and the handler is bypassed.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* test(core): fix fake-timer deadlock in inactivity timeout tests

Two tests awaited expect(consume).rejects before advancing the fake timers, so
the rejection could never arrive and the tests hung to timeout. Assign the
assertion promise, advance the timers, then await it.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* codex: address PR review feedback (#5827)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: fix PR 5827 CI lint failure

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5827)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5827)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5827)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-24 23:33:55 +00:00
tt-a1i
a4eab3d39c
fix(core): allow web_fetch JSON fallback (#5660) 2026-06-24 14:18:33 +00:00
jinye
0787543a94
perf(cli): Optimize serve daemon startup (#5785)
* perf(cli): optimize serve daemon startup

Slim the qwen serve startup path by deferring interactive UI, runtime, web-shell, and settings-heavy imports until after the listener is ready.

Add daemon startup timing, preheat status reporting, fast-path settings/env loading, and regression coverage for import boundaries and runtime directory behavior.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): fix Windows serve fast-path CI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): handle serve fast path runtime failures

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): cover serve startup edge cases

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): cover resolve-on-listen failure cleanup

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): satisfy bridge proxy lint

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(cli): note serve fast path parser sync

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): address serve fast path review feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): guard serve fast path option parity

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5785)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5785)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5785)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5785)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): align workspace permission trust fixture

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-24 17:50:31 +08:00
jinye
06549daa9b
fix(sdk): self-heal stale clientId on invalid_client_id prompts (#5797)
* fix(sdk): self-heal stale clientId on invalid_client_id prompts

After a daemon restart or session reload the daemon's in-memory client
registration is wiped, so a prompt sent with our now-unknown clientId is
rejected at admission with 400 invalid_client_id (PR #5784). That rejection
happens before the turn registers, so the prompt never ran and retrying cannot
double-execute.

DaemonSessionClient.prompt() now wraps the admission call (both the blocking and
non-blocking paths) in a self-heal: on invalid_client_id it re-registers via
resumeSession to obtain a fresh clientId and retries the admission exactly once.
A single-flight guard coalesces concurrent prompts so re-registration happens
once; any other error (and a second invalid_client_id) propagates.

Adds 6 tests covering both paths, the retry bound, the non-matching-error guard,
reattach-failure propagation, and concurrent single-flight.

Design: docs/superpowers/specs/2026-06-24-daemon-clientid-self-heal-design.md

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* test(integration): cover real-daemon invalid_client_id contract for self-heal

Adds an integration case to qwen-serve-routes.test.ts validating the three
real-daemon behaviors DaemonSessionClient's clientId self-heal depends on:
(1) an unregistered prompt clientId is rejected at admission with
400 invalid_client_id, (2) resume re-registers and mints a fresh clientId, and
(3) retrying admission with that clientId is accepted (202). Model-free: prompt
admission runs before any model call, so promptNonBlocking returns 202 on
acceptance without reaching the model.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(sdk): Adjust daemon browser bundle budget

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(sdk): Cover clientId self-heal review cases

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-24 13:59:59 +08:00
jinye
a234860a4a
fix(core): Align MCP OAuth guidance and docs (#5589)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* docs: Align docs with current CLI behavior

Update stale documentation and user-facing MCP OAuth guidance to match the current dialog-based flows, SDK permission semantics, current links, and Qwen OAuth status.

Also replace Ink internal imports with public Ink APIs for the shared text input so the workspace builds against Ink 7.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: fix BaseTextInput Ink import (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): surface MCP OAuth credential read failures

Fix SSE OAuth credential pre-check failures by reporting token storage read errors before connecting.

Update SDK coreTools docs and extension release link text from the follow-up review.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): harden MCP OAuth error handling

Handle stderr warning failures as best-effort and keep SSE 401 OAuth guidance when credential re-read fails.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): keep SSE OAuth pre-read best effort

Avoid blocking SSE MCP connections when the diagnostic credential pre-read fails, and cover BaseTextInput absolute-position edge cases.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): handle SSE OAuth validation errors

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): surface MCP OAuth recovery guidance

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): cover MCP OAuth retry paths

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): address OAuth guidance review

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-24 07:09:53 +08:00
tt-a1i
099b47edca
fix(core): require integer microcompaction keep count (#5652) 2026-06-24 07:05:31 +08:00
Dragon
f1ef9d32b9
docs: fix config/command/auth drift and surface the model-providers page (#5735)
* docs: fix config/command/auth drift and surface model-providers page

Audit docs/ against the current code and correct the highest-impact drift:

- settings.md: move the mis-filed experimental.emitToolUseSummaries row into a
  new experimental section (cron/agentTeam/artifact/emitToolUseSummaries) and
  add general.language/outputLanguage/dynamicCommandTranslation and
  output.showTimestamps.
- commands.md: document /cd, /history, /voice, /import-config and the
  /model --voice and /model <model-id> forms.
- auth.md + model-providers.md: convert all modelProviders examples to the v5
  { protocol, models } object shape, correct the /auth menu (Alibaba
  ModelStudio / Third-party Providers / Custom Provider), fix the default
  OpenAI model (qwen3.5-plus), document the vertex-ai auth type, mark envKey
  optional, and use kebab-case --openai-api-key/--openai-base-url flags.
- overview.md + quickstart.md: rewrite the stale first-run auth flow; fix typo.
- configuration/_meta.ts: surface the orphaned model-providers page in the nav.
- qc-helper SKILL.md: add the 8 missing feature pages to the doc index.

* docs: resolve review feedback — fix provider-name and ModelStudio casing

Align docs with the code's provider labels and UI strings:
- Z.ai -> Z.AI (presets/zai.ts: label 'Z.AI API Key')
- iDeaLab -> Idealab (presets/idealab.ts: label 'Idealab API Key')
- 'Model Studio' -> 'ModelStudio' (UI flowTitle 'Alibaba ModelStudio'; no 'Model Studio' in code)

Applied across auth.md, overview.md, quickstart.md. Used --no-verify to avoid
lint-staged reformatting pre-existing, unrelated (non-CI-enforced) table padding
in auth.md; the five changed lines are individually prettier-clean.

* docs: resolve review feedback — /history subcommands, language type, jsonc fence

- commands.md: add missing '/history expand-on-resume' subcommand (historyCommand.ts registers collapse-on-resume, expand-on-resume, expand-now)
- settings.md: general.language Type string -> enum (settingsSchema.ts declares type: 'enum')
- model-providers.md: relabel the Example fence json -> jsonc (it contains // comments and two JSON docs)

--no-verify: avoids lint-staged re-padding pre-existing, unrelated (non-CI-enforced)
table columns; the three changed lines are content-only.
2026-06-24 06:06:01 +08:00
jinye
c9b5c99e44
feat(serve): Add remote LSP status route (#5741) 2026-06-23 18:13:29 +08:00
Shaojin Wen
663967e10c
revert(core): revert Protocol enum & model-identity decoupling (#5089) (#5745)
* revert(core): revert Protocol enum & model-identity decoupling (#5089)

Reverts the structural changes from #5089 back to the pre-#5089 shape:
AuthType stays a fixed enum (not `string`), the Protocol enum is removed,
modelProviders is `Record<authType, ModelConfig[]>` again (not
`{ protocol, models }`), and createContentGenerator dispatches on authType.
The v4->v5 settings migration is removed and SETTINGS_VERSION reverts to 4.

Features merged on top of #5089 are kept and re-adapted to the old
enum+array structure (not reverted):
- #5632 fastOnly/voiceOnly model flags (test fixtures reshaped to arrays)
- #5638 workspace provider defaults (readProviderModels already tolerates
  both shapes; test fixtures reshaped to arrays)
- #5729 active-runtime-model listing (pre-#5089 getAllConfiguredModels
  already enumerates Object.values(AuthType), so the runtime model is
  included natively)
- #5728 ACP set_config_option deterministic provider fixture (reshaped to
  array; the flake fix is preserved)

KNOWN DOWNGRADE CAVEAT: settings already migrated to $version:5 (shipped in
v0.19.0) retain the v5 `{ protocol, models }` modelProviders shape, which
the reverted ModelRegistry consumes as an array. Such settings will throw
on load until re-configured. A v5->v4 downgrade guard/migration is a
separate follow-up if backward compatibility for migrated users is needed.

* feat(cli): add v5->v4 settings downgrade migration for #5089 revert

After reverting #5089, settings already migrated to $version:5 (shipped in
v0.19.0) carry a modelProviders `{ protocol, models }` shape that the
reverted v4 readers consume as arrays, throwing "models is not iterable"
on load. This adds the inverse migration so those configs auto-converge to
v4 on load (the user-facing "automatically migrate $version:5 to 4").

- V5ToV4Migration: unwraps each modelProviders `{ protocol, models }` back
  to its `models` array, drops the now-implicit protocol (warning only when
  the explicit protocol differs from the key-derived one), and resets
  $version to 4.
- DOWNGRADE_MIGRATIONS keeps the downgrade out of the ascending forward
  ALL_MIGRATIONS chain (preserving its invariants); runMigrations and
  needsMigration consider both via a combined convergence set.
- needsMigration now gates on `=== SETTINGS_VERSION` instead of `>=`, so a
  newer-but-handled version (v5) is reported as needing migration while a
  genuinely unknown newer version (v6+) is still left untouched.

Covered by unit tests for the migration, the framework wiring, and an
end-to-end loadSettings downgrade-on-load test.

* fix(test): align integration settings-version constant with reverted v4

The integration suites hard-coded CURRENT_SETTINGS_VERSION = 5 (introduced
by #5676), which mismatched the reverted SETTINGS_VERSION = 4 and failed the
migration assertions ($version now writes 4, not 5). Revert the constant to
4 in both settings-migration and qwen-config-dir integration tests.

Verified: QWEN_SANDBOX=false vitest run --root ./integration-tests
cli/settings-migration.test.ts cli/qwen-config-dir.test.ts → 21 passed.

* fix: harden v5-era settings handling on the #5089 revert path

Addresses /qreview feedback on the revert:

- vscode findOpenaiModels: restore read-side tolerance for the V5
  { protocol, models } shape. The extension reads/writes settings.json
  without running the CLI v5->v4 migration, so a not-yet-downgraded
  $version:5 file would otherwise return [] and silently drop existing
  OpenAI models on the next write. (Critical)
- modelRegistry.registerAuthTypeModels: guard against a non-array provider
  value (skip + warn) instead of throwing an opaque "models is not
  iterable" — covers hand-edited or unmigrated files the downgrade misses.
- needsMigration JSDoc: update the stale ">= SETTINGS_VERSION" wording to
  match the "=== SETTINGS_VERSION, else fall through" logic the downgrade
  path depends on.
- settings.test.ts: also assert the v5->v4 downgrade is persisted to disk
  (.tmp write-back), not just the in-memory merged result.

Adds tests for the registry guard and the vscode V5 read tolerance.

* fix(core): break contentGenerator import cycle + cover reverted error paths

Addresses /review suggestions on the revert:

- contentGenerator: import PROVIDER_SOURCED_FIELDS from constants.js (where
  it is actually defined) instead of modelsConfig.js, breaking the runtime
  import cycle contentGenerator -> modelsConfig -> contentGenerator.
  constants.js only references contentGenerator at the type level, which is
  erased at runtime, so no cycle remains.
- contentGenerator.test: add coverage for the two authType error paths the
  revert restored (missing authType -> "must have an authType"; unknown
  authType -> "Unsupported authType"), which #5089's protocol-based tests
  had replaced. Neither was covered before.

The acpAgent z.nativeEnum(AuthType).parse(methodId) suggestion is left as-is:
that line is byte-identical to pre-#5089, so it is pre-existing behavior the
revert faithfully restores rather than a regression of this PR.
2026-06-23 16:32:38 +08:00
Shaojin Wen
c1eef15d4b
feat(cli): match MCP resource completions by name and discover servers (#5733)
`@server:` completion previously matched only the resource URI,
case-sensitively, and there was no way to reach a server without typing
its full name first.

- Match the partial after the colon case-insensitively against the
  resource's friendly name/title (`title || name`) as well as the URI,
  ranked URI-prefix > name-prefix > URI-substring > name-substring, and
  surface the name as the suggestion description.
- Before the colon, suggest configured MCP servers that expose resources
  and whose name prefixes the input, prepended to the file results (never
  hiding files). Selecting one expands to `@server:` and drills into the
  resource list (reuses the directory `isDirectory` continuation).

Refs #5601, follows #5635.
2026-06-23 11:25:51 +08:00
Yan Shen
9b20c47f46
feat(core): respect configurable agent ignore files (#4653)
* Respect agent ignore conventions through configurable filtering

Constraint: Issue #1746 requests .agentignore/.aiignore compatibility and maintainer feedback asks for a custom ignore-file configuration path.
Rejected: Hardcode only .agentignore/.aiignore | would not satisfy the maintainer's configurable-ignore direction.
Confidence: high
Scope-risk: moderate
Directive: Keep .qwenignore always included when respectQwenIgnore is enabled; route extra filenames through customIgnoreFiles.
Tested: Core targeted vitest suite; CLI targeted vitest plus config integration; npm run build && npm run typecheck; targeted eslint; git diff --check.
Not-tested: Full repository test suite and model-driven end-to-end CLI integration tests.

* fix(cli): keep custom ignore settings type-safe and honest

The schema default otherwise narrows customIgnoreFiles to the built-in tuple and the UI text implies additive behavior that the implementation does not provide.

Constraint: Address wenshao's two review comments without changing replacement semantics.

Rejected: Merge user values with defaults | Larger behavior change was not requested for this follow-up.

Confidence: high

Scope-risk: narrow

Directive: Preserve replacement semantics unless a future change intentionally updates config merging and docs together.

Tested: cd packages/cli && npx vitest run src/config/config.test.ts src/config/settingsSchema.test.ts; npm run build; npm run typecheck; npm run lint; git diff --check

Not-tested: Full integration suite.

* fix(core): keep custom ignore settings consistent

Ensure review-sensitive file discovery paths, grep ignore resolution, and user-facing ignore names preserve the configured custom ignore behavior instead of falling back to defaults or search-directory-local files.

Constraint: PR #4653 review feedback requires customIgnoreFiles to behave consistently across secondary discovery and grep paths.

Rejected: Merge custom ignore files with defaults | The current PR documents replacement semantics and prior review feedback accepted that contract.

Confidence: high

Scope-risk: narrow

Directive: Keep .qwenignore always included, but treat customIgnoreFiles as the replacement list for additional AI ignore files.

Tested: packages/core targeted vitest for qwenIgnoreParser, fileDiscoveryService, ripGrep, config, read-file; packages/cli config vitest; targeted workspaceFileSystem custom-ignore vitest; Prettier check; ESLint on touched files; git diff --check.

Not-tested: Full workspace typecheck/build because current branch fails before this change on environmentContext.test.ts syntax and unrelated CLI type errors; full workspaceFileSystem suite on Windows because existing symlink tests fail with EPERM.

* test(core): cover subagent custom ignore inheritance

Keep the in-process backend tests aligned with the new per-agent file filtering contract so CI catches missing custom-ignore propagation.

Constraint: PR review fixes require subagents to inherit parent custom ignore settings.
Confidence: high
Scope-risk: narrow
Tested: npx vitest run src/agents/backends/InProcessBackend.test.ts
Tested: npx vitest run src/utils/qwenIgnoreParser.test.ts src/services/fileDiscoveryService.test.ts src/tools/ripGrep.test.ts src/config/config.test.ts src/tools/read-file.test.ts src/agents/backends/InProcessBackend.test.ts src/tools/agent/agent.test.ts
Tested: npx vitest run src/serve/fs/workspaceFileSystem.test.ts -t "uses configured custom ignore files"
Tested: npx prettier --check packages/core/src/agents/backends/InProcessBackend.test.ts
Tested: npx eslint packages/core/src/agents/backends/InProcessBackend.test.ts
Tested: git diff --check
Not-tested: full npm run typecheck remains blocked by existing branch-wide TypeScript errors outside this test change

* test(core): fix subagent custom ignore test typing

Keep the custom-ignore regression test type-checkable under the repo build, where createMockConfig is intentionally cast to never.

Constraint: CI runs package build during dependency installation and type-checks test files.
Confidence: high
Scope-risk: narrow
Tested: npx vitest run src/agents/backends/InProcessBackend.test.ts
Tested: npx prettier --check packages/core/src/agents/backends/InProcessBackend.test.ts
Tested: npx eslint packages/core/src/agents/backends/InProcessBackend.test.ts
Tested: git diff --check
Not-tested: npm run build --workspace=packages/core is still blocked locally by src/utils/environmentContext.test.ts(599,1): error TS1005: '}' expected

* test(core): align notebook ignore message expectation

Keep notebook ignore validation tests in sync with the comma-separated ignore file display used by the review fix.

Constraint: PR review fixes changed ignore file display text from slash-separated to comma-separated.
Confidence: high
Scope-risk: narrow
Tested: npx vitest run src/tools/notebook-edit.test.ts
Tested: npx vitest run src/utils/qwenIgnoreParser.test.ts src/services/fileDiscoveryService.test.ts src/tools/ripGrep.test.ts src/tools/read-file.test.ts src/tools/notebook-edit.test.ts src/config/config.test.ts src/agents/backends/InProcessBackend.test.ts src/tools/agent/agent.test.ts
Tested: npx prettier --check packages/core/src/tools/notebook-edit.test.ts
Tested: npx eslint packages/core/src/tools/notebook-edit.test.ts
Tested: git diff --check
Not-tested: full npm run build --workspace=packages/core remains blocked locally by src/utils/environmentContext.test.ts(599,1): error TS1005: '}' expected

* fix(core): keep ripgrep ignore roots canonical

Constraint: PR #4653 review 4453010590 requested absolute ignore-root fallback behavior.

Rejected: Broader customIgnoreFiles semantic changes | outside the review scope and replacement semantics stay unchanged.

Confidence: high

Scope-risk: narrow

Directive: Keep customIgnoreFiles as replacement for compatibility defaults while always including .qwenignore.

Tested: cd packages/core && npx vitest run src/tools/ripGrep.test.ts; npx prettier --check src/tools/ripGrep.ts src/tools/ripGrep.test.ts; git diff --check

Not-tested: npm run typecheck --workspace=packages/core fails on existing src/utils/environmentContext.test.ts parse error.

* test(core): align ripgrep ignore path expectation

Constraint: macOS canonicalizes temporary paths through /private/var after process.chdir.

Rejected: Changing ripgrep ignore-root behavior | implementation already uses path.resolve correctly.

Confidence: high

Scope-risk: narrow

Directive: Keep the regression test tied to path.resolve behavior rather than raw temp-dir spelling.

Tested: cd packages/core && npx vitest run src/tools/ripGrep.test.ts; npx prettier --check src/tools/ripGrep.test.ts src/tools/ripGrep.ts; git diff --check

Not-tested: Full CI rerun is remote-only after push.

* fix(core): make custom ignore feedback actionable

Review feedback for PR #4653 showed users could not tell which ignore file blocked a path, and worktree isolation lacked coverage for inherited custom ignore files. This also closes the existing environmentContext.test describe block so build can progress to the current unrelated converter type blocker.

Constraint: PR #4653 keeps replacement semantics for customIgnoreFiles and always includes .qwenignore.

Rejected: Exposing matching pattern text | merged ignore and negation semantics can make pattern-level attribution misleading.

Confidence: high

Scope-risk: moderate

Directive: Keep customIgnoreFiles as replacement semantics unless the config contract changes deliberately.

Tested: core targeted Vitest suite; ripGrep regression test; cli settingsSchema test; Prettier check.

Not-tested: npm run build && npm run typecheck blocked by existing FinishReason type errors in converter.ts.

* fix(core): isolate qwen ignore sources

* test(cli): mock qwen ignore defaults in acp test

* test(scripts): make dev launcher test path portable

* fix(core): prevent ripgrep ignore negation bypass

* fix(core): post-filter ripgrep qwenignore matches

* fix(core): preserve ignore negations in grep

- Preserve non-.qwenignore negation semantics for grep searches

- Skip workspace-external ignore-file discovery

- Add coverage for ignore diagnostics and addSource behavior

* test(core): update yaml nested parser expectations

* chore: remove unrelated formatting churn
2026-06-23 10:44:37 +08:00
yao
0c5221ccaf
refactor(core): extract Protocol enum and decouple model identity from auth type (#5089)
* refactor(core): extract Protocol enum and decouple model identity from auth type

- AuthType: enum -> string (custom provider IDs supported)
- New Protocol enum: OPENAI, QWEN_OAUTH, GEMINI, ANTHROPIC
- ContentGeneratorConfig: add `protocol` field for SDK routing
- ProviderConfig: { protocol, models[], baseUrl?, envKey? }
- ModelProvidersConfig: Record<string, ProviderConfig>
- createContentGenerator: dispatch by `protocol` not `authType`
- v4->v5 migration: authType maps to protocol, models wrapped in ProviderConfig

* fix(test): packages/core/src/core/contentGenerator.test.ts

* fix(test): adapt tests to ProviderConfig modelProviders structure

* fix(test): adapt remaining test files to ProviderConfig and Protocol enum

* fix(test): add Protocol mock and update ProviderConfig assertion

* feat(config): add v4-to-v5 settings migration for modelProviders ProviderConfig format

* fix(vscode): write modelProviders in V5 ProviderConfig format

* fix(core): populate protocol on ContentGeneratorConfig from ModelRegistry

* fix(cli): handle ProviderConfig format in auth and ACP model lookup

* fix(providers): derive protocol from authType on first-time install

* test(core): add coverage for protocol dispatch in createContentGenerator

* fix(core): guard AUTH_ENV_MAPPINGS access with Object.hasOwn

* fix(cli): use bracket notation for ProviderConfig.models access in acpAgent

* chore(vscode): regenerate settings schema for v5

* fix(core): derive protocol from authType in resolveModelConfig fallback path

* fix: stale protocol on provider removal, ProviderConfig v4/v5 compat across all callers

* fix(cli): update modelProviders description in settingsSchema source and regenerate schema

* fix: resolve CI failures — InProcessBackend mock missing Protocol export

* fix(core,cli): address review feedback — protocol fallback, prototype safety, test coverage

- Add authTypeToProtocol fallback in ModelsConfig.getGenerationConfig for env-var users without
modelProviders
- Guard AUTH_TYPE_TO_PROTOCOL and PROVIDER_KEY_TO_PROTOCOL with Object.hasOwn to prevent prototype
chain pollution
- Use string literals in AUTH_TYPE_TO_PROTOCOL to break circular dependency with contentGenerator
- Add test coverage for getProtocolForAuthType, v4 array-format compat, and protocol assertions

* fix(core,cli): protocol lookup correctness and type safety

* fix(test): add getProtocol mock to subagent-manager test for ModelsConfig compat

* fix(test): add getProtocol mock to InProcessBackend test for ModelsConfig compat

* fix: resolve merge conflicts and adapt to v5 ProviderConfig format

  - Fix import statement format in server.ts
  - Update test files to use v5 ProviderConfig format (object instead of array)
  - Replace AuthType with Protocol enum in provider configs
  - Adapt modelConfigUtils.test.ts to v5 format

* fix: support custom authType in /model dialog

  - Add getAuthTypes() method to ModelRegistry
  - Update getAllConfiguredModels to use modelRegistry.getAuthTypes()
  - Fix ModelDialog to show custom authTypes alongside built-in ones

* fix(core,cli): address review feedback — Protocol type safety and v5 test format

  - Fix protocol field types from AuthType to Protocol in serve/types.ts
  - Fix protocol state type in useProviderSetupFlow.ts
  - Update getDefaultBaseUrlForProtocol parameter type to Protocol
  - Update install.test.ts to use v5 ProviderConfig format

* fix(cli): accept custom authType in parseAcpModelOption and authenticate

  - Remove z.nativeEnum(AuthType) validation since AuthType is now string
  - Support custom provider authType like 'qwen3.7-idealab' in /model command
  - Remove unused zod imports

* fix(test): adapt config.test.ts to v5 ProviderConfig format after merge

  Fix 3 v4 array format usages in config.test.ts that were introduced
  by merging main, converting them to v5 { protocol, models } format.

* fix(test): update acpModelUtils test and JSDoc to match new parser behavior

  - Update test: any non-empty token in trailing parens is now treated as authType
  - Add test: empty parens fallback returns whole input as modelId
  - Update JSDoc: remove "valid AuthType" since authType is now an arbitrary string

  No logic changes — test and docs only.
2026-06-22 23:35:02 +08:00
tt-a1i
3098717525
docs(mcp): correct mcp add scope default (#5593) 2026-06-22 22:15:17 +08:00
jinye
a8863c203c
refactor(cli): Finish serve kebab-case filenames (#5604) 2026-06-22 21:15:40 +08:00
Shaojin Wen
f29f210201
feat(cli): browse MCP server resources in the /mcp dialog (#5635)
* feat(cli): browse MCP server resources in the /mcp dialog

The @server:uri resource reference (read + inject) shipped in #5544, but
the /mcp management dialog only surfaced a numeric "Resources: N" count —
there was no way to list a server's resource URIs, so a user who didn't
already know a URI couldn't discover what to reference.

Mirror the existing tools flow: add a "View resources" action on the
server detail step that opens a resource list, plus a detail view showing
each resource's URI, name, MIME type, size and description, along with the
exact @server:uri reference to paste into a message. Gated on
resourceCount > 0 (so untrusted folders, where MCP is never discovered,
show nothing). Adds en/zh/zh-TW strings, unit tests for both new steps,
and updates the MCP docs.

* fix(cli): wire MCP resource browser into the extensions manager + dedupe ref builder

Addresses review on #5635:

- [Critical] ServerDetailStep is shared with the extensions manager
  (McpServerActionsView), which doesn't pass onViewResources — so the new
  "View resources" action rendered there as a dead no-op. Gate the action on
  the optional handler being present (and add it to the useMemo deps), and
  implement the resource subview in McpServerActionsView so extension MCP
  servers get the same browser as the /mcp dialog.

- [Suggestion] Add a canonical buildMcpResourceRef() next to
  matchMcpServerPrefix() (its exact inverse) and use it for the @server:uri
  hint and the @server: autocomplete value, so the format has one source of
  truth; add a round-trip test.

- [Suggestion] Cover previously-untested paths: ResourceListStep scroll
  window (15 items: row windowing + ↑/↓ + count), ResourceDetailStep size:0
  and showName dedup, and ServerDetailStep action gating (incl. the
  dead-action regression).
2026-06-22 13:11:07 +00:00
易良
580a72410f
test(integration): run no-AK smoke tests on PRs (#5607)
* test(integration): run no-AK smoke tests on PRs

* test(integration): isolate qwen serve routes auth
2026-06-22 19:41:32 +08:00
kkhomej33-netizen
75fc0a5c18
feat(extensions): support archive install sources (#4909)
* feat(extensions): support archive install sources

* fix(core): harden extension archive installs
2026-06-22 13:36:13 +08:00
jinye
b4705b2534
refactor(cli): rename serve files to kebab-case (#5592)
Rename the PR1 serve and daemon adapter files from issue #5576 to kebab-case and update current imports, tests, comments, and developer docs to match.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-22 13:13:07 +08:00
Shaojin Wen
e5c01aa353
feat(mcp): support MCP resources and reliably surface prompts (#5544)
* feat(mcp): support MCP resources and reliably surface prompts

Prompts were silently hidden for MCP servers that implement `prompts/list`
but under-declare the `prompts` capability in their initialize response.
Drop the capability gate in `listMcpPrompts` (and apply the same leniency to
resources): always attempt the list call and swallow `Method not found`, so
those prompts now appear as `/` slash commands like in other clients.

Add first-class MCP resource support:

- core: `listMcpResources` / `discoverResources`, a `DiscoveredMCPResource`
  type, a `ResourceRegistry`, and `Config.getResourceRegistry()`. Discovery
  is wired into the standalone `McpClient.discover()` path and the
  connection-pool path (snapshot -> `SessionMcpView.applyResources` -> the
  session's registry, with a `resourcesChanged` event on reconnect). A
  resource-only server now counts as a successful discovery.
- cli: the `/mcp` dialog shows per-server Resources (and Prompts) counts.
- cli: `@server:uri` reads an MCP resource and injects its contents into the
  message (text inline, blobs as inlineData); `@server:` autocompletes the
  server's resource URIs. The `server` prefix must match a configured MCP
  server, so existing `@path` file references are unaffected.

Docs updated; unit tests added across core and cli.

* fix(mcp): reload commands on discovery + harden resource ref parsing

Address review feedback and complete the prompt UX:

- Slash commands now rebuild when an MCP server finishes connecting.
  Discovery is progressive (runs after the UI is interactive), so prompts
  a server exposes via prompts/list were registered too late to appear in
  the `/` menu — the `/mcp` dialog showed the count while the slash menu
  stayed empty. A debounced MCP-status listener now reloads the command
  tree on connect. Verified end-to-end in a real TUI against a mock server
  that under-declares the prompts capability: `/greet` now lists.
- `isMethodNotFound` keys off the JSON-RPC `-32601` code rather than the
  server-supplied message text (which may be localized or worded
  differently); applied to both `listMcpPrompts` and `listMcpResources`.
- `useAtCompletion` uses `Object.hasOwn` so `@__proto__:` / `@constructor:`
  and other inherited keys are not mistaken for configured servers.

* fix(mcp): lenient resource read to match discovery + review polish

- `McpClient.readResource` no longer prechecks `getServerCapabilities()
  ?.resources`. This PR is the first caller to make the read path
  reachable, and the strict precheck meant a server that answers
  `resources/read` but under-declares the `resources` capability — exactly
  the servers the lenient `listMcpResources` discovery targets — would get
  its resources discovered, listed in `/mcp`, and autocompleted, yet fail
  every `@server:uri` with a misleading "does not support resources". The
  read now matches discovery; a server that truly lacks resources answers
  `-32601`, surfaced as the existing error card. Added a regression test.
- `@server:uri` success cards now report what was injected ("Injected N
  chars" / "N attachments") or "(no readable content)" when a read yields
  no text/blob parts, so a partially-empty multi-ref read isn't hidden.
- `useAtCompletion` resource filter reduced to a single `includes` match
  (`startsWith` was subsumed; empty partial matches all via `includes('')`);
  the overstated "prefers prefix" comment is corrected.
- Tests: mixed `@file` + `@server:uri` injection (both parts + both cards),
  empty-content card, and the pool restart fan-out now asserts
  `applyResources` alongside `applyTools`.

* perf+security(mcp): parallelize discovery/reads, cap & frame resources

Review round 3 fold-ins:

- Discovery now runs `listMcpPrompts` / `listMcpResources` / `discoverTools`
  concurrently in `discoverAndReturn` (independent reads; the SDK client
  multiplexes by JSON-RPC id), saving per-server round-trips at startup.
- `@server:uri` resource reads run in parallel (`Promise.allSettled`) instead
  of serially, matching how the file path batches via `readManyFiles`; order
  is preserved so cards/labels line up with refs.
- Resource injection is now capped and framed: text is bounded by
  `MAX_MCP_RESOURCE_TEXT_CHARS` (100k) and oversized blobs are skipped, so a
  misbehaving/hostile server can't overflow the context window or OOM; the
  content is fenced with `--- Content from MCP resource <label> ---` /
  `--- End ---` delimiters so the model can separate untrusted server output
  from the user's prompt. The success card reports truncation.
- File-read error path now merges `resourceLabels` into `filesRead` /
  recording, so a resource read that succeeded before a file read failed is
  not dropped from the audit trail.
- `isMethodNotFound` (JSON-RPC -32601) now also covers `discoverTools` and
  `invokeMcpPrompt`, replacing the remaining message-substring checks.
- `PoolEntry.markActive` `initialResources` is now required (no `= []`
  default), removing a footgun where an omitted arg would wipe a server's
  resources via `applyResources([])`.
- `useAtCompletion` resource suggestions rank prefix matches above mid-string
  matches.
- `'Resources:'` added to the remaining 6 locales (ca, de, fr, ja, pt, ru)
  for parity with `'Prompts:'`.

Tests: attribution framing, text truncation, completion prefix ranking.

* test(mcp): cover resource registration in discover() and resource-only discovery

Closes the two coverage gaps flagged in review: assert discover() registers
discovered resources into the Config ResourceRegistry, and that a resource-only
server (no tools/prompts) is a successful discovery rather than throwing.

* fix(mcp): idempotent resource re-discovery + cumulative blob cap

Review round 4 (all Suggestions):
- discover() now clears a server's resources (removeResourcesByServer) before
  re-registering, so reconnect / incremental re-discovery is idempotent and a
  resource the server dropped doesn't linger in the registry (matches the
  pool path's SessionMcpView.applyResources).
- @server:uri injection now caps CUMULATIVE blob size per resource, not just
  each blob, so many sub-limit blobs in one response can't inject unbounded
  data. Added a test for the oversized-blob skip + card.
- Documented that file/resource content parts are grouped by type (model
  correlates by delimiter labels, not position).

* test(mcp): cover the MCP-status command reload (prompts surfacing in /)

Adds the missing coverage for the discovery-driven reload: a CONNECTED status
fires the listener and rebuilds the command tree (so progressively-discovered
MCP prompts appear as / commands), and a non-CONNECTED status does not.

* fix(mcp): don't wipe resources when resources/list transiently fails

- discover() only clears + replaces a server's resources when listMcpResources
  returns a non-empty set. Because that helper swallows all errors (including
  transient network failures) and returns [], an unconditional clear-then-
  register would silently purge a server's resources on a transient list
  failure while tools/prompts succeed. Guarding on length>0 keeps the existing
  set on failure; a real partial drop still re-registers the fresh set.
- Resource success card now shows '(truncated)' for capped/skipped blobs too,
  not just text. Added a cumulative-blob-cap test (two sub-limit blobs whose
  sum exceeds the cap).

* fix(mcp): guard pool applyResources against transient-failure wipe too

The non-pool discover() guard (resources.length > 0) left the pool path
exposed: on a restart, doRestart -> discoverAndReturn swallows a transient
resources/list failure to [], and applyResources([]) then wiped the session's
resources. applyResources is now a no-op on an empty snapshot (mirrors the
discover() guard; applyTools/applyPrompts keep their pre-existing clear-on-empty
behavior, out of scope). Added tests: applyResources([]) does not clear, and
discover() with an empty resource list does not call removeResourcesByServer.

* fix(mcp): preserve pool resource snapshot on transient restart failure

The applyResources([]) no-op only protected already-attached sessions; doRestart
still overwrote the pool entry's resourcesSnapshot with [] when the restart's
resources/list transiently failed, so any session attaching AFTER the restart
got zero resources. doRestart now only updates resourcesSnapshot when the
re-read is non-empty, preserving it for new and existing subscribers alike.
Tests: applyResources([]) preserves a pre-populated set; a restart whose
resources/list comes back empty still serves the prior resource to a new
session.

* fix(mcp): trust-gate resource completion, colon server names, narrower method-not-found

- useAtCompletion no longer surfaces resource URIs in an untrusted folder
  (the read path is already blocked there); avoids leaking resource existence.
- parseMcpResourceRef / getMcpResourceSuggestions match the LONGEST configured
  server name as a '<name>:' prefix instead of splitting on the first colon,
  so a server whose name contains ':' (a valid settings.json key) resolves.
- isMethodNotFound's message fallback is back to the case-sensitive exact
  'Method not found' substring (the -32601 code is the primary check), not a
  broad /method not found/i that would swallow unrelated errors.
Tests: @my:server:uri resolution, untrusted-folder completion.

* refactor(mcp): extract shared longest-prefix server matcher + doc/test fixes

- Extract matchMcpServerPrefix (new mcpResourceRef.ts) and use it from both
  parseMcpResourceRef (injection) and getMcpResourceSuggestions (completion),
  removing the duplicated longest-prefix logic and its drift risk.
- Update parseMcpResourceRef JSDoc to describe longest-prefix matching.
- Tests: shared-helper unit tests; the @my:server colon test now configures
  both 'my' and 'my:server' to exercise disambiguation; a colon completion
  test; isMethodNotFound message-casing tests (exact 'Method not found'
  swallowed, 'method not found handler' not swallowed).
2026-06-21 19:04:52 +08:00
Thibault Jaigu
a6e206c887
feat(core): add Requesty provider (#5478)
* feat(core): add Requesty provider

Requesty (https://requesty.ai) is an OpenAI-compatible model gateway that uses
the same provider/model identifier format as OpenRouter, so it is added by
mirroring the existing OpenRouter provider.

- RequestyOpenAICompatibleProvider + isRequestyProvider detection, mirroring
  the OpenRouter provider (base https://router.requesty.ai/v1, attribution headers)
- register in the openaiContentGenerator dispatch + provider preset registry
- auth migration entry + docs (auth, model-providers)

Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>

* fix(core): harden Requesty provider hostname detection

Address review feedback on isRequestyProvider:

- Replace substring matching (baseURL.includes) with URL-parsed hostname
  detection (host === 'router.requesty.ai' || host.endsWith('.requesty.ai')),
  matching the ownsModel gate in presets/requesty.ts and the MiMo/MiniMax/
  Mistral providers. Rejects crafted URLs like router.requesty.ai.evil.com.
- Add hostile-hostname rejection tests and determineProvider dispatch tests,
  matching the coverage in the other provider suites.

---------

Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>
2026-06-21 06:37:24 +08:00
tt-a1i
977313b5ae
fix(cli): parse force hyperlink override strictly (#5489) 2026-06-21 01:48:27 +08:00
tt-a1i
21998ee09d
fix(core): require opt-in for plan mode prompt (#5433)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
2026-06-20 17:53:57 +08:00
Edenman
61dcf865de
feat(extensions): interactive multi-tab /extensions manager (Installed / Discover / Sources) (#4850)
* feat(extensions): multi-tab /extensions dialog (Discover/Installed/Marketplaces)

Upgrade the /extensions management dialog from a linear wizard into a
multi-tab interactive dialog aligned with Claude Code's /plugin command.

UI (packages/cli):
- Discover: pull installable plugins from configured marketplaces,
  multi-select (Space), batch install (i) with Global/Project/Local scope,
  open homepage, per-plugin details.
- Installed: plugins + standalone MCP servers grouped by
  Favorites/Local/User/Project/Disabled; Space toggles enable/disable,
  f toggles favorite, Enter opens details with an action menu
  (toggle/favorite/mark-for-update/update/uninstall).
- Marketplaces: add/list/view/remove marketplace sources
  (owner/repo, SSH, HTTP JSON, local path).
- Tabbed shell with Tab/arrow switching and a focus-lock contract so a tab
  owns Escape while in a sub-view.

Core (packages/core):
- ExtensionPreferencesStore: favorites + per-extension scope intent.
- MarketplaceRegistryStore + discoverPlugins(): persistent marketplace
  source registry and cross-source discovery.
- loadMarketplaceConfigFromSource() in marketplace.ts (GitHub/local/HTTP-JSON).
- ExtensionManager methods for marketplaces, discovery, favorites and scopes;
  preference cleanup on uninstall.

Scope mapping: Global -> User; Project/Local -> workspace-scoped enablement
(install then re-scope so the choice actually restricts where it is active).

The Errors tab is intentionally deferred per the spec.

Tests: 19 core unit tests (preferences/registry/discovery) and 11 tabbed
dialog integration tests; existing extension suites updated. typecheck,
lint and i18n checks pass.

* feat(extensions): align Discover plugin detail with Claude Code

Rework the Discover tab's Enter detail view to match Claude Code's
"Plugin details" page in both layout and interaction:

- Layout: "Plugin details" header, title, "from <marketplace>", last
  updated / version, description, "By: <author>", a "Will install:"
  component summary (Skills/Commands/Agents/MCP servers), and a trust
  warning.
- Interaction: the scope choice is now an inline action selector on the
  detail page (Install for you / for all collaborators / in this repo
  only / Open homepage / Back to plugin list), selected with Enter —
  replacing the previous i/h shortcuts and the separate scope step.
- Footer shows "Enter to select · Esc to go back" while a tab sub-view
  is open.

Core: DiscoveredPlugin now carries declared `components` and a
best-effort `lastUpdated`, surfaced by discoverPlugins().

Adds a core test for component/lastUpdated extraction and a UI test for
the detail layout + inline selector.

* feat(extensions): align Add Marketplace view with Claude Code

Match CC's "Add Marketplace" screen: a bold "Add Marketplace" header,
an "Enter marketplace source:" prompt, and an "Examples:" bullet list
(owner/repo · git@…:owner/repo.git (SSH) · https://…/marketplace.json ·
./path/to/marketplace) above a bare cursor input (placeholder removed).

Update the Marketplaces add-view tests accordingly.

* feat(extensions): fix Discover hang + add search/scrolling, align list with CC

Discover reliability and UX fixes:

- Fix the "Discovering plugins…" hang: marketplace network fetches had no
  timeout, so a slow/unreachable source could block discovery forever. Add a
  10s per-request timeout (resolve null) plus socket drain on non-200.
- Cache the fetched listing in ExtensionManager for the session so revisiting
  the tab no longer refetches over the network; `installed` flags are
  recomputed cheaply, and the cache is invalidated on add/remove marketplace.

CC-aligned Discover list:

- Windowed/scrolling viewport (no longer renders the entire 200+ list at
  once) with "↑ more above" / "↓ more below" hints, sized to the terminal.
- Type-to-search filter with a search box and a "Discover plugins (pos/total)"
  count header.
- Item layout: cursor "›", ○/●/✓ checkbox, bold title · marketplace ·
  "<N> installs", with a truncated description line.
- Space toggles selection, Enter views detail (or installs the selected set);
  the conflicting "i" shortcut was removed in favor of search.

Core: DiscoveredPlugin gains a best-effort `installs` count.

Adds tests for windowing, search filtering, and install-count extraction.

* feat(extensions): align marketplace detail with CC + Browse-to-Discover

Rework the Marketplaces tab detail to match Claude Code:

- Show marketplace name, source, "N available plugins", and the plugins
  from this marketplace that are installed ("Installed plugins (K):" with
  descriptions) — instead of dumping a truncated list of all plugins.
- Replace the ad-hoc "d to remove" hint with an action selector:
  Browse plugins (N) · Update marketplace [(last updated DATE)] ·
  Remove marketplace.
- "Browse plugins" switches to the Discover tab filtered to this
  marketplace (only its plugins); the filter clears on manual tab switch
  and is shown in the Discover header.
- "Update marketplace" re-fetches the marketplace config, stamps a fresh
  "last updated", and invalidates the discovery cache.

Core: MarketplaceSource gains lastUpdatedAt; addMarketplace stamps it and
ExtensionManager.markMarketplaceUpdated() refreshes it + clears the
discovery cache.

Adds tests for the marketplace detail layout and the Browse-to-Discover
filtering flow.

* feat(extensions): show plugin type in Installed; guide single-extension adds

- Installed list: plugin rows now show their type + version ("Extension
  v0.7.0"), parallel to MCP rows ("MCP"), instead of a bare version.
- Add Marketplace: when the source is not a Claude marketplace but is a
  valid single extension source (Gemini/Claude/git/npm), the error now
  guides the user to install it directly ("... looks like a single
  extension, not a marketplace. Install it with: /extensions install X")
  instead of the generic "expected marketplace.json" message.

* fix(extensions): resolve git@ SSH marketplace sources

The Marketplaces add flow advertises git@github.com:owner/repo.git (SSH)
as a supported format, but loadMarketplaceConfigFromSource relied on
parseGitHubRepoForReleases, which rejects the git@ scp-like form. Extract
owner/repo directly from the git@github.com:owner/repo(.git) form before
falling back to the URL parser, so SSH marketplace sources actually
resolve. Adds a regression test.

* feat(extensions): cap Discover list window at 6 items

* feat(extensions): unify 'Extension' wording, reorder tabs, expand Marketplaces tab

- Terminology: use 'Extension' instead of 'Plugin' across the dialog
  (Discover extensions, Extension details, Back to extension list, etc.).
- Tabs reordered to Installed, Discover, Marketplaces; the dialog now
  opens on Installed by default.
- Marketplaces tab is now a sources hub:
  - new 'Install new extension' action (installs a single Gemini/Qwen/
    Claude/git/npm extension directly via parseInstallSource).
  - 'Add new marketplace' annotated as a Claude plugin marketplace.
  - items grouped into 'Extensions' and 'Marketplaces' sections; an
    extension row opens a compact detail with Uninstall.

Updates the dialog tests for the new wording, tab order and layout.

* feat(extensions): update Marketplaces tab footer hint

* feat(extensions): full extension actions in both tabs + context-aware Marketplaces footer

- Add a shared ExtensionActionsView (info + components + action menu +
  scope-select + uninstall-confirm) used by both the Installed and
  Marketplaces tabs, so the Marketplaces extension detail now offers the
  full set (Enable/Disable, Favorite, Mark for Update, Update Now,
  Uninstall) instead of just Uninstall/Back.
- Add a new 'Change scope' action (Global/Project/Local) that re-scopes
  enablement (User vs workspace), available in both tabs.
- Context-aware Marketplaces footer: shows 'Enter details' for an
  extension row, 'Enter open · d remove marketplace' for a marketplace
  row, and a neutral hint for the action rows — no longer says
  'd remove marketplace' when an extension is selected.

Adds a test for the full extension actions in the Marketplaces detail.

* feat(extensions): rename Marketplaces tab to Sources, hide Favorites + fix enable/disable in Sources detail

- Rename the user-visible tab label 'Marketplaces' -> 'Sources' (TabBar +
  TABS). The in-tab 'Marketplaces' section header (grouping marketplace
  sources) is kept. Also update the Discover empty-state hint to point at
  the 'Sources' tab.
- Hide the Add/Remove Favorites action in the Sources extension detail via
  a showFavorite prop (default true; Installed keeps it).
- Fix a stale enable/disable label in the Sources extension detail:
  ExtensionActionsView re-read enablement through the manager cache keyed by
  a tick, but refreshCache() briefly empties that cache, so the read raced
  and fell back to the stale extension prop (isActive: true). It now holds
  authoritative local state (enabled/isFavorite/scope) updated optimistically
  after each action — no cache read-back. The Installed tab was immune only
  because it fed a fresh extension object each load.

Adds regression tests for the enable/disable toggle staying in sync and for
change-scope re-scoping + re-enabling a disabled extension.

* feat(extensions): group Sources action rows + show current scope in selector

- Add an 'Add new' section heading above the '+ Install new extension' and
  '+ Add new marketplace' rows on the Sources tab, so those two actions are
  grouped like the Extensions and Marketplaces sections.
- In the Change scope selector, default the cursor to the extension's current
  scope and show a 'Current: <scope>' line. Previously it always defaulted to
  Global, so after changing scope it was unclear whether the change took
  effect. Applies to both the Sources and Installed extension detail (shared
  ExtensionActionsView).

Updates tests to assert the 'Add new' section title renders and that
re-entering the scope selector reflects the now-current scope.

* fix(extensions): move uninstall note to confirm step; complete zh/zh-TW i18n

- Move the 'Note: Uninstall permanently removes this extension.' warning out
  of the detail-view action list and into the uninstall confirmation step
  (replacing the near-synonymous 'This action cannot be undone.').
- Fix the Chinese/English mix in the extensions manager: the new multi-tab UI
  added ~104 English strings that had no locale entries, so they fell back to
  the English key at runtime. Add Simplified (zh) and Traditional (zh-TW)
  translations for all of them, plus the matching en.js base keys (en.js is
  the canonical superset; zh/zh-TW require strict key parity per check-i18n).

Placeholders, keyboard tokens (Tab/Enter/Esc/Space/↑↓/·) and the ⚠ glyph are
preserved across all locales.

* feat(extensions): label Installed scope groups as 用户级/项目级/本地级

Rename the Installed-tab scope group headers from User/Project/Local to
'X level' (用户级/项目级/本地级) so the grouping reads as scope levels.
Adds the new keys to en/zh/zh-TW locales.

* feat(extensions): reuse /mcp server detail for installed MCP servers

The Installed-tab MCP item detail was a read-only view (name/type/scope/
transport/status) with a meaningless 'Enter to select' and no actions. Replace
it with McpServerActionsView, which reuses the /mcp dialog's ServerDetailStep,
ToolListStep, ToolDetailStep and AuthenticateStep so the behaviour matches
/mcp exactly: live connection status, View tools, Enable/Disable, Reconnect
(when disconnected), Re-authenticate and Clear authentication.

Handlers mirror MCPManagementDialog (mcp.excluded settings + toolRegistry
discover/disable/disconnect + MCPOAuthTokenStorage). Delete the now-unused
McpDetailView and its obsolete locale keys; add the two new status strings to
en/zh/zh-TW.

* fix(extensions): populate MCP promptCount from prompt registry

Review follow-up: buildServer hardcoded promptCount to 0, diverging from
/mcp's fetchServerData. Query the prompt registry like the original so the
reused MCPServerDisplayInfo is computed consistently.

* refactor(extensions): rename the source-management layer from marketplace to source

The Sources tab treats both single-extension sources and Claude plugin
marketplaces as 'sources', so the source-management layer is renamed for
consistency:
  MarketplaceSource        -> ExtensionSource
  marketplaceRegistry(.ts) -> sourceRegistry(.ts)
  MarketplaceRegistryStore -> SourceRegistryStore
  add/get/remove/markMarketplaceUpdated -> add/get/remove/markSourceUpdated
  loadMarketplace/updateMarketplace     -> loadSource/updateSource
  MarketplacesTab -> SourcesTab; EXTENSIONS_TABS.MARKETPLACES -> SOURCES
  + the source-detail UI handlers.

Terms that refer to the Claude marketplace manifest *format* are kept, since a
marketplace is one source type: ClaudeMarketplaceConfig,
loadMarketplaceConfigFromSource, the .claude-plugin/marketplace.json path,
DiscoveredPlugin.marketplaceName, and the in-tab 'Marketplaces' group label.

* fix(extensions): keep marketplaces.json filename so saved sources survive the rename

The source/* rename accidentally renamed the persisted registry file from
marketplaces.json to sources.json, so previously added sources (e.g. a Claude
marketplace) appeared to vanish — the data was intact in marketplaces.json but
the code read sources.json. Restore the marketplaces.json filename for
backward compatibility.

* fix(extensions): stay on the Discover detail when an install fails

Previously runInstall always returned to the list after attempting an install.
Now it only returns to the list on success; on failure it stays on the
extension detail page so the error message remains visible and the user can
retry without re-navigating.

* feat(extensions): support 'git-subdir' plugin source in Claude marketplaces

Some Claude marketplace plugins live in a subdirectory of a git repo and use a
'git-subdir' source ({url, path, ref, sha}), which resolvePluginSource didn't
handle — installing failed with 'Unsupported plugin source type'. Add the
git-subdir branch: clone the repo (pinned to ref/sha when provided) and return
the subdirectory as the plugin source.

Verified against github.com/42Crunch-AI/claude-plugins @ v1.5.5: the cloned
plugins/api-security-testing subdir is a valid plugin (.claude-plugin/plugin.json).

* feat(extensions): drop the unused 'local' install scope

Simplify the install/visibility scope model from three options (user /
project / local) down to two (user / project). The 'local' option duplicated
the workspace-level enablement of 'project' without providing a meaningfully
different storage location, so it was UI clutter rather than a real feature.

- core: ExtensionScope = 'user' | 'project'; read() filters unknown values
  via a type guard, so any stale 'local' (or otherwise invalid) entry in
  extension-preferences.json is dropped and falls back to 'user' downstream.
- UI: remove the 'local' option from the Discover install menu, the change-
  scope picker, and the Installed tab's group ordering.
- copy: rename 'Project (All Collaborators)' to 'Project (Workspace)' and
  'Install for all collaborators on this repository' to 'Install for the
  current workspace', matching the new two-tier model.
- i18n: clean up the now-unused 'Local *' keys in en / zh / zh-TW and
  retranslate the renamed keys.
- tests: update the scope-change spec and replace the legacy-scope
  migration test with one that exercises the unknown-value filter.

* feat(extensions): add Ctrl+R shortcut to refresh Discover tab

Press Ctrl+R in the Extensions Manager Discover tab to bypass the discover cache and re-fetch all marketplace sources. The refresh hint is merged into the dialog footer, and a success status is shown after the refresh completes.

* fix(extensions): keep j/k typeable in Discover search

The Discover tab list reused the global SELECTION_UP/DOWN matchers,
which include bare j/k as Vim-style navigation. Combined with
type-to-search input, that made it impossible to type j or k into the
search query.

Switch the Discover list navigation to explicit arrow keys plus
Ctrl+P/Ctrl+N, so bare j/k fall through to the printable-character
branch and append to the query. Other extension tabs (Installed,
Sources) remain pure lists and keep the Vim navigation.

* feat(extensions): install standalone Claude Code plugins from a git URL

A repo whose root holds .claude-plugin/plugin.json (no marketplace.json) is
a standalone Claude plugin. Previously installing one by git URL failed with
"Configuration file not found" because the converter only handled gemini
extensions and marketplace-based Claude plugins.

Add convertClaudePluginStandalone: read plugin.json, fold MCP servers from a
root .mcp.json into the config, collect skills/commands/agents, and write
qwen-extension.json. The marketplace path is refactored to share the build
step. MCP entries are normalized from Claude's transport shape (type:'http' +
url) to Qwen's (httpUrl), and the cloned .git is dropped from the result.

Note: cloneFromGit does not init git submodules, so submodule-provided skills are not installed yet.

* i18n(zh): relabel user-scope install as 全局安装

The Discover detail's user-scope install option now reads 全局安装(用户作用域) instead of 仅为你安装(用户作用域), which better conveys that user scope installs the extension globally.

* fix(extensions): keep the manager mounted during install consent prompts

Consent, setting-input and plugin-choice requests raised by an install
used to replace the ExtensionsManagerDialog in DialogManager, unmounting
it mid-install: the dialog remounted on the default Installed tab with
pre-install data, and the completion reload signal hit the dead
instance. Render those prompts inside the dialog instead (tab content
hidden but mounted), and gate the previously always-active key handlers
(MCP detail steps, uninstall confirm) so hidden views can't double-handle
keys while a prompt is shown.

* feat(extensions): show loading feedback for scope change and toggles

Changing an extension's scope now swaps the selector for a
"Changing scope..." line (Esc ignored while in flight), and the
Installed tab's Space toggle reports "Enabling/Disabling ..." in the
status line right away — MCP enable rediscovers tools and can take a
while. Overlapping toggle/favorite presses are ignored until the
in-flight mutation finishes.

* feat(extensions): add --scope to install and a sources CLI command group

Bring the qwen extensions CLI up to par with /extensions manage:
install --scope user|project (workspace accepted as an alias) records
the scope preference and, for project, re-scopes enablement to the
current workspace only — mirroring the Discover tab's install flow.
New sources add/list/update/remove subcommands manage the Claude
marketplace sources that power the Discover tab.

* feat(extensions): trim the Sources tab and add marketplace detail retry/refresh

Drop the redundant installed-extensions list from the Sources tab (those
live on the Installed tab); the marketplace detail still summarizes which
of its plugins are installed, now capped with a "… and N more" line so a
long list stays short. Add an R key in the marketplace detail that
re-fetches the source — surfaced in the footer as a retry on load
failure and a refresh once loaded. Reword the install row to "Install a
new extension".

* feat(extensions): nest extension-bundled MCP servers under their extension

The Installed tab now lists each extension's bundled MCP servers as
indented child rows beneath it, with Enter opening the shared MCP detail
view (tools, OAuth authenticate/clear) just like the /mcp panel.

- Skip child rows shadowed by a same-named user/project server and ones
  blocked by the MCP allow-list, matching the runtime merge semantics.
- Space only blocks the disable direction for bundled servers; an
  individually-excluded server under an active extension can be
  re-enabled. Blocked Space/favorite and Enter on a disabled extension's
  server give info feedback instead of failing silently.
- Hide the always-failing Disable action for active extension-provided
  servers in ServerDetailStep (also fixes the /mcp panel).
- Window the Installed list to the terminal height with scroll hints,
  clamped offset and group-header anchoring.
- Fall back to the list if the open detail's item disappears on reload
  so the tab can't get stuck locked.
- Hermetic tests: stub loadSettings, stabilize two flaky assertions.

* feat(extensions): per-server disable for extension MCPs and live status

Extension-bundled MCP servers can now be disabled individually, aligned
with Claude Code. The record lives in extension-preferences.json keyed
by extension name (not the global mcp.excluded list), so it never
affects same-named servers from other sources, survives restarts via
config.isMcpServerDisabled, and is cleaned up on uninstall.

- All three surfaces support the toggle: Installed tab Space, the
  extensions MCP detail view, and the /mcp panel; ServerDetailStep
  offers Disable for extension servers again.
- Installed tab MCP rows now show the live connection state (connected
  / needs authentication / connecting / disconnected) instead of a bare
  enabled flag; selected rows highlight the badge and status text too.
- "Needs authentication" (401 marker or declared OAuth with no stored
  token) renders consistently in the /mcp list and both detail views;
  a successful connect clears the sticky 401 marker in core.
- All three surfaces subscribe to MCP status changes for live updates,
  with statuses re-stamped synchronously before setState to close the
  load/listener race.
- Perf: extension preferences are cached by mtime, and the disabled
  predicate finds the owning extension without rebuilding the merged
  server map.

* docs(extensions): document interactive manager and backfill missing i18n

Add a 'The interactive extension manager' section covering the
Discover/Installed/Sources tabs, per-server MCP enable/disable, and
keybindings.

Backfill en/zh/zh-TW entries for six previously-untranslated strings
referenced via t() (npm install flags, the install command description,
'Description', and 'Delete Session') so they render localized instead of
falling back to English.

* fix(extensions): harden untrusted-marketplace handling from PR review

Address review findings on the new marketplace/plugin attack surface:

Security
- Strip ANSI/control sequences from marketplace-sourced display strings
  (Discover + Sources) so a hostile source can't manipulate the terminal
  before install consent.
- Confine git-subdir source.path to the cloned repo (reject absolute/.. /empty)
  to stop path traversal; prefer the immutable SHA pin over a named ref.
- Refuse absolute/relative local-path plugin sources from remote marketplaces.
- Reject absolute/escaping mcpServers and hooks file paths in plugin.json so
  the converter can't read arbitrary out-of-tree files.
- Only follow http(s) plugin homepages (block file:// etc. via open()).
- Cap marketplace HTTP response bodies and add a wall-clock fetch deadline
  (req.setTimeout is socket-idle only) to prevent OOM / indefinite hangs.

Correctness / robustness
- A post-install scope/enablement failure no longer marks the install failed.
- .mcp.json without an mcpServers object is skipped instead of misparsed.
- Guard Ctrl+R refresh against concurrent in-flight discovery.
- Log (not swallow) OAuth token-store lookup failures.
- InstalledTab: single-pass tool-count map (drop N+1 getAllTools) and O(1)
  row index lookup (drop per-row items.indexOf).

Tests
- Make the .git-stripping assertion meaningful and cover the absolute-path
  mcpServers guard and the .mcp.json fallback.

* fix(extensions): consent layout, per-extension update check, uninstall progress

- ConsentPrompt: render the markdown prompt in a column. The inner Box
  defaulted to flexDirection=row, so a multi-paragraph consent (e.g. an
  extension bundling several MCP servers) tiled its blocks into narrow
  vertical columns. It now stacks vertically.
- "Mark for Update" now checks only the selected extension via
  checkForExtensionUpdate (previously ran the full checkForAllExtensionUpdates
  with a discarded result), stores the result, and reports "update available"
  vs "already up to date" so the "Update Now" action shows up immediately.
- Uninstall: show an "Uninstalling ..." line while removal runs so the confirm
  prompt no longer looks frozen after Enter.
- i18n (en/zh/zh-TW): add the new strings; drop the now-unused
  "Checked ... for updates." key.

* feat(extensions): clearer update-check feedback for "Mark for Update"

Distinguish the four check outcomes instead of collapsing everything to
"up to date": update-available, up-to-date, not-updatable, and error.
For not-updatable Claude marketplace plugins, spell out the reason and
workaround (they are install-time conversions with no git remote, so they
update by reinstalling). Also show a "Checking ... for updates..." line
first, since git/github-release/npm checks hit the network.

* fix(extensions): confine resource/source paths, sanitize homepage (review round 2)

- claude-converter: route commands/skills/agents resource paths (collectResources)
  and the relative string plugin-source through path-confinement — reject
  absolute / ..-escaping values — matching the existing mcpServers/hooks guard.
- sourceRegistry: sanitize plugin.homepage like the other untrusted marketplace
  display fields (it otherwise reaches status toasts unsanitized).
- InstalledTab: construct MCPOAuthTokenStorage once instead of once per server.

* fix(extensions): confine symlink targets when converting untrusted plugins

A plugin source from an untrusted marketplace/git repo can embed a
symlink whose name stays inside the package but whose target points at a
host file (e.g. skills/leak.txt -> ~/.ssh/id_rsa). The path-confinement
checks were purely lexical (path.resolve), while the downstream
copy/read calls follow symlinks, so the target's content could be
shipped into the installed extension.

- copyDirectory: pin the package real-path root and skip symlinks whose
  resolved target escapes it (this is the bulk-copy vector at
  buildQwenExtensionFromPlugin).
- resolvePluginRelativeFile: re-verify the real path with realpathSync
  after the lexical check (covers mcpServers/hooks/single-file resources).
- collectResources: skip symlinked files in a collected folder whose
  target escapes the resource dir.
- resolvePluginSource: reject a string source that resolves outside the
  marketplace dir through a symlink.

Adds regression tests for the bulk-copy and collected-folder paths.

* fix(extensions): confine manifest reads, share path-containment helpers

Follow-up to the symlink confinement work (review round 3):

- Guard the three manifest reads that bypassed the new checks: a hostile
  clone could make marketplace.json / plugin.json / .mcp.json themselves
  symlinks to JSON-shaped host files (e.g. ~/.docker/config.json), whose
  content was parsed into the merged config. Each read now verifies the
  real path stays inside the package before reading; .mcp.json is treated
  as absent, plugin.json/marketplace.json throw.
- Hoist the containment logic into gemini-converter as exported
  isPathWithin (lexical) + realPathWithin (symlink-resolved) so the rule
  lives in one place instead of being duplicated across both converters.
- Document copyDirectory's confineRoot parameter in its JSDoc.

Adds targeted tests for the resolvePluginRelativeFile and
resolvePluginSource symlink-rejection branches and the plugin.json
manifest guard.

* fix(extensions): cap manager dialog width to the main content area

On a wide terminal the /extensions manager clipped its right-aligned
status column to a sliver (e.g. "扩…"). Root cause: the dialog sized
itself to boxWidth = columns - 4 with no cap, while the app's main
content area is capped at 100 columns (AppContainer's mainAreaWidth).
Past ~104 columns the dialog grew wider than its container and the right
edge — including the status column flexGrow pushes to the far right — was
clipped off-screen. Narrow terminals were unaffected because columns - 4
stayed within the cap.

Cap boxWidth at Math.min(columns - 4, 100) to match the main content
area, mirroring the existing DiffDialog idiom.

Adds a wide-terminal regression test that renders through a 200-column
stdout and asserts no line exceeds the content area (the uncapped dialog
produced ~196-col lines).

* fix(diff): cap /diff dialog width to the main content area

Same wide-terminal clipping as the extensions manager: DiffDialog sized
itself to Math.min(columns - 4, 110), but the app's main content area is
capped at 100 cols (AppContainer's mainAreaWidth). On a wide terminal the
dialog grew to 110 columns inside the 100-column container and its right
border/edge was clipped off-screen. Narrow terminals were unaffected
because columns - 4 stayed within the cap.

Cap dialogWidth at Math.min(columns - 4, 100) to match the container.

Adds a wide-terminal regression test (200-column stdout) asserting no
rendered line exceeds the content area.

* fix(extensions): address review round 3 — symlink/ANSI confinement, crash & data-loss hardening

Security:
- claude-converter: confine git-subdir subdir against symlink escape;
  fail strict mode on a symlinked plugin.json; sanitize untrusted
  source/path in conversion error messages
- sourceRegistry: sanitize version/category/lastUpdated and component
  names before TUI render
- gemini-converter: guard gemini-extension.json reads with realPathWithin
- SourcesTab: sanitize persisted marketplace name in list + remove-confirm

Robustness:
- SourcesTab: wrap sync removeSource in try/catch (was crashing the TUI);
  only mark a marketplace updated when the refresh actually loaded
- InstalledTab: move bundled-MCP enable write inside try/catch
- DiscoverTab: return to the list (not an arbitrary plugin's detail) after
  a failed batch install
- install.ts: roll back the User-scope disable when the Workspace enable fails
- claude-converter: reject a null/non-object plugin.json with a clear error;
  warn instead of silently skipping a symlinked .mcp.json
- extensionPreferences/sourceRegistry: quarantine a corrupt state file to
  ${path}.corrupted so the next write can't clobber recoverable data

Tests:
- cover marketplace.json/plugin.json/.mcp.json symlink guards, strict-mode
  rejection, and null plugin.json
- delete the process.stdout.columns override on non-TTY in dialog width tests

* fix(extensions): guard toggleFavorite write against unhandled rejection

* test(extensions): normalize realpathSync mock so gemini guard passes on Windows

* fix(extensions): address review round 4 — shared sanitizer, narrowed quarantine, visible warnings, tests

- consolidate the three drifted ANSI/control-char strippers into a single
  shared stripAnsiAndControl in core textUtils (fixes the C1-range gap in
  workflow-orchestrator's copy); sourceRegistry/claude-converter now delegate
- claude-converter: also sanitize the git-subdir ref/sha in the not-found error
- corruptFile: surface the quarantine on stderr (debug log is gated off, so a
  silent move would still look like data loss)
- extensionPreferences/sourceRegistry: only quarantine on a JSON parse failure,
  not on transient read errors (EACCES/EMFILE/EISDIR) that leave a valid file
- install: surface a failed scope-change rollback instead of swallowing it

Tests:
- gemini-converter: negative-path coverage for the realPathWithin guards
- new corruptFile.test (rename-aside + best-effort failure)
- stripAnsiAndControl unit tests (ANSI/OSC/C0/C1)
- install rollback + rollback-also-fails cases

* test(extensions): assert discoverPlugins strips ANSI/control chars from display fields

Locks in the untrusted-metadata sanitization that was only covered
empirically before. Feeds a hostile plugin (cursor moves, line clears,
OSC title-injection, BEL) through discoverPlugins and asserts every
rendered field — name/version/description/author/homepage/category/
lastUpdated/component names plus the marketplace name — comes out clean.

Addresses the maintainer verification note on PR #4850.

* fix(extensions): address review round 5 — scope-change rollback, version sanitization, visible read warnings

- Critical: the UI scope-change (ExtensionActionsView) and project-scope
  install (DiscoverTab) disabled User then enabled Workspace with no
  rollback — a failed Workspace enable left the extension disabled at all
  scopes (silently dead, and in DiscoverTab the outer catch swallowed it so
  the install still reported success). Mirror the CLI install.ts pattern:
  roll the User enable back on failure.
- Persist the scope preference only AFTER enablement succeeds (install.ts +
  both UI paths), so a rolled-back enable can't leave prefs pointing at a
  scope the extension isn't actually enabled at (Installed tab mislabel).
- Security: the persisted 'version' is rendered raw on the Installed tab and
  PluginDetailView — only 'name' is validated on load, so a marketplace
  plugin could inject ANSI/control sequences post-install on every render.
  Scrub via stripUnsafeCharacters at both sinks (covers already-installed
  extensions; the Discover-side sanitization doesn't reach this path).
- Transient read errors in extensionPreferences/sourceRegistry only logged
  via the gated debugLogger, so a user's favorites/scopes/sources could
  vanish with no trail. Add an stderr warning matching quarantineCorruptFile.
- Tests: assert quarantine runs on a parse error (.corrupted sibling) and
  does NOT run on a transient read error (EISDIR), for both stores.

* fix(extensions): address review round 6 — marketplace-name sanitization, rollback-failure surfacing, url-source guard, security tests

Code:
- ANSI injection via the Discover marketplace filter: the marketplace name
  (untrusted, from a remote marketplace.json) flowed through onBrowse to the
  Discover hint render unsanitized. Scrub it in handleBrowseSource — this also
  fixes the filter comparison (it is matched against the already-sanitized
  DiscoveredPlugin.marketplaceName).
- '(Tab to clear)' hint was misleading: Tab cycled tabs rather than clearing
  the marketplace filter in place. On Discover with an active filter, Tab now
  clears the filter in place, matching the hint.
- Scope-change rollback failures were silently swallowed by a bare catch in
  both the Discover batch install and ExtensionActionsView, unlike the CLI
  install.ts which surfaces them. Both now report the rollback failure so the
  user knows the extension may be disabled at all scopes (new i18n keys for
  en/zh/zh-TW).
- resolveInstallSource: the structured { source: 'url' } branch bypassed the
  local-path guard applied to string sources, letting a remote http
  marketplace redirect the installer at a local filesystem path. Apply the
  same guard.

Tests:
- marketplace fetchUrl: wall-clock deadline (stalled server) and body-size cap
  (oversized stream) now covered.
- sourceRegistry: remote-marketplace local-path rejection covered for both the
  string and { source: 'url' } source forms.
- claude-converter git-subdir: clone+sha-pin happy path plus path-escape,
  absolute-path, missing-subdir, and symlink-escape rejections covered.

Note: the bot's 'missing scope rollback' threads target a pre-631e271fb
snapshot — that rollback already landed in round 5.

---------

Co-authored-by: 克竟 <dingbingzhi.dbz@alibaba-inc.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-20 13:18:19 +08:00
Gove
0ba245ea3d
feat(cli): add persistent history collapse on resume with refined commands (#4085)
* feat(cli): add --quiet-restore flag to suppress history output on session resume

* fix: preserve history state for /rewind while suppressing rendering

* refactor: model quiet-restore as display policy with shared utilities

* refactor: replace --quiet-restore with /history collapse|expand slash command

* fix: persist history collapse state as user setting

* fix(cli): address maintainer feedback on history collapse persistence and i18n

* test(cli): fix TypeScript compilation errors in historyCommand tests

* fix(cli): address maintainer review feedback on history collapse

* test: fix act() warning in slashCommandProcessor.test.ts

* fix: make applyCollapsePolicyAndSummary pure to avoid React batching bug

* chore: revert unrelated changes to lockfile and NOTICES.txt

* test: verify isRealUserTurn handles suppressOnRestore items correctly

* wip(cli): preserve local history review fixes before redesign

* feat(cli): refine history resume collapse commands

* fix(cli): address maintainer review feedback on history collapse

* test(cli): cover cold-boot collapsed resume

* fix(cli): address reviewer feedback on history collapse

* fix(cli): resolve rebase conflicts and missing imports

* fix(cli): strip suppressOnRestore in handleRewindConfirm

* fix(i18n): add Chinese translations for history collapse commands

* fix: address wenshao review comments on PR #4085

- Restore restoreGoalFromHistory call in cold-boot resume path
- Extract stripSuppressOnRestore to shared utility in resumeHistoryUtils
- Add comment explaining historyRef pattern in slashCommandProcessor
- Use historyCommand.name constant instead of string literal
- Add missing i18n translation for collapse summary message
- Fix pluralization in createHistoryCollapseSummaryItem

* fix(i18n): add missing English translations for history collapse commands

* fix: address wenshao follow-up review comments

- Filter out collapse-summary items in rewind path (AppContainer.tsx)
- Show info messages for collapse-on-resume/expand-on-resume commands (slashCommandProcessor.ts)
- Use visibleHistory instead of uiState.history in summaryByCallId useMemo (MainContent.tsx)
- Remove dead hasHistoryManager guard and optional chaining (useResumeCommand.ts)

* fix: restore optional chaining for remount in useResumeCommand

* test: update slashCommandProcessor tests for history command feedback changes

* chore: remove generated artifact files from branch

- Remove .learnings/LEARNINGS.md (local workflow artifact)
- Remove .pr-body.md (PR description draft)
- Remove build_output.log (build log)
- Remove vscode_test_output.log (test output log)

These files are unrelated to the history-collapse feature and were causing
git diff --check whitespace errors.

* fix: address wenshao review comments on PR #4085

- Remove stray [!tip] file from repo root
- Add selfManaged flag to MessageActionReturn for explicit UI feedback control
- Fix expand-now to return load_history type instead of calling loadHistory directly
- Replace hardcoded isSelfManaged path check with result.selfManaged in processor
- Fix MainContent merge detection to use visibleHistory.length (avoid flicker)
- Refactor applyCollapsePolicyAndSummary to not mutate input array
- Extract expandCollapsedHistory shared helper
- Restore dialog:memory test in slashCommandProcessor.test.ts
- Add stripSuppressOnRestore dedicated tests
- Add visibleHistory filtering test in MainContent.test.tsx
- Update historyCommand tests for new load_history return type
- Fix eslint errors: remove unused imports and fix dependency array

* fix: address wenshao review comments on PR #4085

- Remove stray [!tip] file from repo root
- Add selfManaged flag to MessageActionReturn for explicit UI feedback control
- Fix expand-now to return load_history type instead of calling loadHistory directly
- Replace hardcoded isSelfManaged path check with result.selfManaged in processor
- Fix MainContent merge detection to use visibleHistory.length (avoid flicker)
- Refactor applyCollapsePolicyAndSummary to not mutate input array
- Extract expandCollapsedHistory shared helper
- Restore dialog:memory test in slashCommandProcessor.test.ts
- Add stripSuppressOnRestore dedicated tests
- Add visibleHistory filtering test in MainContent.test.tsx
- Update historyCommand tests for new load_history return type
- Fix eslint errors: remove unused imports and fix dependency array
- Fix TypeScript errors: add useEffect import, fix display.kind type assertions

* fix: add braces around if statement body (eslint curly rule)

Fixes lint error in editorGroupUtils.ts:
- Expected { after 'if' condition on line 32

* fix: address wenshao follow-up review comments on PR #4085

- Revert expand-now to use loadHistory/refreshStatic directly (no load_history return)
- Remove dead selfManaged flag from MessageActionReturn and processor
- Fix visibleHistory filter to also exclude collapse-summary items
- Simplify applyCollapsePolicyAndSummary (return rawItems when !collapseOnResume)
- Fix test assertion shapes (content→text, timestamp as separate arg)
- Add mockClient for expand-now test
- Update historyCommand tests for new behavior
- Add expandCollapsedHistory dedicated tests
- Fix MainContent filtering test to use historyItemDisplayPropsSpy

* fix: address wenshao latest review comments on PR #4085

- Fix visibleHistory filter: remove collapse-summary exclusion (summary should render when all items suppressed)
- Update MainContent test: assert summary item IS rendered alongside unsuppressed items
- Remove dead selfManaged flag from MessageActionReturn type
- Remove dead selfManaged check from slashCommandProcessor.ts
- Add remount?.() to useResumeCommand error path
- Remove unused 'History expanded.' translation key from en.js, zh.js, zh-TW.js
- Fix expand-now test: mock action to return undefined (matching real behavior)

* fix: address wenshao latest review comments on PR #4085

- Add missing i18n translations to ca.js, de.js, fr.js, ja.js, pt.js, ru.js
- Simplify applyResumeDisplayPolicy: remove dead options parameter
- Fix expand-now test: mock action to return undefined (matches real behavior)
- Revert unrelated changes to package-lock.json and editorGroupUtils.ts

* fix: address wenshao latest review comments

- Add openDiffDialog to createMockActions() in slashCommandProcessor.test.ts
- Add sentToModel: false to user message assertions in slashCommandProcessor.test.ts
- Fix useResumeCommand.test.ts mock: spread original @qwen-code/qwen-code-core exports to include createDebugLogger
- Remove dead optional chaining (addItem?., clearItems?., loadHistory?.) in useResumeCommand.ts
- Simplify if (!config || !startNewSession) to if (!config) since startNewSession is required
- Fix truncatedCount off-by-one in AppContainer.tsx rewind path: exclude collapse-summary from effective length
- Apply collapse policy (applyCollapsePolicyAndSummary) in useBranchCommand.ts for /branch
- Add settings to UseBranchCommandOptions with proper useCallback dependency

* fix: add missing mockUpdateItem arg to test renderHook calls, remove stray file

- Add mockUpdateItem (17th arg) to resume-direct and memory dialog test calls
- Remove accidentally committed packages/core/.qwen/computer-use/installed.json
- Add .qwen/computer-use/installed.json to .gitignore

* fix: address review comments (type, gitignore, test, split-brain)

1. UIActionsContext: handleResume return type → Promise<void>
2. .gitignore: split corrupted merged line into .codegraph + .qwen/...
3. useBranchCommand.test: add settings to makeOptions(), add collapseOnResume test
4. useResumeCommand: reorder core-before-UI with rollback (matches branch pattern)
5. useResumeCommand.test: add getSessionId to mocks, add rollback test

* fix: complete history collapse translations in 6 locale files

- Added missing translation text for 'History collapsed' message
- Fixed syntax errors in de, fr, ja, pt, ru, zh-TW locale files

* fix: add missing history parameter in slashCommandProcessor test

- Fixed parameter order in 'should skip reload when consumeSlashReloadSuppression' test
- Added missing empty array for history parameter
- All 58 tests now pass

* merge: resolve conflicts with upstream main

- docs: keep ui.history.collapseOnResume setting, adopt upstream showCitations default
- fix: update rewindRecording call to include file history snapshots parameter

* fix(cli): repair history collapse CI failures

---------

Co-authored-by: qqqys <qys177@gmail.com>
2026-06-19 19:22:48 +00:00
Shaojin Wen
715ef938f5
feat(cli): serve the Web Shell UI from qwen serve (#5392)
* feat(cli): serve the Web Shell UI from `qwen serve`

`qwen serve` now serves the built Web Shell SPA at its root on the same
origin as the API, so a released binary exposes the browser terminal
without the dev-only Vite server (the `npm run dev:daemon` two-process
setup is unchanged for development).

- New `webShellStatic.ts` mounts `/`, `/assets/*` and an SPA deep-link
  fallback. The fallback uses the same document-navigation discriminator
  as the Vite dev proxy so it never shadows API JSON 404s.
- The static shell is registered BEFORE bearerAuth (a browser can't attach
  a token to a `<script>` subresource or an address-bar navigation; the
  shell carries no secrets and every API route stays token-gated). HTML
  responses set CSP + X-Frame-Options + Referrer-Policy + no-cache.
- `--open` launches the browser at the daemon URL (with `?token=` when set)
  once the listener is up, guarded by `shouldLaunchBrowser()`.
- `--no-web` opts out for an API-only daemon.
- Bundle / npm publish / standalone packaging now ship `dist/web-shell/`.
  Missing assets degrade to API-only with a breadcrumb, never a hard fail.

Tests: +6 cases in server.test.ts (root shell, assets, SPA fallback,
non-navigation 404 passthrough, security headers, --no-web off).

* fix(cli): address review on Web Shell serving

Review fixes for #5392 (qwen-code-ci-bot):

- [Critical] SPA fallback no longer shadows /health or /demo on non-loopback
  binds — those paths fall through to their own routes / bearerAuth instead
  of receiving index.html.
- [Critical] --open trims the bearer token before putting it in the browser
  URL, matching runQwenServe's own trimming, so a trailing newline from
  `$(cat token.txt)` no longer makes every API call 401.
- --open is wrapped in its own try/catch so a failed browser launch can't
  take down the already-listening daemon; it normalizes wildcard binds
  (0.0.0.0 / ::) to loopback, and only fires when the UI is actually mounted
  (new RunHandle.webShellMounted).
- resolveWebShellDir() now requires BOTH index.html and assets/, so a partial
  build degrades to API-only instead of serving a shell whose chunks 404.
- runQwenServe logs a positive "Web Shell UI served from <dir>" breadcrumb,
  and warns that on a non-loopback bind without --allow-origin the shell is
  read-only (same-origin POSTs are blocked by the CORS wall).
- Document the --open token-in-process-list exposure in help text + a stderr
  note when a token is forwarded.
- Tests: POST method guard, sec-fetch navigation signal, /health not shadowed,
  sendFile 500 path, plus isDocumentNavigation and resolveWebShellDir units.

* fix(cli): harden Web Shell asset resolution and send-error logging

Second-round review (claude /qreview on the initial commit):

- resolveWebShellDir() now walks up from this module to find a sibling
  packages/web-shell/dist, covering the transpiled layouts the previous
  fixed `..` depth missed — per-package `tsc` output and the integration
  daemon harness (packages/cli/dist/index.js), which would otherwise resolve
  to nonexistent paths and silently run API-only.
- sendFile failures are no longer silent: log the error (matching the /demo
  handler — previously the only 5xx path that emitted nothing) and res.end()
  a half-streamed response instead of leaving the client on a 200 with a
  partial body.

The remaining comment (open-browser inside the boot try) was already fixed
in 2487c90, where the --open block gained its own try/catch.

* fix(cli): pass --open token via URL fragment + add auth-contract tests

Third-round review (qwen3.7-max /review):

- --open now puts the token in the URL fragment (#token=) instead of a query
  param, and the Web Shell reads it from the fragment first (falling back to
  ?token= for the dev launcher / hand-built URLs). A fragment is never sent to
  the server, so the token stays out of access logs and Referer headers. It is
  still visible in the browser-launcher's argv, so the stderr note stays and a
  one-time-code exchange remains the real fix for multi-user hosts (follow-up).
- Add a server test pinning the "shell served before bearerAuth, API still
  token-gated" contract (GET / → 200 without auth, /capabilities → 401 with a
  token set), plus front-end getDaemonToken fragment/query precedence tests.

The token-trim comment in this pass was already addressed in 2487c90.

* fix(cli): read --open token from RunHandle.resolvedToken; doc + test polish

Fourth-round review (qwen3.7-max /review), all suggestions:

- --open now reads the server's resolved (trimmed) token from
  RunHandle.resolvedToken instead of re-deriving it from argv/env. Removes the
  duplicated QWEN_SERVER_TOKEN literal + trim logic and any drift risk; the
  browser token is by construction what the daemon authenticates against.
- Simplify webShellMounted to !!webShellDir (serveWebShell===false already
  forces webShellDir to undefined, so the extra conjunct was dead).
- Docs: the --open row now documents the #token= fragment transport (was
  ?token=) and why a fragment is used.
- Tests: add removeDaemonTokenFromUrl coverage (strip from fragment / query /
  both, preserve non-token hash params, no-op when absent) and the missing
  afterEach import.

* fix(cli): register Web Shell SPA fallback after API routes

Fifth-round review (claude /qreview):

- The SPA fallback no longer sits before bearerAuth. It now runs after every
  API route (just before the error handler), so authed routes — and their
  401s — always win, and only genuine 404 misses fall through to the shell.
  A navigation with an attacker-controlled `Accept: text/html` to
  /capabilities (or /health on a non-loopback bind) no longer coaxes the 200
  shell out of a gated endpoint, and the fragile exact-match /health,/demo
  denylist (which trailing-slash variants slipped past) is gone.
  registerWebShell is split into mountWebShellAssets (/, /assets — still
  pre-auth so a browser can load the shell + subresources without a header)
  and mountWebShellSpaFallback (post-auth). The contract test now sends
  Accept: text/html to /capabilities and asserts 401 — it would have been 200
  before this change (the test was passing only because it omitted Accept).
- verifyBundleArtifacts (the publish gate) now requires dist/web-shell, so a
  build that skipped the web-shell workspace (e.g. npm ci --ignore-scripts
  bypassing the root prepare) fails packaging loudly instead of silently
  shipping an API-only CLI whose GET / 404s.

* fix(cli): return a clean 404 for missing Web Shell assets

Sixth-round review (qwen3.7-max /review):

A missing /assets/* (e.g. a stale hashed chunk after a redeploy renamed it)
now returns 404 instead of falling through to the SPA fallback and answering a
browser navigation with a 200 index.html. Implemented with an explicit /assets
404 handler after express.static rather than serve-static's `fallthrough:
false` — the latter forwards a 404 error to the catch-all error handler, which
would turn it into a 500. Test added.

* test(cli): cover --open + Web Shell signals; add shell security headers

Seventh-round review (qwen-code-ci-bot):

- [Critical] Extract the --open browser-launch logic into the exported
  maybeOpenWebShellBrowser() and unit-test it: --open / webShellMounted /
  shouldLaunchBrowser gating, wildcard-host -> loopback rewrite, token in the
  URL fragment (not query), and the never-throws error catch.
- [Critical] Assert RunHandle.webShellMounted (false under --no-web) and
  resolvedToken (trimmed / undefined) in runQwenServe.test.ts; also cover
  --web/--no-web and --open arg parsing.
- Drop dead code: target.hostname === '::' is unreachable (Node's URL returns
  the IPv6 wildcard as '[::]', which is already handled).
- Add defense-in-depth headers to the shell response: base-uri 'none' in the
  CSP (does not fall back to default-src), X-Content-Type-Options: nosniff,
  and a restrictive Permissions-Policy.
- Add serve-debug-gated logging for /assets 404s and SPA-fallback hits so a
  white-screen shell / routing misconfig has a diagnostic trail.

* fix(test): satisfy the Web Shell release gate in package-assets fixture

Eighth-round review (claude /qreview) — this is the actual CI failure.

The verifyBundleArtifacts Web Shell gate (requiring dist/web-shell, added in
this PR) broke scripts/tests/package-assets.test.js, which merge-main pulled
in: its createBundleArtifacts fixture only created cli.js / vendor / bundled,
so preparePackage exited 1 at the new gate before the test's assertions ran —
red on all three Test jobs. Add the web-shell artifacts (index.html +
assets/) to the fixture. The gate itself is intentional (it stops an API-only
package from shipping).
2026-06-19 19:41:33 +08:00
tt-a1i
b773b895c2
feat(cli): show optional response token rate (#5401) 2026-06-19 17:39:59 +08:00
tt-a1i
73e6c7ef0f
fix(cli): clarify cumulative statusline token labels (#5400) 2026-06-19 17:39:31 +08:00
tt-a1i
0430ff7af4
fix(openai): add string tool result compatibility mode (#5399)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
2026-06-19 16:56:49 +08:00
Heyang Wang
a8eb824fb7
feat(config): add settings file change detection via chokidar watcher (#3696) (#4933)
Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
2026-06-19 15:00:44 +08:00
tt-a1i
3cecd667d5
fix(core): validate grep result limits (#5389)
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-19 13:47:13 +08:00
MikeWang0316tw
26ad36e95b
feat(cli): show follow-up suggestion in input placeholder (#5145)
* feat(cli): show follow-up suggestion in input placeholder

When enableFollowupSuggestions is true, display the generated
follow-up suggestion as the input placeholder text (replacing
the default "Type your message..."). Tab/Enter/Right arrow
accepts the suggestion; typing dismisses it.

Also change the default of enableFollowupSuggestions from false
to true so the feature is on by default.

Key changes:
- AppContainer: dismissPromptSuggestion no longer clears
  promptSuggestion state, preserving it for placeholder restore
  after user types then deletes
- InputPrompt: Tab/Enter/Right arrow/typing handlers check
  promptSuggestion prop as fallback when followup.state is not
  visible (e.g. after 300ms delay or user dismissed)
- Composer: placeholder shows suggestion text when available
- hasTabConsumer: include promptSuggestion to prevent Windows
  bare Tab from cycling approval mode

* chore: update settings.schema.json (enableFollowupSuggestions default: false → true)

* test(cli): add tests for promptSuggestion prop fallback paths (#5145)

- Add unit tests for Tab/Right arrow/Enter accepting promptSuggestion
  when followup.state.suggestion is null (type-then-delete path).
- Add unit test for hasTabConsumer reporting true immediately when
  promptSuggestion prop is set (no followup debounce needed).
- Update stale comment on speculation abort useEffect in AppContainer.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): address PR #5145 review feedback for promptSuggestion

- Fix Enter key to fill buffer instead of submitting suggestion (matches
  Tab/Right-arrow behavior and Claude Code design)
- Add suggestionDismissed state to hasTabConsumer for Windows Tab cycling
- Fix suggestionDismissed to be set to true on user input (paste/typing)
- Add speculation abort to dismissPromptSuggestion callback
- Remove dead placeholder branch from Composer.tsx
- Update tests to reflect Enter no longer auto-submits suggestion

* fix(cli): address PR #5145 review from wenshao + telemetry gap

wenshao's review (posted after the previous fixes) flagged two issues,
both still valid against the current code; doudouOUC's telemetry gap
is addressed too.

- settings description: replace stale "Enter to accept and submit" with
  "Press Tab, Right Arrow, or Enter to accept into the input buffer" in
  both settingsSchema.ts and settings.schema.json (Enter now only fills
  the buffer, and the feature defaults to enabled).

- hasTabConsumer / handler consistency: drop the redundant
  `suggestionDismissed` state and gate hasTabConsumer on
  `buffer.text.length === 0` — the exact condition the Tab/Right/Enter
  handlers already use. Fixes the type-then-delete desync where Windows
  bare Tab would both insert the suggestion and cycle approval mode
  (regression of #4171).

- fallback telemetry: add a `fallbackText` option to the followup
  controller's accept() so the prop-fallback path (no live suggestion,
  e.g. within the show delay or after type-then-delete) routes through
  accept() and logs onOutcome instead of silently bypassing telemetry.
  Tab/Right/Enter handlers now call accept(method, { fallbackText }).

- tests: add core-level coverage for accept() with/without fallbackText,
  and fix the InputPrompt "fallback" tests that advanced 700ms (which
  silently exercised the normal visible-suggestion path) to advance only
  100ms so followup.state.suggestion truly stays null.

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

* feat(cli): add accept_source telemetry + tests for promptSuggestion fallback

Follow-up to wenshao's second review pass on #5145.

- accept_source telemetry: fallback accepts report time_to_accept_ms: 0
  (the suggestion was never shown via the timer), which is indistinguishable
  from an instant accept. Add an `accept_source: 'live' | 'fallback'` field to
  the followup controller's onOutcome and PromptSuggestionEvent so analytics
  can tell the two apart. The controller derives it from whether a live
  `currentState.suggestion` was present before applying `fallbackText`.

- tests: assert accept_source on the fallback accept; add a test that a live
  suggestion takes priority over fallbackText (guards the `?? fallbackText`
  ordering); add an InputPrompt test pinning the new buffer.text.length === 0
  gate — hasTabConsumer reports false when a promptSuggestion is set but the
  buffer is non-empty (the old Boolean(promptSuggestion) gate wrongly reported
  true). The empty-buffer → true direction stays covered by the existing test.

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

* refactor(cli): address doudouOUC review on #5145 (dedupe, rename, telemetry)

Three [Suggestion]-level items from the latest review pass.

- Extract `availableSuggestion`: the compound condition
  `(followup.state.isVisible || promptSuggestion) && (followup.state.suggestion ?? promptSuggestion)`
  was copy-pasted across the Tab/Right/Enter accept guards, both
  typing-dismiss guards, and the placeholder prop. Collapse them into one
  derived value so the sites can't drift apart. Behavior is unchanged
  (the controller keeps `isVisible` and `suggestion` in lockstep).

- Rename `dismissPromptSuggestion` -> `abortPromptSuggestion` across the
  UIState context, AppContainer, Composer, and the MainContent mock. The
  function only aborts in-flight generation/speculation and deliberately
  does NOT clear `promptSuggestion` (so the placeholder can restore it);
  the "dismiss" name implied the suggestion was gone.

- Omit `time_to_first_keystroke_ms` for fallback accepts. With
  `accept_source: 'fallback'` the suggestion was never shown via the timer
  (shownAt stayed 0), so `prevShownAtRef` still holds a previous
  suggestion's timestamp and the delta would be meaningless.

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

* feat(cli): actually enable followup suggestions by default

PR #5145 changed the schema default to `true`, but `mergeSettings` never
applies SETTINGS_SCHEMA defaults, so the runtime `=== true` gates left the
feature off while the settings panel read it as on (verified by wenshao).

- Flip both runtime gates to treat an unset value as enabled — only an
  explicit `false` opts out: `AppContainer.tsx` and the ACP `Session.ts`
  (`#maybeEmitFollowupSuggestion`).
- Add a Session test for the unset/default-on path.
- Fix the stale `UIStateContext` JSDoc left over from the dismiss→abort
  rename (it no longer clears state).
- Docs: mark the feature on-by-default, correct Enter (fills the input,
  does not submit), ghost-text → placeholder text, and add a cost note that
  `fastModel` forks to a separate cache and can cost more than the default
  main-model + shared-cache path on long conversations.

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

* fix(core): reject control chars and ANSI escapes in prompt suggestions

The follow-up suggestion is influenceable through conversation history
(tool/file/web output) and is rendered verbatim in the input placeholder
now that enableFollowupSuggestions defaults to on. Raw control bytes (CR,
ESC/CSI, C1) reached the terminal because getFilterReason only rejected
newlines and asterisks. Reject them at the source so the displayed and
inserted text always match.

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

* fix(cli): clear promptSuggestion on submit and accept paths

Addresses doudouOUC review on #5145. Since abortPromptSuggestion was
changed to preserve `promptSuggestion` for type-then-delete restore, the
submit and accept paths leaked stale suggestion text:

- handleSubmitAndClear only called followup.dismiss(); after a synchronous
  command (/clear, /help) that never triggers AppContainer's streaming
  transition, the placeholder kept showing the old suggestion.
- Tab/Right/Enter accept never cleared the prop, so clearing the buffer
  without submitting (Ctrl+U) made the accepted suggestion reappear as a
  ghost placeholder.

Both now call onPromptSuggestionDismiss?.() after the followup action. Also
reuse the availableSuggestion single-source-of-truth in hasTabConsumer
instead of an inlined parallel expression, and add useFollowupSuggestions
tests asserting the accept_source guard suppresses time_to_first_keystroke_ms
on fallback accepts.

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

* test(cli): assert promptSuggestion is cleared on accept and submit

Regression coverage for the state-leak fixed in 04fcffd1c (doudouOUC
Critical #1/#2, confirmed by wenshao's maintainer re-verification): Tab,
Right-arrow and Enter accepts plus message submit must each call
onPromptSuggestionDismiss, so the persisted promptSuggestion can't reappear
as a ghost placeholder when the buffer is next cleared.

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 13:39:12 +08:00
Zqc
abfdcc112b
fix(core): prevent OOM in auto-memory extraction during /quit (#5147) (#5181)
The auto-memory extraction background task caused FATAL ERROR: Reached
heap limit after /quit because buildTranscriptMessages() ran
.replace() over every history message synchronously, but the resulting
text was never consumed downstream.

Changes:
- extract.ts: delete buildTranscriptMessages/loadUnprocessedTranscriptSlice.
  Replace with a zero-stringify cursor scan on the unprocessed slice only.
- manager.ts: add isUnderMemoryPressure() using existing MemoryPressureMonitor.
  Gates in runExtract(), scheduleDream(), and scheduleSkillReview().
- client.ts: add shutdownRequested flag + requestShutdown().
- AppContainer.tsx: call requestShutdown() in /quit callback.
- Tests: 46 passed (11 extract + 34 manager + 2 client).
- Docs: update memory-system.md flowchart to cursor-first flow.

Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
2026-06-19 08:17:45 +08:00
Yan Shen
8d2fe0a798
feat(stats): expose token usage for cost visibility (#4564)
* feat(stats): expose token usage for cost visibility

Persist content-free API token counters and surface daily/monthly summaries plus CSV/JSON export through /stats.

Constraint: Issue #4479 requested CLI token visibility with monthly/model breakdowns and export while coordinating with #4252/#4182.\nRejected: Add a separate top-level token command | /stats keeps related statistics in one surface.\nConfidence: high\nScope-risk: moderate\nDirective: Keep TTFT/TPS generation timing and memory diagnostics outside this token-usage surface unless their issues explicitly broaden scope.\nTested: npx vitest run src/services/tokenUsageService.test.ts src/telemetry/loggers.test.ts; npx vitest run src/ui/commands/statsCommand.test.ts src/ui/hooks/useAutoAcceptIndicator.test.ts src/ui/components/AutoAcceptIndicator.test.tsx; npm run check-i18n --workspace=packages/cli; npm run lint --workspace=packages/cli; npm run lint --workspace=packages/core; npm run typecheck; npm run build; git diff --check\nNot-tested: full integration suite

* fix: Address token usage review feedback

Tighten persisted token usage so internal prompt traffic and disabled usage statistics do not write history, while surfacing non-ENOENT write failures outside debug logs. Complete the reviewer-requested i18n coverage and regression tests around auto mode notices and best-effort writes.

Constraint: Follow-up to wenshao review comments on PR #4564.

Rejected: Keeping token usage recording outside the internal-prompt gate | It would inflate daily and monthly stats with background prompts.

Confidence: high

Scope-risk: narrow

Directive: Keep /stats token usage scoped to user-visible API responses unless future requirements explicitly include background traffic.

Tested: npx vitest run src/services/tokenUsageService.test.ts src/telemetry/loggers.test.ts; npx vitest run src/ui/hooks/useAutoAcceptIndicator.test.ts src/ui/commands/statsCommand.test.ts; npm run typecheck; npm run lint --workspace=packages/core; npm run lint --workspace=packages/cli; npm run check-i18n --workspace=packages/cli; npm run build; git diff --check

Not-tested: Full repository test suite

* fix(stats): satisfy token usage review contract

Constraint: wenshao review required consistent token stats, exports, i18n, and best-effort logging behavior.

Rejected: Change cached-token labeling | keeping cached tokens included in input preserves the accepted /stats display contract.

Confidence: high

Scope-risk: narrow

Directive: Keep cached tokens included in input whenever cached-only metadata is used in total fallback.

Tested: cd packages/core && npx vitest run src/services/tokenUsageService.test.ts src/telemetry/loggers.test.ts

Tested: cd packages/cli && npx vitest run src/ui/commands/statsCommand.test.ts src/i18n/mustTranslateKeys.test.ts

Tested: npm run check-i18n --workspace=packages/cli; npm run typecheck; git diff --check

Not-tested: full integration suite

* Refine token usage recording after review

Constraint: Address wenshao's latest PR #4564 review suggestions without expanding the /stats command surface. Rejected: Keeping synchronous token-usage writes | sync I/O remains on the API response hot path. Confidence: high Scope-risk: narrow Directive: Keep token usage persistence best-effort and gated by explicit usage-statistics enablement. Tested: cd packages/core; npx vitest run src/services/tokenUsageService.test.ts src/telemetry/loggers.test.ts; cd packages/cli; npx vitest run src/ui/commands/statsCommand.test.ts; npm run typecheck; npm run build; npm run lint --workspace=packages/core; npm run lint --workspace=packages/cli; git diff --check Not-tested: Full repository test suite

* fix(stats): avoid silent zero usage on read failures

Propagate token usage read failures through the existing /stats error path while keeping missing usage files empty, and remove the unreachable telemetry wrapper catch.

Constraint: PR #4564 review requested user-visible read failures, full i18n for export errors, and removal of dead telemetry catch code.
Rejected: Adding warning fields to TokenUsageSummary | It would expand the JSON/export schema when the existing command error path already fits read failures.
Confidence: high
Scope-risk: narrow
Directive: Keep jsonl.read default swallowing behavior for existing session/history callers unless a user-visible caller opts into rethrowing non-ENOENT errors.
Tested: npx vitest run src/utils/jsonl-utils.test.ts src/services/tokenUsageService.test.ts src/telemetry/loggers.test.ts
Tested: npx vitest run src/ui/commands/statsCommand.test.ts
Tested: npm run check-i18n --workspace=packages/cli
Tested: npx prettier --check changed files
Tested: npm run typecheck
Tested: npm run lint --workspace=packages/core
Tested: npm run lint --workspace=packages/cli
Tested: git diff --check
Tested: npm run build
Not-tested: Full integration test suite

* fix: close token usage review gaps

Keep the review follow-ups local to token usage accounting and stats export without adding new abstractions.

Constraint: Address PR #4564 reviewer requests on token usage export/query reuse, write-failure stderr noise, and invalid-record diagnostics.
Confidence: high
Scope-risk: narrow
Directive: Keep token usage writes best-effort and avoid noisy stderr loops for repeated local failures.
Tested: git diff --check; prior targeted core/cli tests, typecheck, and lint passed for this working tree.
Not-tested: Full repository test suite.

* fix(stats): address token usage review feedback

* Update packages/core/src/services/tokenUsageService.ts

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* test(core): keep token usage stderr assertion current

Keep the repeated write-failure regression test aligned with the runtime wording that the PR now emits.

Constraint: PR #4564 CI failed after the implementation wording changed to "since last log".
Rejected: Reverting the implementation wording | it is the latest PR behavior and the failure is test-only.
Confidence: high
Scope-risk: narrow
Tested: cd packages/core && npx vitest run src/services/tokenUsageService.test.ts
Not-tested: full repository test suite

* fix(stats): clarify export review edge cases

Address the remaining PR review polish without changing token accounting, export formats, or path containment behavior.

Constraint: Review 4452925552 requested narrow documentation, ENOENT wording, and NOTICES cleanup only.

Rejected: Broader merge-conflict rework | GitHub currently reports the PR as mergeable, and the requested fixes are review polish.

Confidence: high

Scope-risk: narrow

Directive: Keep token usage records content-free and preserve export path validation semantics except for the final ENOENT message.

Tested: cd packages/core && npx vitest run src/services/tokenUsageService.test.ts; cd packages/cli && npx vitest run src/ui/commands/statsCommand.test.ts; npm run check-i18n --workspace=packages/cli; npm run typecheck; git diff --check on changed code and i18n files

Not-tested: Full test suite not run.

* fix: address token usage review feedback

* fix(stats): harden token usage CSV export

* fix(stats): remove unrelated auto mode noise

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-19 07:14:07 +08:00
曹潇缤
8fcc43943d
feat(channel): add QQ Bot (QQ机器人) channel adapter (#5202)
* feat(channel): add QQ Bot channel adapter

Add @qwen-code/channel-qqbot package implementing QQ Bot WebSocket
Gateway connection via the official QQ Bot API.

Supports:
- WebSocket Gateway (HELLO/IDENTIFY/HEARTBEAT/DISPATCH/RECONNECT)
- C2C single chat (C2C_MESSAGE_CREATE)
- Group @mention (GROUP_AT_MESSAGE_CREATE) — code path exists, unverified
- Streaming output via msg_id + msg_seq multi-block sending
- Auto-reconnect with exponential backoff
- Sandbox environment toggle

TODO (technical debt acknowledged):
- Group chat not verified end-to-end
- Single-file architecture (should split into gateway/send/auth modules
  like weixin channel)
- No tests (weixin has send.test.ts + media.test.ts)
- No typing indicator (onPromptStart/onPromptEnd not yet implemented)
- No channel instructions injection in connect()
- No structured error types

Closes #5201

* feat(qqbot): add QR login, group chat support with typed events

- Add QR code login via @tencent-connect/qqbot-connector with credential persistence
- Add Intent constants for C2C (1<<12) and GROUP_AT_MESSAGE (1<<25)
- Use QQGroupMessageEvent type in handleGroup instead of cast
- Remove resolved TODO comments for group chat verification
- Add msg_seq to send error log for debugging

* fix(qqbot): address PR review — lint errors, token refresh, security

- Use bracket notation for Record<string, unknown> to fix TS4111 lint errors
- Add chmodSync(credsFile, 0o600) for credential file permissions
- Implement token refresh at 80% TTL with expires_in tracking
- Fix RECONNECT opcode: use code 4000 + serverRequestedReconnect flag
- Fix connect() Promise: reject on close before READY via connectReject
- Log empty-token case in sendMessage, drain response body on error
- Clear chatTypeMap/replyMsgId/msgSeqMap in disconnect()
- Capture msgId at send-time to avoid race on replyMsgId
- Switch channel-registry.ts to Promise.allSettled (isolated channel failures)
- Add chatId validation (isValidChatId) to prevent SSRF

* fix(qqbot): add qqbot to build order, fix ESLint default-case

- Add packages/channels/qqbot to scripts/build.js buildOrder
  (CLI imports @qwen-code/channel-qqbot but it wasn't being built)
- Add default case to handleGatewayMessage switch

* feat(qqbot): prepend sender name in group messages for shared context

When sessionScope is set to 'thread', all group members share one
session. Prepending [senderName] helps the agent distinguish who
said what in the shared context.

* feat(qqbot): cross-server context continuation via SessionRouter persistence

- Persist SessionRouter mappings to disk via sessionsPath, surviving daemon restarts
- Persist QQ routing state (chatTypeMap, replyMsgId, msgSeqMap) to {name}-state.json
- Backup/restore global sessions.json on disconnect/connect to survive start.ts cleanup
- fixRestoredSessions() workaround for ACP LoadSessionResponse missing sessionId
- READY handler delays resolve() until restoreSessions() completes, preventing race

* feat(qqbot): add Session Resume + reconnect retry resilience

- Support WS session resume (RESUME opcode 6) on reconnect,
  falling back to full IDENTIFY when session is invalid
- Add reconnectWithRetry() loop: retries gateway fetch up to 5x
  with exponential backoff, then schedules 60s fallback retry
  (fixes silent death after GW HTTP 500)
- connect() now retries up to 3 times on initial failure
- Bump maxReconnectAttempts from 10 to 20
- Refresh token before each reconnect attempt

* fix(qqbot): address review feedback from wenshao

- fixRestoredSessions: use entry.target directly instead of tt.get(undefined)
  (fixes first restored session routing to wrong conversation when 2+ sessions)
- scheduleTokenRefresh: retry in 60s on token refresh failure, not just log
- sendMessage: move saveQQState() after chunk loop, avoid redundant disk I/O
- handleGroup: drop message when group_openid is missing instead of falling
  back to author.id (which would cause 404 on group message send)

* fix(qqbot): address 3rd review from doudouOUC (12 issues)

- QWEN_HOME: use getGlobalQwenDir() instead of homedir()
- name sanitization: prevent path traversal in file paths
- fetch timeouts: AbortSignal.timeout(15s) on all 3 fetch calls
- TOCTOU: writeFileSync with {mode: 0o600} instead of chmodSync after
- msg_seq gaps: only increment seq on send success, break on failure
- message dedup: seenMessages Map with 5min TTL cleanup timer
- disconnect: set disposed flag + flushQQState sync + clear timers
- heartbeat ACK: track lastHeartbeatAck, force close on 2x interval timeout
- reconnect exhaustion: FATAL log when max attempts reached post-connect
- debounced saveQQState: 500ms debounce, flush on disconnect
- handleGroup: skip [senderName] prefix for slash commands, log for audit
- disposed guard: connectGateway checks disposed before creating WS

* fix(qqbot): robustness round — RESUMED, token expiry, SSRF, disposed, typing stubs

- Handle RESUMED event on RESUME success (start heartbeat, restore sessions)
- Check token expiry before sendMessage, refresh if expired
- Tighten isValidChatId regex (remove . and /) to close path traversal
- Reset disposed flag in connect() for reusability
- Add onPromptStart/onPromptEnd stubs (QQ Bot has no typing API)
- Add robustness comments for splitText surrogate pairs, restoreQQState
  corruption, and senderId identity fragmentation across contexts

* refactor(qqbot): split into modules — api, accounts, login

Extract HTTP calls, credential I/O, and QR login into separate files
matching the weixin channel's architecture:

- api.ts: fetchAccessToken, fetchGatewayUrl, getApiBase, sendQQMessage
- accounts.ts: getCredsFilePath, loadCredentials, saveCredentials
- login.ts: qrCodeLogin (qrConnect wrapper)

QQChannel.ts drops inline fetch/credential/qrConnect logic and imports
from the new modules. Net -41 lines in the adapter.

* feat(qqbot): markdown message support (msg_type: 2)

Detect markdown syntax in AI responses and send as msg_type=2
with markdown.content field instead of plain-text msg_type=0.

Detection covers headers, code blocks, bold, italic, strikethrough,
inline code, links, and lists via a single regex.

* fix(qqbot): defensive patches from complete review

- reconnectWithRetry: guard against disposed channel to prevent infinite loop
- handleGroup: broaden @mention regex to match both legacy <@!id> and V2 <@openid>
- handleGroup: set isReplyToBot=true (every group msg is an @mention)
- fixRestoredSessions: document fragile private-field access
- saveCredentials: correct TOCTOU claim in comment
- hasMarkdownSyntax: document false-positive trade-off

* fix(qqbot): guard against empty content in C2C and group handlers

- handleC2C: return early when event.content is null/empty (image/sticker msgs)
- handleGroup: return early when cleanText is empty after @mention stripping

* fix(qqbot): close remaining review gaps — disposed guard, connectReject, token retry, RESUMED restore

* fix(qqbot): address wenshao review — RESUME restore removal, disposed guards, timer tracking, logging, heartbeat floor, requiredConfigFields, channel-registry error labels

* fix(qqbot): markdown fallback to plain text on rejection

* docs(qqbot): clarify markdown permission — Open Platform has no gate, FAQ is a different platform

* feat(qqbot): add Ark (msg_type=3) and Media (msg_type=7) message support

- types.ts: ArkKV, ArkPayload, FileType, MediaUploadRequest/Response, MediaPayload
- api.ts: uploadQQMedia() — file upload for rich media
- QQChannel.ts: sendArk(chatId, templateId, kv) + sendMedia(chatId, fileType, url, text?)
  - C2C/group upload paths separated (file_info not interchangeable)
  - file_type=4 (文件) blocked for groups per QQ API
  - Embed (msg_type=4) skipped — QQ频道专用, not available for Bot Open Platform

* feat(qqbot): auto-route !ark / !media commands from LLM text via sendMessage

LLM outputs text — the channel now parses structured commands inline:
  !ark(24, #TITLE#=标题, #META_DESC#=描述)
  !media(image, https://example.com/photo.jpg, caption text)

parseArkCommand / parseMediaCommand extract at sendMessage entry;
normal text/markdown flow unchanged.

* feat(qqbot): inject channel instructions for ark/media commands

Sets config.instructions on connect() so the LLM learns about:
  !ark(template_id, key=val, ...) — 3 default templates (23/24/37)
  !media(type, url, [caption])   — image/video/voice/file

Fixes known debt: 'No channel instructions'.

* feat(qqbot): gate ark/media behind config flags (enableArk/enableMedia)

Both features default to false — opt-in via settings.json:
  channels.my-qq.enableArk = true
  channels.my-qq.enableMedia = true

Instructions injected conditionally; command routing gated per-flag.

* refactor(qqbot): extract resolveRoute() to eliminate duplication across sendMessage/sendArk/sendMedia

disposed check, token refresh, chatId validation, sandbox path selection
now in one place. All three methods call resolveRoute() instead of
repeating the same 15-line preamble.

* chore(qqbot): remove Ark and Media message support

Remove !ark() / !media() text parsing, sendArk/sendMedia methods,
uploadQQMedia, and all related types. The text-parsing approach
was too fragile against LLM output formatting. Only text/markdown
messaging remains.

* fix(qqbot): robustness patches for review findings

- Add { mode: 0o600 } to all writeFileSync calls (state/session files)
- Guard against stale WebSocket close event nuking new connection
- Add isReconnecting guard to prevent parallel reconnectWithRetry chains
- Reset isReconnecting flag in READY, RESUMED, and exhaustion paths

* docs(channel): add QQ Bot user documentation

Add user-facing documentation for the QQ Bot channel adapter:

- New docs/users/features/channels/qqbot.md covering setup, configuration,
  QR code login, group chat, Markdown support, token management, connection
  resilience, and troubleshooting
- Update docs/users/features/channels/_meta.ts to include QQ Bot in nav
- Update docs/users/features/channels/overview.md to reference QQ Bot
  across the intro, quick start, type options, slash commands, and the
  media platform differences table

* docs(qqbot): fix prerequisites — QR login needs no developer account

QR code login via qrConnect() does not require a developer account or
manual app registration. First qwen channel start is all you need.

* docs(qqbot): emphasize QR login, keep developer portal as secondary path

Both paths work (config → persisted file → QR scan), confirmed against
fetchToken() code. Reposition QR code login as the primary setup flow,
remove redundant tips/troubleshooting entries.

* docs(qqbot): remove Images and Files section — not supported in channel code

handleC2C/handleGroup both skip messages with no text content.
No media download or upload logic exists in this channel adapter.

* test(qqbot): add unit tests for send utilities

Add vitest test suite for QQ Bot channel following the weixin channel
testing patterns. Extract isValidChatId, hasMarkdownSyntax, and splitText
as exported module-level functions to enable direct testing.

- 27 tests covering: chatId SSRF validation, Markdown syntax detection, and
  text chunking for QQ's 2000-char message limit
- Add vitest.config.ts and test script to qqbot package
- Register qqbot in root vitest workspace projects

Refs: #5202

* test(qqbot): add sendMessage flow tests with mocked API

Follow the weixin sendImage test pattern: mock sendQQMessage and
channel-base dependencies to test sendMessage end-to-end.

- C2C/group routing verification
- Markdown msg_type=2 vs plain text msg_type=0
- Markdown rejection fallback to plain text
- Disposed guard and error-stop behavior
- msg_id + msg_seq tracking for multi-chunk streaming

9 new tests, 36 total (all passing)

* test(qqbot): fix review issues — add missing edge cases

Self-review fixes:
- Fix misleading test name: 'returns early when chatId not in chatTypeMap'
  → 'defaults to C2C path for unknown chatId' (code doesn't return early)
- Add SSRF validation test: sendMessage rejects '../traversal' chatId
- Add network error test: thrown sendQQMessage caught by try/catch
- Add token expiration test: expired token + failed refresh → early return
- Hoist mockFetchAccessToken and set default resolved value in beforeEach
  to prevent silent undefined-access failures in accidental token-refresh paths

39 tests, all passing

* test(qqbot): add api and accounts unit tests

Add api.test.ts (13 tests) and accounts.test.ts (8 tests) following
weixin channel vitest patterns: vi.hoisted() mocks, vi.mock() module
replacement, and dynamic import() after mock setup.

api.test.ts covers getApiBase, sendQQMessage, fetchAccessToken, and
fetchGatewayUrl — including HTTP errors, missing fields, and request
body format.

accounts.test.ts covers getCredsFilePath, loadCredentials (missing file,
corrupt JSON, missing fields, valid data), and saveCredentials (dir
creation + 0o600 permissions).

All 60 tests pass (39 existing + 21 new). tsc --build and eslint clean.

* chore(qqbot): suppress CodeQL ReDoS false positives

Add codeql[js/polynomial-redos] suppression comments for two
regexes flagged by CodeQL:

- hasMarkdownSyntax(): input is LLM-generated reply text,
  never attacker-controlled in Qwen Code Channel context.
- handleGroup(): <@...> prefix is injected by QQ servers;
  openid is assigned by QQ, not attacker-chosen.

Both paths have no practical exploit vector — an adversary
would need to either control an LLM's output or register a
malicious openid with QQ, neither of which is achievable.

* fix(qqbot): allow QR-code-only login and guard qrConnect return

- requiredConfigFields: [] — fetchToken() already resolves credentials
  from config → persisted file → QR fallback chain. Blocking at config
  validation prevented QR-code-only users from starting the channel.
- qrCodeLogin(): add bounds check for empty qrConnect() return value.
  If the external library returns an empty array, throw descriptive
  error instead of crashing with TypeError on creds.appId.

* chore(qqbot): add comments for requiredConfigFields and qrConnect guard

- index.ts: explain why requiredConfigFields is empty — fetchToken()
  already resolves credentials via config → file → QR fallback chain.
  Requiring appID/appSecret at config level would block QR-only users
  from reaching the fallback through the built-in channel path.

- login.ts: clarify qrConnect() guard is a defensive robustness patch,
  not a response to an observed failure. Verified by removing appID
  from config and running qwen channel start — QR login triggers
  correctly and returns valid credentials.

* fix(qqbot): replace quadratic regexes with linear patterns, remove failed suppress comments

* fix(qqbot): split hasMarkdownSyntax into individual tests to pass CodeQL

* fix(qqbot): replace markdown link regex with indexOf to eliminate CodeQL ReDoS
2026-06-19 06:32:52 +08:00
tt-a1i
905041f940
docs(cli): document tmux scroll workaround (#5248) 2026-06-19 02:18:12 +08:00
qqqys
f4d405ca4a
feat(loop): wire prompt-only /loop to self-paced wakeups (#5197)
* feat(loop): add second-resolution session wakeup engine

Add a session-scoped wakeup primitive for self-paced /loop, aligned with
Claude Code's ScheduleWakeup. An independent, second-resolution channel in
CronScheduler — separate from cron jobs (never durable, not counted against
MAX_JOBS, fired at an exact time, not minute-rounded):

- scheduleWakeup(delaySeconds, prompt): clamps to [60, 3600]s (1200s default
  for non-finite input); returns {scheduledFor, clampedDelaySeconds, wasClamped}.
- Fires through the existing onFire channel and counts toward sessionSize, so
  there are no cli delivery-path changes and a pending wakeup holds a headless
  run open — re-arm keeps the loop alive, omitting the call ends it.
- cancelWakeup / cancelAllWakeups primitives (for loop-scoped cancellation).
- loop_wakeup tool: delaySeconds schema, structured clamp output, cache-window
  picking guidance, verbatim /loop prompt, reason shown to the user, and the
  "call to keep alive / omit to end" contract — all mirroring ScheduleWakeup.

getDefaultPermission stays 'ask' (out of SAFE_TOOL_ALLOWLIST) so AUTO still
routes scheduling future model input through the classifier, like CronCreate.

Closes #5156

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(loop): tighten session wakeup lifecycle

* fix(loop): address wakeup review nits

* feat(loop): wire prompt-only /loop to self-paced wakeups

Make `/loop <prompt>` (no interval) a self-paced loop in the bundled loop
skill: run the prompt immediately, then schedule at most one future
continuation via loop_wakeup (delaySeconds) — no recurring cron.

- Three explicit paths: prompt-only self-paced (LoopWakeup), fixed-interval
  recurring (CronCreate), and list/clear management (CronList/CronDelete).
- The continuation uses delaySeconds (aligned with the second-resolution
  wakeup engine) and re-feeds `/loop ${original prompt}` verbatim to re-enter
  the skill; the model re-arms only when a further check is useful.
- Adds loop_wakeup to the skill's allowedTools.
- Static SKILL contract tests, including delaySeconds (not delayMinutes).

Closes #5184

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(loop): clarify wakeup delay reporting

* fix(loop): make wakeups manageable

* fix(loop): address wakeup review feedback

* fix(loop): clarify wakeup management wording

* fix(loop): clarify wakeup continuation tooling

* fix(loop): bound self-paced wakeup chains

* fix(loop): distinguish wakeup fires in lists and UI

* test(loop): cover wakeup labels in cli paths

* fix(loop): enforce session wakeup chain limit

* fix(loop): align wakeup delay metadata

* fix(loop): handle stopped wakeup scheduling

* fix(loop): make the wakeup chain limit a true session-level budget

The 24h chain limit reset `wakeupChainStartedAt` whenever `wakeups`
emptied — on every fire and on cancel. Because a self-paced loop leaves
at most one pending wakeup, each fire emptied the map and restarted the
clock, so a continuous re-arming loop never reached the cap (and a
cancel-then-reschedule could reset it too).

Reset the chain clock only on stop()/destroy() (a new session): the 24h
budget now spans the whole session, bounds continuous re-arming, and
closes the cancel bypass. Tests cover the clock persisting across fires,
cancel not resetting it, and stop starting a fresh budget.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(loop): correct CronList durability wording and bound wakeup prompt

Address two review suggestions on #5197:

- CronList tool description said cron jobs and loop wakeups are "both
  session-only and durable", implying wakeups can be durable. Loop
  wakeups are always session-only; only cron jobs can be durable.
  Reword so the model isn't misled into expecting durable wakeups.
- LoopWakeup `prompt` had no maxLength, unlike sibling tools
  (task-create, send-message). Add maxLength: 10000 to bound the
  model-generated continuation prompt.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(loop): let the first wakeup arm before the scheduler starts

Critical (wenshao, #5197): LoopWakeup hard-rejected when
`scheduler.running` was false, but on the first self-paced /loop in a
session with no cron jobs the scheduler hasn't started yet
(#startCronSchedulerIfNeeded bails on !hasPendingWork). The post-prompt
hook starts the tick *after* the turn, once a wakeup exists — so the
guard rejected the very call that makes the loop possible, breaking the
primary use case.

The `running` check was a proxy for "cron is alive", added to reject
re-arms after the token-limit breaker. Replace it with an explicit,
permanent `disabled` state so the two cases are distinguishable:

- CronScheduler gains `disabled` + `disable()` (sets the flag, stops).
- LoopWakeup rejects only when `scheduler.disabled`, not when merely
  stopped — a stopped-but-restartable scheduler still accepts wakeups.
- The token-limit breaker calls `disable()` instead of `stop()`, so its
  rejection (the original intent) is preserved.

Also attribute cron-prompt errors by source: `[loop error]` vs
`[cron error]` (item.source was already in scope).

Tests: reject-when-disabled, schedule-when-stopped (the regression),
and a disable() unit test.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(loop): clear the pending wakeup when a re-arm exceeds the 24h budget

Critical (round-4 review, #5197): scheduleWakeup() threw the 24h-limit
error *before* clearing the prior wakeup, so a rejected re-arm left the
previous wakeup in the map. Its fireAtMs is now in the past, so the next
tick fires it — one iteration past the budget it's meant to cap. A test
even codified this (sessionSize === 1 after a rejected re-arm).

Production can't actually reach it (the 1s tick fires the wakeup at
~+3600s, and a stopped scheduler clears wakeups), but the safety budget
should hold unconditionally. Clear the pending wakeup up front, before
the budget check, so a rejected re-arm leaves nothing behind. Update the
test to assert no wakeup remains.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(loop): update Session token-limit test for the disable() breaker

CI regression from the disable() refactor (7f488e6c6): the token-limit
breaker now calls scheduler.disable() instead of stop(), but the
cron-fired token-limit test still asserted scheduler.stop and its mock
scheduler had no disable() — so the call hit an undefined method and the
stop spy saw 0 calls. Missed because the prior change ran core tests +
cli tsc but not the cli test suite (Session.test.ts is cli).

Add disable() to the mock and assert it's called once. The breaker
disables (permanent for the session, so a later LoopWakeup is rejected),
which internally stops too.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* refactor(loop): enforce disabled guard in scheduleWakeup, unexport SessionWakeup

Two review suggestions on #5197:

- scheduleWakeup() now rejects when the scheduler is disabled, enforcing
  the invariant at the layer that owns `_disabled`. A disabled scheduler
  never fires, so a wakeup scheduled past it would be a silent zombie.
  LoopWakeup still pre-checks `disabled` for a friendly message; this
  guards any other caller. (The CHANGES_REQUESTED "missing disable mock"
  critical was already resolved in 240e8892b — verified Session.test
  128/128.)
- SessionWakeup is only used inside cronScheduler.ts (private map +
  file-local wakeupToJob); drop the unused `export` to keep the public
  surface minimal.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(loop): start the scheduler in finally so a post-arm error can't strand the loop

Review (doudouOUC, #5197): #startCronSchedulerIfNeeded() ran only in the
success path of prompt(), after #executePrompt() resolved. If a turn armed
a wakeup via LoopWakeup and then threw on a later step (e.g. an API error),
the call was skipped and the scheduler never started — the self-paced loop
died silently until the next successful user prompt.

Move it into the finally block. It's idempotent (start() no-ops when the
timer exists) and gated on hasPendingWork/disposed/disabled, so it only
starts when a wakeup or cron job is actually pending. Verified the existing
128 Session tests still pass (success path unchanged).

Also fix a test-type drift flagged in the same review: SessionInternals
typed cronQueue as string[], but the implementation switched it to
CronQueueItem objects ({ prompt, source }); type it accordingly and push a
proper object.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(loop): add zh/zh-TW display-name translations for LoopWakeup

After merging main, its i18n coverage test ("has a zh translation for
every core tool display name") failed on all 3 OS: the new LoopWakeup
tool had no toolDisplayName entry, so localizeToolDisplayName fell back
to the English name. Add 循环唤醒 / 循環喚醒, plus the en.js parity key
so check-i18n passes.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-18 18:36:24 +08:00
Shile Zhang
daec44a4f3
feat(hooks): pass original API call ID (toolCallId) to hook system (#4918)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
- Add toolCallId field to PreToolUse/PostToolUse/PostToolUseFailure hook inputs
- Thread toolCallId from LLM API response (toolCall.id) through to hook subprocess stdin
- Fix message bus handler in config.ts to forward toolCallId (was silently dropped)
- toolCallId is optional — only included when the LLM provider supplies an ID

Signed-off-by: Shile Zhang <shile.zhang@linux.alibaba.com>
2026-06-18 08:20:38 +00:00
jifeng
858c900af9
feat(serve): add daemon idle detection to GET /health?deep=true (#4934)
* docs(design): add daemon idle detection API design for machine reclamation

When qwen daemon is deployed across multiple machines, an external
scheduler needs a reliable signal to determine if a daemon is idle
and the machine can be reclaimed. This design proposes enhancing
GET /health?deep=true with activePrompts, connectedClients,
channelAlive, lastActivityAt, and idleSinceMs fields.

* feat(serve): add idle-detection fields to GET /health?deep=true

* fix(test): add activePromptCount and lastActivityAt to fakeBridge

Update the test helper to satisfy the expanded AcpSessionBridge
interface so /health?deep=true tests pass with the new fields.

* test(idle-detection): add unit tests for activePromptCount, lastActivityAt and /health?deep=true new fields

* refactor(bridge): replace activePromptCount iteration with O(1) counter

* fix(bridge): move idleSinceMs computation into bridge to eliminate race window

* fix(idle-detection): keep daemon health state consistent

Guard prompt teardown so activePromptCount only decrements once and compute deep health idle fields from the same activity snapshot.

* fix(acp): prevent active prompt leak after channel crash

Reject queued prompts after their session entry has been torn down so daemon health does not report phantom active prompts.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-18 06:55:03 +00:00
tt-a1i
19307eb0a6
fix(cli): support ctrl np in completions (#5259) 2026-06-18 14:36:27 +08:00
Dragon
5c87b5649e
docs: fix SSE ring size errors and add /workflows command (#5205)
* docs: fix SSE ring size errors and add /workflows command

- qwen-serve.md: fix SSE ring size (4000 → 8000) in 4 locations
- qwen-serve-protocol.md: fix SSE ring size (4000 → 8000)
- commands.md: add /workflows command to tool management table

The SSE ring default is 8000 frames (not 4000) per the implementation
in packages/cli/src/serve/server.ts. The /workflows command inspects
workflow runs from the WorkflowRunRegistry.

* docs: fix missed SSE ring size reference (4000 -> 8000)

Address review feedback on #5205: daemon-client-quickstart.md still
referenced the old 4000-event ring buffer. The daemon ring buffer
default is DEFAULT_RING_SIZE = 8000 (packages/acp-bridge/src/eventBus.ts).

* docs: resolve circular ring-size wording in Stage 1.5 list

Address review feedback on #5205: after the 4000->8000 correction, item 5
read circularly (default 8000 ... need 8000+). The ring default has shipped
at 8000 (DEFAULT_RING_SIZE / eventRingSize default), so mark the size bump
done (strikethrough, matching item 3) and scope remaining work to
per-session configurability, which is still open (eventRingSize is a single
daemon-construction value, not settable per session).
2026-06-18 09:43:38 +08:00
Dragon
b6febe0856
docs: add CLI subcommands section with qwen sessions list (#5254)
- Add section 5 to commands.md for CLI-level subcommands
- Document qwen sessions list with --json and --limit flags
- Include output format, examples, and usage patterns

The sessions list command was added in commit 14e6ae8c2 but not documented.
2026-06-18 08:59:09 +08:00
jinye
49c43cf53f
feat(cli): Add daemon status API (#5174)
* feat(cli): add daemon status API

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5174)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: simplify daemon status tests (#5174)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): tighten daemon status test mock types

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-16 13:07:40 +08:00
joeytoday
f27e0afa45
docs(channels): add screenshots to Feishu setup guide (#4983) 2026-06-16 04:34:03 +00:00
Dragon
3408c32110
docs: fix MCP token path, daemon UI event count, add Feishu channel (#5172)
* docs: fix MCP token path, daemon UI event count, add Feishu channel

- mcp.md: update OAuth token storage path to v2 encrypted file
  (mcp-oauth-tokens-v2.json with AES-256-GCM, keychain preferred)
- daemon/00-index.md: fix UI event type count (36 → 37)
- daemon/01-architecture.md: add Feishu to channel bots diagram
- daemon/15-channel-adapters.md: add Feishu adapter (WebSocket/webhook,
  config keys, permission UX, reverse-call caveat)

* docs: correct Feishu config keys and permission UX caveat

Address review feedback on 15-channel-adapters.md:
- Feishu config keys are clientId/clientSecret (not appId/appSecret),
  per FeishuAdapter.ts:116-118; add encryptKey (required for webhook
  mode, enforced at FeishuAdapter.ts:165-168).
- Note that channel permission UX currently auto-approves via AcpBridge
  (AcpBridge.ts:111); interactive approval is planned.

* docs: fix missed sibling references for audit consistency

Address review feedback (wenshao) — apply the same corrections to other
doc locations that referenced the stale values:
- MCP token path: also fix docs/developers/tools/mcp-server.md (was still
  ~/.qwen/mcp-oauth-tokens.json).
- UI event type count 36 -> 37: also fix 14-cli-tui-adapter.md (3 spots),
  13-sdk-daemon-client.md.
- Feishu channel: add Feishu rows to both tables in 15-channel-adapters.md
  (Per-channel adapters + Adapter matrix) and the package-level Adapters
  subgraph in 01-architecture.md, so prose/tables/diagrams all agree.
- Add a note under the Adapter matrix clarifying that Permission UX /
  approvalMode are not wired yet (all channels auto-approve via AcpBridge).
2026-06-16 11:37:43 +08:00
Shaojin Wen
e97be6c669
fix(agent): stop forking result-bearing work; keep omitted subagent_type awaitable (#5155)
Enabling fork-by-default for every interactive session (#4963) made an omitted `subagent_type` mean a fire-and-forget background fork. A fork's findings never flow back into the parent turn, so any caller that spawns parallel agents and then aggregates their results breaks — the bundled `review`/`simplify` skills, third-party skills, and the model's own delegation. The orchestrator waits forever for results that never arrive; nudged by the periodic todo reminder it can also spin on identical tool calls until DashScope rejects the request with `InternalError.Algo.InvalidParameter: Repetitive tool calls detected` (HTTP 400).

Make forking an explicit, deliberate choice and stop steering the model toward it when it needs the results:
- Dispatch: `subagent_type: "fork"` selects a fork (interactive only; otherwise general-purpose). Omitting `subagent_type` always resolves to the awaitable general-purpose subagent whose result returns inline — never a fork. `/fork` passes `subagent_type: "fork"` explicitly.
- Prompt: drop the "launch parallel forks for research" guidance, reframe the tool description and "When to fork" around "never fork work whose output you need", and stop advertising `fork` in the subagent_type enum (it stays valid via validation for `/fork` and intentional use).
- Skills: `review` and `simplify` launch their parallel agents with `subagent_type: "general-purpose"` and are told explicitly not to fork, since they aggregate findings.

Forking stays a first-class, default-available feature for genuinely fire-and-forget work — it just isn't the silent default, an enum pick, or something the model is nudged toward when it must read the results.
2026-06-16 09:38:10 +08:00
jinye
1d2ee34f44
docs(daemon): Refresh daemon docs in English (#5144)
* docs: Translate daemon developer docs to English

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: Align daemon docs with main implementation

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: apply auto-fixes from /review

- eventRingSize default: 1024 → 8000 (01-architecture.md)
- Restore full 8-step middleware chain in HTTP lifecycle diagram
- Hysteresis re-arm condition: <= → < (06-mcp-budget-guardrails.md)
- permissionMediator.ts line range: 1-1292 → 1-1198
- FsErrorKind count: 13 → 14 (07-workspace-filesystem.md)
- Remove broken links to design/f2-mcp-transport-pool.md

* docs: Smooth daemon developer docs language

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: Sync daemon capability docs

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5144)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5144)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5144)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-15 22:38:01 +08:00