mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-25 08:33:55 +00:00
893 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
a4eab3d39c
|
fix(core): allow web_fetch JSON fallback (#5660) | ||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
099b47edca
|
fix(core): require integer microcompaction keep count (#5652) | ||
|
|
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.
|
||
|
|
c9b5c99e44
|
feat(serve): Add remote LSP status route (#5741) | ||
|
|
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. |
||
|
|
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. |
||
|
|
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 |
||
|
|
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.
|
||
|
|
3098717525
|
docs(mcp): correct mcp add scope default (#5593) | ||
|
|
a8863c203c
|
refactor(cli): Finish serve kebab-case filenames (#5604) | ||
|
|
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). |
||
|
|
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 |
||
|
|
75fc0a5c18
|
feat(extensions): support archive install sources (#4909)
* feat(extensions): support archive install sources * fix(core): harden extension archive installs |
||
|
|
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> |
||
|
|
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).
|
||
|
|
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> |
||
|
|
977313b5ae
|
fix(cli): parse force hyperlink override strictly (#5489) | ||
|
|
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
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
b773b895c2
|
feat(cli): show optional response token rate (#5401) | ||
|
|
73e6c7ef0f
|
fix(cli): clarify cumulative statusline token labels (#5400) | ||
|
|
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
|
||
|
|
a8eb824fb7
|
feat(config): add settings file change detection via chokidar watcher (#3696) (#4933)
Co-authored-by: heyang.why <heyang.why@alibaba-inc.com> |
||
|
|
3cecd667d5
|
fix(core): validate grep result limits (#5389)
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
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
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
905041f940
|
docs(cli): document tmux scroll workaround (#5248) | ||
|
|
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 (
|
||
|
|
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> |
||
|
|
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> |
||
|
|
19307eb0a6
|
fix(cli): support ctrl np in completions (#5259) | ||
|
|
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). |
||
|
|
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
|
||
|
|
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> |
||
|
|
f27e0afa45
|
docs(channels): add screenshots to Feishu setup guide (#4983) | ||
|
|
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). |
||
|
|
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. |
||
|
|
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> |