qwen-code/packages/cli
Shaojin Wen 76addf4ef6
fix(serve): keep skill slash commands available when the ACP child is unavailable (#6169)
* fix(serve): keep skill slash commands available after the ACP child is reaped

`GET /workspace/skills` is answered exclusively by the ACP child — the
daemon has no local SkillManager. `requestWorkspaceStatus` only checks for
an already-live channel (`liveChannelInfo()`, never `ensureChannel()`), so
when no child is running it returns the idle placeholder
(`initialized: false`, empty `skills`).

That is the norm before the first session, and — crucially — again after
the child is reaped on session close, which happens immediately by default
(`--channel-idle-timeout-ms` defaults to 0 = immediate kill). Unlike
`/workspace/providers`, skills have no daemon-local status provider to fall
back on. So once a user has created and closed a session, every subsequent
pre-first-prompt `/workspace/skills` query returns empty, the Web Shell's
slash-command list falls back to the hardcoded built-ins (which omit
skills), and `/rev` stops autocompleting `/review`.

Retain the last skills status a live child produced and replay it while no
channel is live, so skill-backed slash commands keep autocompleting; the
next live query refreshes the cache. `initialized` cleanly separates a real
child answer (always `true`) from the idle placeholder (always `false`).

Completes #6153, which wired the Web Shell to fetch `/workspace/skills` in
the deferred-connect path but could not surface skills the daemon was
unable to answer without a live child.

* fix(serve): enumerate workspace skills locally when the ACP child is unavailable

The cache from the previous commit keeps the last child answer alive across
a reap, but it never warms when the child never answers at all — most
visibly under `npm run dev`, where the on-demand-transpiled child's
`initialize` handshake routinely exceeds the 10s preheat budget, so preheat
times out and no channel ever comes up. `/workspace/skills` then stays empty
until the first prompt, dropping `/review` and every other skill from the
Web Shell's pre-first-prompt autocomplete even though the skills exist on
disk (typing `/review` in full still runs it, since submitting spawns a
session — hence "not in the list, but usable").

Add a daemon-local skills provider that enumerates skills straight from the
filesystem via SkillManager (a lightweight Config shim — no child, no MCP
init), mirroring the existing daemon-local providers-status provider. The
facade falls back to it only after both a live child answer and the cached
last answer are unavailable, so the live child stays authoritative (and
keeps extension-provided skills) while a never-preheated child still yields
the on-disk skills — `/review` included.

* fix(serve): fall back to cached/local skills when the child query throws mid-flight

Addresses review feedback on #6169: the channel can die after
`liveChannelInfo()` returns a valid channel but before the RPC completes, so
`queryWorkspaceStatus` rejects. Previously that exception propagated even
though the cache or the daemon-local provider could still answer. Wrap the
query in try/catch (logging via writeStderrLine, matching
getWorkspaceEnvStatus / getWorkspacePreflightStatus) and treat a mid-flight
failure as "no live child", so the request degrades to the cached last answer
or daemon-local enumeration instead of failing.

* refactor(serve): address review feedback on daemon-local skills provider

- Extract the SkillConfig → ServeWorkspaceSkillStatus mapping into a shared
  workspace-skills-mapping module used by both the ACP child's
  buildWorkspaceSkillsStatus and the daemon-local provider, so the two skill
  listings can't drift; cover it (including the disable-model-invocation
  branch) with a unit test.
- Memoize the SkillManager per workspace so repeat queries reuse its in-memory
  cache instead of re-scanning every skill level on each call.
- Honor the safe-mode env (isSafeModeEnv, as Config does) instead of hardcoding
  isSafeMode to false; keep bareMode off (the daemon never runs `--bare`).
- Log daemon-local enumeration failures via writeStderrLine, matching the rest
  of the workspace-service error handling.

* test(serve): cover daemon-local skills error path; guard the facade provider call

- Test the previously-uncovered `buildWorkspaceSkillsStatus` catch branch:
  when enumeration fails it returns `{ initialized: false, skills: [],
  errors: [{ kind: 'skills', status: 'error', error }] }` and logs to stderr.
  Also cover the per-workspace SkillManager memoization.
- Wrap the facade's `workspaceSkillsStatusProvider` call in try/catch so a
  throwing injected provider degrades to the idle placeholder instead of
  failing the request (matching getWorkspaceEnvStatus / getWorkspacePreflightStatus),
  with a facade test for the throw path.
2026-07-02 11:31:36 +00:00
..
src fix(serve): keep skill slash commands available when the ACP child is unavailable (#6169) 2026-07-02 11:31:36 +00:00
index.ts perf(cli): Optimize serve daemon startup (#5785) 2026-06-24 17:50:31 +08:00
package.json chore(release): v0.19.4 (#6132) 2026-07-01 13:13:47 +00:00
test-setup.ts perf(cli): code-split lowlight to cut startup V8 parse cost (#4070) 2026-05-15 17:26:18 +08:00
tsconfig.json feat(cli): Add daemon-managed channel worker for serve --channel (#6031) 2026-07-01 01:40:54 +00:00
vitest.config.ts fix(cli): Guard serve fast-path bundle closure (#5995) 2026-06-29 14:38:32 +00:00