Skip to content

fix(cli): prevent file paths from being treated as slash commands - #3743

Merged
yiliang114 merged 11 commits into
QwenLM:mainfrom
yiliang114:fix/slash-command-file-path-1804
May 6, 2026
Merged

fix(cli): prevent file paths from being treated as slash commands#3743
yiliang114 merged 11 commits into
QwenLM:mainfrom
yiliang114:fix/slash-command-file-path-1804

Conversation

@yiliang114

@yiliang114 yiliang114 commented Apr 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #1804.

Slash-prefixed path inputs whose first token contains a path separator, such as
/api/apiFunction/接口的实现 and /Users/name/path, should be sent to the
model as normal prompts instead of being consumed as unknown slash commands.

This PR intentionally keeps the scope narrow: it does not change the behavior
for single-token unknown slash commands or broader unknown /token ...args
routing.

Root Cause

isSlashCommand() previously treated every /-prefixed input as a slash
command, except JavaScript-style comments. A path-like prompt such as
/api/apiFunction/接口的实现 therefore entered slash-command handling and ended
as Unknown command.

Fix

  • Detect path-like slash input by checking whether the first slash token
    contains / or \.
  • Apply the same path-like check in handleSlashCommand() so direct slash
    processing and lexical slash detection agree.
  • Preserve existing slash-command behavior for real commands, aliases, and
    bare /.
  • Count path-like slash prompts as real model-sent user turns in rewind/history
    mapping by using isSlashCommand() instead of a raw startsWith('/')
    heuristic.
  • Tighten the slash-command hook test action mock so it satisfies the current
    SlashCommandProcessorActions interface.

Out Of Scope

  • Unknown single-segment slash inputs such as /data, /README.md, or
    /data foo.
  • Queue draining semantics for mixed prompt/slash-command queues.
  • Explicit sentToModel provenance for slash commands that submit prompts to
    the model.

Those are separate routing/history design questions and should be reviewed in a
follow-up PR rather than mixed into this targeted #1804 fix.

Reviewer Test Plan

  • /api/apiFunction/接口的实现 is sent to the model rather than producing
    Unknown command.
  • /Users/name/path 帮我看一下 is sent to the model rather than producing
    Unknown command.
  • /help, /?, and bare / still stay in the slash-command flow.
  • Rewind/history mapping counts a model-sent path-like slash prompt as a real
    user turn.

Verification

  • cd packages/cli && npx vitest run src/ui/utils/commandUtils.test.ts src/ui/utils/historyMapping.test.ts src/ui/hooks/slashCommandProcessor.test.ts
  • direct tsc check for src/ui/hooks/slashCommandProcessor.test.ts
  • npm run typecheck
  • npm run lint

…enLM#1804)

When users input file paths starting with '/' (e.g. '/api/apiFunction/...',
'/Users/name/path'), they were incorrectly parsed as slash commands, resulting
in "Unknown command" errors. The input was discarded instead of being sent to
the model for processing.

Root cause: isSlashCommand() only checked for a '/' prefix without validating
whether the first token actually looks like a command name. Any '/' prefix
triggered the slash command flow, and when no matching command was found, the
error was shown with no fallback.

Fix: Add looksLikeCommandName() that validates command names contain only
[a-zA-Z0-9:_-]. Both isSlashCommand() and handleSlashCommand() now check the
first token — if it contains path separators, dots, or non-ASCII characters,
the input falls through to normal model processing instead of the command
dispatcher.

Closes QwenLM#1804
Address review feedback:
- Allow '.' in looksLikeCommandName() regex to support extension-qualified
  commands like gcp.deploy (CommandService renames conflicts as ext.cmd)
- Add regression tests for dot-named commands in both commandUtils and
  slashCommandProcessor
- Fix prettier formatting in slashCommandProcessor test file
@yiliang114

yiliang114 commented Apr 29, 2026

Copy link
Copy Markdown
Collaborator Author

Review follow-up

Addressed review feedback in 776a2ab:

  1. Dot-command regression fixedlooksLikeCommandName() now allows . in the pattern ([a-zA-Z0-9:._-]) to support extension-qualified commands like gcp.deploy. Added regression tests.
  2. Prettier formatting fixedslashCommandProcessor.test.ts reformatted.

Test results

commandUtils.test.ts:               46 tests passed
slashCommandProcessor.test.ts:       41 tests passed
useCommandCompletion.test.ts:        19 tests passed
Full CLI package (303 files):      4707 tests passed, 0 failed
Prettier check:                      All files pass

Terminal capture (local)

Recorded via integration-tests/terminal-capture framework (scenarios/bugfix-1804.ts):

Step Input Behavior
1 /api/apiFunction/接口的实现 Sent to model as normal prompt (no "Unknown command" error)
2 /Users/xxx/Desktop/.../dw-operator-skill 帮我安装 Model processes path correctly
3 /help Real slash command still executes normally

7 screenshots + animated GIF generated in scenarios/screenshots/bugfix-1804-slash-path/.
streaming

Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts Outdated
Comment thread packages/cli/src/ui/utils/commandUtils.ts Outdated
Comment thread packages/cli/src/ui/utils/commandUtils.ts Outdated
Comment thread packages/cli/src/ui/utils/commandUtils.ts Outdated

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Typecheck/build verification found two blocking issues that cannot be anchored to diff lines because GitHub does not expose those unchanged test-helper lines in the PR diff.

[Critical] packages/cli/src/ui/hooks/slashCommandProcessor.test.ts:157 — The setupProcessorHook helper passes an incomplete SlashCommandProcessorActions object to useSlashCommandProcessor. The interface now requires additional actions such as openManageModelsDialog, handleResume, openDeleteDialog, openExtensionsManagerDialog, openMcpDialog, openHooksDialog, and openRewindSelector, so TypeScript reports TS2345 and the PR fails typecheck/build verification. Add vi.fn() stubs for the missing required fields.

[Critical] packages/cli/src/ui/hooks/slashCommandProcessor.test.ts:1071 — The inline SlashCommandProcessorActions object in the lifecycle test has the same issue and omits the required action properties. Add the same missing stubs here as well, or reuse a typed helper so future interface additions only need to be updated in one place.

— gpt-5.5 via Qwen Code /review

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Review follow-up validation

I rechecked the review feedback rather than applying it mechanically. The final behavior keeps command-like Unicode names and aliases valid, and only falls back to model input for path-like values when they are not known loaded commands or aliases.

Fresh local verification on a13d5baa5:

npm run lint:ci       PASS
npm run typecheck     PASS
npm run test          PASS
npm run build         PASS

Additional focused regression run:

cd packages/cli && npx vitest run \
  src/ui/utils/commandUtils.test.ts \
  src/ui/hooks/slashCommandProcessor.test.ts

2 files passed, 92 tests passed

Full workspace test totals from npm run test:

packages/cli:                  303 files, 4713 passed, 7 skipped
packages/core:                 253 files, 6237 passed, 2 skipped
packages/sdk-typescript:         6 files,  201 passed
packages/vscode-ide-companion:  40 files,  278 passed, 1 skipped

I also reran the terminal-capture style smoke flow. The runner completed, but its automated slash-input submission is noisy for /? and root-slash completion, so I am not treating those screenshots as the primary evidence. The reliable coverage for the review cases is in the focused command utility and slash command processor regression tests above.

@wenshao

wenshao commented Apr 30, 2026

Copy link
Copy Markdown
Collaborator

Re-review: PR #3743 — Prevent file paths from being treated as slash commands

Overview

Closes #1804. When users type /api/apiFunction/接口的实现 or /Users/name/path, the existing isSlashCommand() only checked for a / prefix and let unmatched paths fall through as "Unknown command" errors. This PR adds a token-shape validator (looksLikeCommandName) and a single-segment absolute-path heuristic so path-like inputs are sent to the model as ordinary prompts, while real slash commands, aliases, Unicode names, and extension-qualified names (gcp.deploy) still work.

Files: commandUtils.ts (+32/-1), commandUtils.test.ts (+92/-1), slashCommandProcessor.ts (+72/-1), slashCommandProcessor.test.ts (+100/-0). 92 focused regression tests pass; full workspace typecheck/lint/build also pass on pr-3743 locally.

Code quality

  • Iterative response to review is good. Across four commits the author addressed the previous critical comments: bare / is preserved, Unicode names accepted, ? alias accepted, single-segment absolute paths fall through. Each fix has a regression test.
  • Regex choice is sound. /^[\p{L}\p{N}\p{M}:._?-]+$/u covers Unicode letters/numbers/marks plus the punctuation actually used in command names. Better than the original ASCII allowlist.
  • Tests are thorough and readable. The new specs cover Unicode, dot-qualified, MCP-style, alias, bare-slash, and several path-shaped variants.
  • Comments are clear and accurate. The docstring on isSlashCommand and the inline comment explaining the bare-slash carve-out justify the behavior nicely.

Verification of prior review concerns

  • The earlier "typecheck/build will fail" claim was a false positive. packages/cli/src/ui/hooks/slashCommandProcessor.test.ts is in the exclude list of packages/cli/tsconfig.json (alongside many other test files marked TODO(5691)), so the missing actions in setupProcessorHook (openManageModelsDialog, handleResume, openDeleteDialog, openExtensionsManagerDialog, openMcpDialog, openHooksDialog, openRewindSelector) do not produce TS2345. Re-ran npm run typecheck and npm run build on the PR branch — both succeed. That CHANGES_REQUESTED review citing typecheck failure should be retracted; the diagnosis was wrong, apologies for the noise.

Suggestions

  1. Pragmatic but brittle: COMMON_ABSOLUTE_PATH_ROOTS is a hardcoded allowlist (packages/cli/src/ui/hooks/slashCommandProcessor.ts:81). It misses legitimate single-segment paths like /data foo, /vendor explain, /workspace check, custom mounts, project-relative dirs, etc. — those still hit "Unknown command". Consider inverting the rule: treat a / prefix with extra args as a slash command only if the first token is an actually loaded command/alias (you already compute isKnownTopLevelCommandToken); otherwise fall through. That eliminates both the hardcoded set and the file-extension regex, and fixes the /data foo class of inputs as well.

  2. Path-detection logic is split across two files. commandUtils.ts owns looksLikeCommandName, but the absolute-path roots and file-extension regex live in slashCommandProcessor.ts. Future maintainers will have to touch both. Either move the helpers to commandUtils.ts, or make isSlashCommand accept the loaded commands list and make a single, consistent decision.

  3. Minor inconsistency: isSlashCommand('/tmp inspect') returns true but handleSlashCommand returns false. Today this is harmless because callers (useGeminiStream, useMessageQueue, AppContainer) all defer the final decision to handleSlashCommand's return value. But useCommandCompletion.tsx:129 enters SLASH completion mode on the truthy isSlashCommand, so typing /tmp inspe… opens the slash autocomplete dropdown for a path-like input, which is mildly user-confusing. Centralizing the decision (suggestion pre-release: fix ci #1) fixes this too.

  4. SINGLE_SEGMENT_FILE_RE = /^[^\s/\\]+\.[A-Za-z0-9]{1,16}$/ matches anything with a dot and 1–16 alphanum chars after it. That includes gcp.deploy1 (already covered as a known command) and mycmd.v2, etc., which is why the heuristic is gated on "not a known command". OK as-is given the gate, but it reinforces suggestion pre-release: fix ci #1.

  5. Redundant guard at slashCommandProcessor.ts:514firstToken && inside the second if is unnecessary because commandParts.length > 1 already implies a non-empty first token. Trivial.

  6. Missing test: add a case where a path-shaped first token is a loaded command (e.g., a user defines tmp as a custom command), and confirm it still wins over the path heuristic. The code already orders isKnownTopLevelCommandToken before looksLikeSingleSegmentAbsolutePath, but a regression test pins that behavior.

Risks

  • Low blast radius. The change only widens which inputs fall through to the model; it cannot newly mistreat a previously valid slash command — every recognized command name is also a valid looksLikeCommandName token, and known commands beat the path heuristic in handleSlashCommand.
  • Autocomplete edge case (如何自定义密钥文件 .env可能与其他文件冲突 #3 above) is the only user-visible UX wrinkle.
  • Test-file typecheck exclusion is preexisting tech debt (TODO(5691)); not this PR's concern.

Recommendation

Approve once the brittle hardcoded path-roots set is replaced (or at least clearly justified). The behavior is right for the cases users will actually hit; the implementation can just be simpler and more general.


中文版本

重新审查:PR #3743 — 防止文件路径被识别为斜杠命令

概述

修复 #1804。当用户输入 /api/apiFunction/接口的实现/Users/name/path 时,原先的 isSlashCommand() 仅检查 / 前缀,导致未匹配的路径以 "Unknown command" 错误终止。本 PR 增加了一个 token 形态校验函数(looksLikeCommandName)以及单段绝对路径启发式判断,让路径形态的输入作为普通提示词发送给模型,同时保留真实斜杠命令、别名、Unicode 命令名和扩展前缀命令(gcp.deploy)的正常工作。

涉及文件:commandUtils.ts (+32/-1)、commandUtils.test.ts (+92/-1)、slashCommandProcessor.ts (+72/-1)、slashCommandProcessor.test.ts (+100/-0)。本地在 pr-3743 分支上:92 个焦点回归测试通过;整体 typecheck/lint/build 也通过。

代码质量

  • 响应评审的迭代过程良好。 经过 4 个 commit,作者已逐项修复前次评审的 critical 问题:保留裸 /、接受 Unicode 命名、接受 ? 别名、单段绝对路径回落到模型。每次修复都附带回归测试。
  • 正则选择合理。 /^[\p{L}\p{N}\p{M}:._?-]+$/u 覆盖了 Unicode 字母/数字/组合标记,加上命令名实际用到的标点,比最初的 ASCII 白名单更稳。
  • 测试覆盖完整。 新增用例涵盖 Unicode、点号扩展、MCP 风格、别名、裸斜杠以及多种路径形态。
  • 注释清晰准确。 isSlashCommand 上的 docstring 与裸斜杠豁免的内联注释把行为讲清楚了。

对前次评审顾虑的核实

  • 此前 "typecheck/build 会失败" 的判断是误报。 packages/cli/src/ui/hooks/slashCommandProcessor.test.ts 已经在 packages/cli/tsconfig.jsonexclude 列表中(与许多其他标注为 TODO(5691) 的测试文件一起),因此 setupProcessorHook 中缺失的 actions(openManageModelsDialoghandleResumeopenDeleteDialogopenExtensionsManagerDialogopenMcpDialogopenHooksDialogopenRewindSelector)不会产生 TS2345 错误。在 PR 分支上重新跑了 npm run typechecknpm run build,两者均通过。那条以 typecheck 失败为由提出的 CHANGES_REQUESTED 评审应当撤回——诊断是错的,对此致歉。

改进建议

  1. 务实但脆弱:COMMON_ABSOLUTE_PATH_ROOTS 是硬编码白名单packages/cli/src/ui/hooks/slashCommandProcessor.ts:81)。它会漏掉合法的单段路径,例如 /data foo/vendor explain/workspace check、自定义挂载点、项目相对目录等——这些仍会触发 "Unknown command"。建议反转规则:仅当 / 前缀后的第一个 token 已加载的命令/别名(isKnownTopLevelCommandToken 已经在算)时才走斜杠命令分支,否则一律回落到模型。这样既能去掉硬编码集合和文件扩展名正则,也能修复 /data foo 这一类输入。

  2. 路径检测逻辑分散在两个文件中。 commandUtils.ts 里有 looksLikeCommandName,但绝对路径根集合和文件扩展名正则却放在 slashCommandProcessor.ts。后续维护者得同时改两个地方。建议要么把这些 helper 移到 commandUtils.ts,要么让 isSlashCommand 接收已加载命令列表,做一次统一决策。

  3. 轻微不一致:isSlashCommand('/tmp inspect') 返回 true,但 handleSlashCommand 返回 false 目前没问题,因为调用方(useGeminiStreamuseMessageQueueAppContainer)最终都以 handleSlashCommand 的返回值为准。但 useCommandCompletion.tsx:129 是基于真值的 isSlashCommand 进入 SLASH 自动补全模式,所以用户输入 /tmp inspe… 时仍会弹出斜杠命令下拉框,对路径输入而言体验略显困惑。按建议 pre-release: fix ci #1 集中决策可以一并解决。

  4. SINGLE_SEGMENT_FILE_RE = /^[^\s/\\]+\.[A-Za-z0-9]{1,16}$/ 会匹配任何包含点号 + 1~16 个字母数字的字符串。包括 gcp.deploy1(已作为已知命令处理)和 mycmd.v2 等——这正是为何启发式被 "非已知命令" 前置条件守门。在当前条件下可接受,但也再次说明建议 pre-release: fix ci #1 的合理性。

  5. slashCommandProcessor.ts:514 的冗余守卫 —— 第二个 if 里的 firstToken && 是多余的,因为 commandParts.length > 1 已经隐含 firstToken 非空。属于细节。

  6. 缺一个测试用例: 添加一个场景:路径形态的第一个 token 正好是已加载命令(比如用户定义了一个名为 tmp 的自定义命令),验证它仍然胜过路径启发式。代码已经在 handleSlashCommand 中将 isKnownTopLevelCommandToken 排在 looksLikeSingleSegmentAbsolutePath 之前,加个回归测试把这个行为锁住。

风险

  • 影响面小。 改动只会扩大 "回落到模型" 的输入范围,不会让原本有效的斜杠命令被错误识别——任何被识别的命令名也都满足 looksLikeCommandName,且已知命令在 handleSlashCommand 中优先级高于路径启发式。
  • 自动补全边缘情况(上面 如何自定义密钥文件 .env可能与其他文件冲突 #3)是唯一用户可见的小瑕疵。
  • 测试文件被排除在 typecheck 外属于历史遗留技术债(TODO(5691)),与本 PR 无关。

建议结论

在硬编码路径根集合被替换(或至少给出明确说明)后可以批准。功能行为对用户实际遇到的场景是正确的,实现还可以更简洁、更通用。

— Claude Opus 4.7 via Claude Code /review

@tanzhenxin tanzhenxin added the type/bug Something isn't working as expected label May 1, 2026
@yiliang114

Copy link
Copy Markdown
Collaborator Author

Follow-up commit e0cb5d624 addresses the queued slash-like prompt ordering case and keeps loaded custom command names with filename punctuation working.

Fresh local verification:

  • npm run lint:ci PASS
  • npm run typecheck PASS
  • npm run build PASS
  • npm run test PASS

Focused regression:

  • 5 files passed, 156 tests passed

Full workspace test totals:

  • packages/cli: 303 files, 4718 passed, 7 skipped
  • packages/core: 253 files, 6237 passed, 2 skipped
  • packages/sdk-typescript: 6 files, 201 passed
  • packages/vscode-ide-companion: 40 files, 278 passed, 1 skipped

Build still reports the existing VSCode companion lint warnings and Browserslist notice; no build errors.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] [typecheck] packages/cli/src/ui/hooks/slashCommandProcessor.test.ts:157 and line 1113 — Mock objects are not assignable to SlashCommandProcessorActions. tsc --noEmit reports TS2345 at both locations. The mock factories need to be updated to match the current interface signature.

Comment thread packages/cli/src/ui/utils/commandUtils.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a CLI UX bug where slash-prefixed absolute file paths (e.g. /Users/name/path or /api/apiFunction/接口的实现) were incorrectly routed into slash-command handling and turned into Unknown command errors instead of being sent to the model as normal prompts.

Changes:

  • Add looksLikeCommandName() and use it to distinguish command-like /token inputs from path-like or metacharacter-prefixed inputs.
  • Update slash-command processing to fall back to normal prompt handling for unknown /token ...args inputs (while still giving loaded commands/aliases precedence).
  • Fix message queue draining to preserve original typed order by draining only the leading plain-text run (instead of filtering/reordering).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/cli/src/ui/utils/commandUtils.ts Adds looksLikeCommandName() and tightens isSlashCommand() to ignore path-like first tokens.
packages/cli/src/ui/utils/commandUtils.test.ts Adds focused tests for command-name classification and regression coverage for issue #1804.
packages/cli/src/ui/hooks/useMessageQueue.ts Changes draining logic to stop at the first slash command, preserving queue order.
packages/cli/src/ui/hooks/useMessageQueue.test.ts Updates/extends tests to cover preserved ordering and slash-like prompt behavior.
packages/cli/src/ui/hooks/slashCommandProcessor.ts Adds early fall-through for path-like tokens and unknown /token ...args prompts.
packages/cli/src/ui/hooks/slashCommandProcessor.test.ts Adds regression tests for bare /, alias matching, Unicode commands, and path-like fall-through.
packages/cli/src/ui/AppContainer.tsx Updates comment to match the new “drain leading plain prompts” queue behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] [typecheck] tsc --noEmit 报告两处 TS2345 错误(行 157 和 1137):mock 对象不可赋值给 SlashCommandProcessorActions。Mock factory 缺少接口要求的某些属性,阻塞 typecheck。建议补全 mock 对象中缺失的属性或使用 vi.fn() 打桩。

— glm-5.1 via Qwen Code /review

@yiliang114

Copy link
Copy Markdown
Collaborator Author

@wenshao You were right - the regular package typecheck excludes slashCommandProcessor.test.ts, so my previous check missed this test-file type issue. Fixed in 579274bac by replacing the two incomplete inline action mocks with a typed createMockActions() helper that satisfies SlashCommandProcessorActions.

Verified:

  • direct tsc command including packages/cli/src/ui/hooks/slashCommandProcessor.test.ts
  • cd packages/cli && npx vitest run src/ui/hooks/slashCommandProcessor.test.ts
  • npm -w packages/cli run typecheck

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/cli/src/ui/utils/commandUtils.ts Outdated
Comment thread packages/cli/src/ui/utils/commandUtils.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.test.ts Outdated

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] [review] isRealUserTurnpackages/cli/src/ui/utils/historyMapping.ts:21 误判新路径产生的斜杠前缀用户轮次,导致 rewind 截断索引错误。

此 PR 使 /data foo 等输入通过 isSlashCommand()handleSlashCommand() 返回 false → 作为常规提示发送给模型,创建文本以 / 开头的 USER 历史条目。但 isRealUserTurn 假定所有 / 前缀文本都是未到达模型的斜杠命令,返回 false。这导致 computeApiTruncationIndex 的 UI 用户轮次计数比实际少,rewind 索引指向错误位置。

复现: 输入 hello/data fooworld/rewind 选择 world/data foo 交换在 UI 中可见但 API 历史已被截断,后续模型对话在缺失上下文的情况下继续。

建议修复: 在历史条目中添加 sentToModel 标志(在 useGeminiStream 的 fallthrough 路径设置),并更新 isRealUserTurn 检查该标志。

[Suggestion] [review] drainQueue+popNextSegment 的 drain→pop→drain 组合循环(AppContainer 实际使用的模式)未在 useMessageQueue.test.ts 中测试。建议添加覆盖完整 drain→pop→drain 序列的测试。

Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts Outdated
Comment thread packages/cli/src/ui/hooks/useMessageQueue.ts
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.test.ts Outdated
Comment thread packages/cli/src/ui/utils/commandUtils.ts Outdated
Comment thread packages/cli/src/ui/utils/commandUtils.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts Outdated
@yiliang114

Copy link
Copy Markdown
Collaborator Author

@wenshao Follow-up pushed in 08d904335.

This fixes the rewind/history mapping issue by recording explicit model-turn provenance instead of inferring it only from the prompt prefix:

  • normal prompts, including /data foo fallthrough, now add sentToModel: true
  • submit_prompt slash commands mark the visible invocation as model-sent once the command resolves
  • isRealUserTurn() checks that metadata first, with the legacy prefix fallback kept for older history
  • accepted speculation-rendered user text is also marked model-sent

I also handled the smaller review items around the ? alias boundary, isSlashCommand() JSDoc, drain -> pop -> drain queue coverage, and the brittle positional Parameters<> type in the test.

Design note: I kept single-token unknown commands as Unknown command intentionally, so likely typos like /hlep still surface locally. Longer term, I think the cleaner direction is to centralize slash routing around "known command/alias vs model text" and keep explicit history provenance for rewind. This PR now applies the provenance part without turning the fix into a broader command parser rewrite.

Verified locally:

  • git diff --check
  • npm run lint:ci
  • npm run typecheck
  • cd packages/cli && npx vitest run src/ui/utils/commandUtils.test.ts src/ui/hooks/useMessageQueue.test.ts src/ui/hooks/slashCommandProcessor.test.ts src/ui/utils/historyMapping.test.ts src/ui/hooks/useGeminiStream.test.tsx src/ui/hooks/useHistoryManager.test.ts (6 files, 227 tests)
  • npm run build (passes; only the existing VSCode companion warnings and Browserslist notices)

CI has started on 08d904335.

Bojun-Vvibe added a commit to Bojun-Vvibe/oss-contributions that referenced this pull request May 2, 2026
- BerriAI/litellm#27059 (Grok 4.20 azure_ai metadata) merge-after-nits
- QwenLM/qwen-code#3743 (path-vs-slash-command classifier) merge-after-nits
- QwenLM/qwen-code#3767 (capture actual wire request in OpenAI logger) merge-after-nits
- google-gemini/gemini-cli#26306 (bound retry fallback to prevent infinite loop) merge-after-nits
- google-gemini/gemini-cli#26305 (/mcp remove slash command) merge-after-nits
Comment thread packages/cli/src/ui/types.ts Outdated
Comment thread packages/cli/src/ui/utils/commandUtils.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts Outdated
Comment thread packages/cli/src/ui/utils/historyMapping.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts Outdated
@yiliang114

Copy link
Copy Markdown
Collaborator Author

@wenshao Following the latest review discussion, I split the scope:

So for this PR, I’d like to keep the review focused on the small #1804 behavior. The broader design questions can continue in #3826 without blocking this targeted fix.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] [review] isRealUserTurn in packages/cli/src/ui/utils/historyMapping.ts:18 still uses startsWith('/') to exclude slash commands from rewind. After this PR, file-path inputs like /api/apiFunction/接口的实现 are sent to the model but isRealUserTurn still returns false for them. This causes rewind selector to skip file-path prompts (RewindSelector.tsx:29), API truncation index to miscount (historyMapping.ts:95), and rewind target turn index to be wrong (AppContainer.tsx:1718).

Fix: replace startsWith('/') with isSlashCommand().

export function isRealUserTurn(item: HistoryItem): boolean {
  if (item.type !== 'user' || !item.text) return false;
  return !isSlashCommand(item.text) && !item.text.startsWith('?');
}

— deepseek-v4-pro via Qwen Code /review

Comment thread packages/cli/src/ui/utils/commandUtils.ts
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No issues found. LGTM! ✅

Verification:

  • Typecheck: PASS
  • ESLint: PASS (0 errors on changed files)
  • Tests: 90/90 PASS
  • 10-agent review: no Critical or high-confidence findings

The isRealUserTurn fix from the previous review is correctly applied, and the scope is appropriately narrowed to the #1804 path-separator fix.

— deepseek-v4-pro via Qwen Code /review

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Fixes #1804: prompts like /api/apiFunction/接口的实现 or /Users/me/path were being routed to the slash-command handler and rejected as Unknown command. The fix adds a path-detector that classifies inputs whose first whitespace-delimited token contains another / or \ as regular prompts. Out-of-scope items are sensibly documented.

What's good

  • Single source of truth. hasSlashCommandPathSeparator() is reused by isSlashCommand(), the inner check in handleSlashCommand, and (transitively) by historyMapping.isRealUserTurn. No drift between detectors.
  • Correct rewind/history behavior. Switching isRealUserTurn from startsWith('/') to isSlashCommand is the right call — a path-like prompt was actually sent to the model, so it must count as a real user turn for computeApiTruncationIndex. The new historyMapping.test.ts case verifies this; without it, rewinds would land on the wrong API turn.
  • Test mock fix is genuinely needed. SlashCommandProcessorActions had grown several fields (openArenaDialog, openManageModelsDialog, handleResume, openDeleteDialog, openExtensionsManagerDialog, openMcpDialog, openHooksDialog, openRewindSelector) that the test mock didn't supply. Hoisting the mock into a createMockActions() factory and exporting the interface tightens types and removes duplication.
  • Edge-case coverage. Tests cover Unicode, Windows backslash (/tmp\\test.txt), absolute paths, hidden config paths, and a path-with-trailing-args (/var/log/syslog check this). /, /help, and /? still round-trip as slash commands.
  • Positive side effect on the queue. useMessageQueue.drainQueue() partitions on isSlashCommand. Pre-fix, a queued /api/foo would be retained as a "slash command" and never drained — a latent bug. Post-fix, it drains alongside other prompts. Worth calling out in the PR description even though it's listed as out of scope.

Other notes

  • Trailing-slash single segments. /foo/ (first token = 'foo/') is now classified as a path, but /foo is not, so they take different routes. Consistent with the documented out-of-scope list, but worth flagging in the follow-up issue for the broader /token routing question.

Risk assessment

  • No regression for real commands. Built-in commands and aliases use simple identifiers; none contain / or \ in the first token. parseSlashCommand itself splits on whitespace, not slashes (commands.ts:29), so subcommand routing is unaffected.
  • Args containing paths still work. /help foo/bar → first token is 'help', classified as slash command.
  • Performance. One short regex test per isSlashCommand call (called on each completion keystroke). Negligible.

Verdict

LGTM with optional cleanup — the fix is correct, well-scoped, and the tests cover the right surface. None of the inline suggestions are blocking; the biggest is the dead defensive check in handleSlashCommand — either annotate or remove it before merge so it doesn't bit-rot.

if (!trimmed.startsWith('/') && !trimmed.startsWith('?')) {
return false;
}
if (trimmed.startsWith('/') && hasSlashCommandPathSeparator(trimmed)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This branch is unreachable in practice. Every caller of handleSlashCommand already gates on isSlashCommand(...) (useGeminiStream.ts:656, AppContainer.tsx:1257), and isSlashCommand itself returns false for path-like inputs after this PR.

Two options:

  1. Drop the check and rely on the caller-side gate.
  2. Keep it as a safety net but add a one-line comment explaining why it duplicates isSlashCommand's logic.

Without a comment, a future maintainer will likely "DRY it up" and remove it, possibly along with the safety net it was meant to provide.

query.slice(1).trimStart().split(/\s+/)[0] ?? '';

export const hasSlashCommandPathSeparator = (query: string): boolean =>
SLASH_PATH_SEPARATOR_RE.test(getSlashCommandFirstToken(query));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Unstated precondition: getSlashCommandFirstToken calls query.slice(1) and silently strips the first character whether or not it's /. Today the only caller is hasSlashCommandPathSeparator, whose only reachable callers gate on startsWith('/') — so this is safe. But hasSlashCommandPathSeparator is exported, and a future caller passing 'foo/bar' would get back 'oo/bar' and a wrong answer.

Two cheap mitigations:

  • Add a JSDoc note ("expects a /-prefixed query") on hasSlashCommandPathSeparator, or
  • Have hasSlashCommandPathSeparator early-return false if !query.startsWith('/'), removing the precondition entirely.

const SLASH_PATH_SEPARATOR_RE = /[/\\]/;

const getSlashCommandFirstToken = (query: string): string =>
query.slice(1).trimStart().split(/\s+/)[0] ?? '';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: String.prototype.split always returns a non-empty array, so [0] is always a string and the ?? '' never fires. Harmless, but slightly misleading — it suggests split could return [].

@yiliang114
yiliang114 merged commit fe1fb31 into QwenLM:main May 6, 2026
13 checks passed
DragonnZhang pushed a commit that referenced this pull request May 8, 2026
)

* fix(cli): prevent file paths from being treated as slash commands (#1804)

When users input file paths starting with '/' (e.g. '/api/apiFunction/...',
'/Users/name/path'), they were incorrectly parsed as slash commands, resulting
in "Unknown command" errors. The input was discarded instead of being sent to
the model for processing.

Root cause: isSlashCommand() only checked for a '/' prefix without validating
whether the first token actually looks like a command name. Any '/' prefix
triggered the slash command flow, and when no matching command was found, the
error was shown with no fallback.

Fix: Add looksLikeCommandName() that validates command names contain only
[a-zA-Z0-9:_-]. Both isSlashCommand() and handleSlashCommand() now check the
first token — if it contains path separators, dots, or non-ASCII characters,
the input falls through to normal model processing instead of the command
dispatcher.

Closes #1804

* fix(cli): allow dots in command names and fix prettier formatting

Address review feedback:
- Allow '.' in looksLikeCommandName() regex to support extension-qualified
  commands like gcp.deploy (CommandService renames conflicts as ext.cmd)
- Add regression tests for dot-named commands in both commandUtils and
  slashCommandProcessor
- Fix prettier formatting in slashCommandProcessor test file

* fix(cli): handle slash command review edge cases

* docs(cli): align slash command validation comment

* fix(cli): preserve slash prompt ordering

* fix(cli): reject shell-metacharacter slash tokens

* test(cli): align slash command action mocks

* fix(cli): track model-sent user turns

* fix(cli): narrow slash path handling scope

* fix(cli): count slash path prompts in history

* test(cli): type slash command action mocks
xaelistic pushed a commit to xaelistic/qwen-code that referenced this pull request Jun 7, 2026
…enLM#3743)

* fix(cli): prevent file paths from being treated as slash commands (QwenLM#1804)

When users input file paths starting with '/' (e.g. '/api/apiFunction/...',
'/Users/name/path'), they were incorrectly parsed as slash commands, resulting
in "Unknown command" errors. The input was discarded instead of being sent to
the model for processing.

Root cause: isSlashCommand() only checked for a '/' prefix without validating
whether the first token actually looks like a command name. Any '/' prefix
triggered the slash command flow, and when no matching command was found, the
error was shown with no fallback.

Fix: Add looksLikeCommandName() that validates command names contain only
[a-zA-Z0-9:_-]. Both isSlashCommand() and handleSlashCommand() now check the
first token — if it contains path separators, dots, or non-ASCII characters,
the input falls through to normal model processing instead of the command
dispatcher.

Closes QwenLM#1804

* fix(cli): allow dots in command names and fix prettier formatting

Address review feedback:
- Allow '.' in looksLikeCommandName() regex to support extension-qualified
  commands like gcp.deploy (CommandService renames conflicts as ext.cmd)
- Add regression tests for dot-named commands in both commandUtils and
  slashCommandProcessor
- Fix prettier formatting in slashCommandProcessor test file

* fix(cli): handle slash command review edge cases

* docs(cli): align slash command validation comment

* fix(cli): preserve slash prompt ordering

* fix(cli): reject shell-metacharacter slash tokens

* test(cli): align slash command action mocks

* fix(cli): track model-sent user turns

* fix(cli): narrow slash path handling scope

* fix(cli): count slash path prompts in history

* test(cli): type slash command action mocks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/bug Something isn't working as expected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✕ Unknown command: /api/apiFunction/接口的实现

4 participants