Skip to content

fix(cli): track model-sent slash command history - #3826

Merged
yiliang114 merged 28 commits into
QwenLM:mainfrom
yiliang114:fix/slash-routing-history-followup-pr
May 29, 2026
Merged

fix(cli): track model-sent slash command history#3826
yiliang114 merged 28 commits into
QwenLM:mainfrom
yiliang114:fix/slash-routing-history-followup-pr

Conversation

@yiliang114

@yiliang114 yiliang114 commented May 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Draft follow-up stacked conceptually on #3743.

This PR tracks whether a visible slash-command user history entry actually
reached the model. Some slash commands show /command in UI history but only
perform local UI work, while commands that return submit_prompt send generated
prompt content to the model. History rewind should distinguish those two cases.

Why this is split from #3743

#3743 is intentionally kept minimal for #1804: path-like slash input whose first
token contains a path separator should fall through to the model instead of
being treated as a slash command.

This PR handles a separate history correctness issue for slash commands that
submit prompts to the model. It should not broaden #3743's routing behavior.

What changed

  • Add optional sentToModel metadata to user history items.
  • Mark visible slash-command invocations as local-only by default.
  • Mark the same visible invocation as model-sent when the command returns
    submit_prompt.
  • Pass updateItem into the slash command processor explicitly, matching the
    production caller.
  • Let history mapping trust sentToModel when present, while keeping the
    existing lexical fallback for older history.
  • Reuse the same visible invocation when confirmation flows re-run a command,
    avoiding duplicate user history and duplicate slash-command recording/logging.
  • Use the existing HistoryItemWithoutId type at history add/update boundaries.
  • Align the latest main resume-session history notifications with the same
    typed history boundary.

Out of scope

  • Unknown slash+args fallback such as /data foo.
  • Single-token unknown slash inputs such as /README.md.
  • Queue draining behavior for mixed prompt/slash-command queues.
  • Command-name regex or shell-metacharacter validation changes.

Those are separate routing/product decisions and are not part of this follow-up.

Reviewer note

Until #3743 lands, GitHub may show both #3743 and this follow-up in the full PR
diff because this PR targets main. The intended incremental review is this
PR's change on top of #3743.

Validation

  • cd packages/cli && npx vitest run src/ui/utils/historyMapping.test.ts src/ui/hooks/slashCommandProcessor.test.ts src/ui/hooks/useHistoryManager.test.ts src/ui/hooks/useResumeCommand.test.ts
  • npm run typecheck
  • npm run lint
  • npm run build

…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 marked this pull request as ready for review May 4, 2026 08:44
@yiliang114
yiliang114 marked this pull request as draft May 4, 2026 11:19
@yiliang114 yiliang114 changed the title fix(cli): refine slash-like prompt routing fix(cli): track model-sent slash command history May 4, 2026
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.test.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts
Comment thread packages/cli/src/ui/utils/historyMapping.ts
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts
Comment thread packages/cli/src/ui/types.ts
Comment thread packages/cli/src/ui/utils/historyMapping.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts
Comment thread packages/cli/src/ui/types.ts Outdated
Comment thread packages/cli/src/ui/utils/resumeHistoryUtils.test.ts

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The sentToModel persistence across resume is already wired in the current head (resumeHistoryUtils.ts L243-245). Both regex patterns already include the u flag. CI is green — mind taking another look?

@yiliang114
yiliang114 requested a review from wenshao May 16, 2026 15:32

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review Summary

This PR adds sentToModel metadata tracking for slash-command history items. Good approach overall, but there is one blocking issue:

Critical

slashCommandProcessor.test.ts: TS2554 — Missing arguments at 3 test call sites (lines 1216, 1284, 1342)

useSlashCommandProcessor was updated to accept 17 arguments (added setSessionName and updateItem), but these 3 test hook calls still pass only 15 arguments. TypeScript reports TS2554: Expected 17 arguments, but got 15 at each location. This leaves setSessionName and updateItem as undefined in those test scenarios, meaning the submit_promptupdateItem(…, { sentToModel: true }) code path (line 770) is completely uncovered in these tests.

Suggested fix: Append vi.fn() (for setSessionName) and vi.fn() (for updateItem) to the argument list at each of the 3 call sites:

vi.fn(), // setSessionName
vi.fn(), // updateItem

Additional Note

useEditorSettings.test.ts:44 — The type migration from Omit<HistoryItem, 'id'> to HistoryItemWithoutId is incomplete. useEditorSettings.ts was updated but this test file still uses the old pattern. Consider updating for consistency, though this was not part of the diff and is informational only.

— DeepSeek/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.

[Critical] TypeScript compilation failure — 3 call sites need updating

tsc --noEmit reports TS2554: Expected 17 arguments, but got 15 at three locations in packages/cli/src/ui/hooks/slashCommandProcessor.test.ts:

  • Line 1216useSlashCommandProcessor(...) passes 15 args, needs setSessionName and updateItem
  • Line 1284 — same issue
  • Line 1342 — same issue

The function signature was expanded to accept setSessionName and updateItem, but these three test call sites were not updated. Tests pass at runtime because JS doesn't enforce arity, but tsc correctly rejects the code.

Fix: Add vi.fn() (or mockUpdateItem) as the two missing arguments at each call site.


— glm-5.1 via Qwen Code /review

Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.test.ts
@yiliang114
yiliang114 force-pushed the fix/slash-routing-history-followup-pr branch from 08880d6 to f7b3aaa Compare May 17, 2026 10:12
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.test.ts Outdated
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 new issues found. All previously reported concerns have been addressed in the existing review rounds. Tests pass, typecheck clean, build succeeds. LGTM ✅

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

…llowup-pr' into fix/slash-routing-history-followup-pr
main currently exposes setSessionName as an optional trailing parameter.
The earlier addition of updateItem pushed it before updateItem and
forced every caller — including AppContainer and all tests — to pass an
explicit undefined. Reorder so updateItem stays required and
setSessionName remains optional, preserving the prior ergonomic.
@yiliang114
yiliang114 dismissed stale reviews from wenshao, wenshao, wenshao, wenshao, and wenshao May 17, 2026 15:29

Persistence wired in resumeHistoryUtils.ts:240-246; addressed at head 3cd426b.

Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.test.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.test.ts Outdated
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.test.ts Outdated
Comment thread packages/cli/src/ui/hooks/useHistoryManager.ts
Comment thread packages/cli/src/ui/utils/historyMapping.ts Outdated
Comment thread packages/cli/src/ui/utils/resumeHistoryUtils.ts
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts
Comment thread packages/cli/src/ui/hooks/useHistoryManager.ts
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts
Comment thread packages/cli/src/ui/utils/resumeHistoryUtils.ts
Comment thread packages/cli/src/ui/hooks/slashCommandProcessor.ts
@yiliang114

Copy link
Copy Markdown
Collaborator Author

Addressing remaining suggestions (2026-05-18 round)

Thanks for the thorough review. Here's my assessment of the three open suggestions:

1. Optimistic sentToModel: true (slashCommandProcessor.ts:773)

This is not actually optimistic — when the code reaches the case 'submit_prompt': branch (L767), the command has already successfully returned submit_prompt content. The caller (prepareQueryForGemini) passes this content directly to sendMessageStream() in the same synchronous call chain. There is no async gap or fallible step between setting sentToModel: true and the content entering the API history.

If sendMessageStream itself fails (network error), the session is already in an error state — rewind precision is irrelevant at that point.

Impact if not fixed: none in practice.

2. Other resume paths not setting sentToModel (resumeHistoryUtils.ts:244)

Those paths (regular API messages ~L298, mid_turn_user_message ~L318, at_command ~L335) create user items whose text never starts with / or ?. The lexical fallback in isRealUserTurn returns true for all of them — correctly and unconditionally. Adding explicit sentToModel: true would be redundant.

Impact if not fixed: zero — the fallback is correct for these paths by construction.

3. Options object for positional params (slashCommandProcessor.ts:515)

Valid refactoring suggestion. However, there are only 2 recursive call sites, both covered by tests, and TypeScript catches arity mismatches at compile time. This is a code style improvement better suited for a dedicated refactor rather than this bug-fix PR.

Impact if not fixed: zero runtime risk, minor maintainability concern for future changes.


All three are either non-issues under the current architecture or low-priority style suggestions. CI is green across all platforms. Could you re-approve when you get a chance? Happy to address the options-object refactor in a follow-up if desired.

@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.

Suggestion: The test "still records unrelated commands via the chat recorder (control)" in slashCommandProcessor.test.ts:1588 asserts recordSlashCommand was called but does not verify the sentToModel field in the recorded payload. For non-submitting commands (action returns undefined), sentToModel should be false. Consider updating to expect(recorder.recordSlashCommand).toHaveBeenCalledWith(expect.objectContaining({ phase: 'invocation', rawCommand: '/regular', sentToModel: false })).

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

Comment thread packages/cli/src/ui/hooks/useHistoryManager.ts
@yiliang114
yiliang114 requested a review from wenshao May 28, 2026 16:21
@wenshao

wenshao commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Maintainer Verification Report

PR: #3826fix(cli): track model-sent slash command history
Verified by: maintainer local testing
Branch: fix/slash-routing-history-followup-pr
Date: 2026-05-28


Build

Step Result
npm run build --workspace=packages/core PASS
npm run build --workspace=packages/cli PASS
sentToModel in compiled output (3 files) PASS
sentToModel in core .d.ts PASS

Unit Tests

Test File Tests Result
historyMapping.test.ts 19 PASS
slashCommandProcessor.test.ts 48 PASS
useHistoryManager.test.ts 8 PASS
useResumeCommand.test.ts 9 PASS
resumeHistoryUtils.test.ts 9 PASS
Total 93 ALL PASS

Static Analysis

Check Result
tsc --noEmit (CLI) PASS — zero errors
Prettier (changed files) PASS

E2E Functional Tests (17/17 PASS)

Tested compiled JS modules directly:

isRealUserTurn (8 tests):

  • Slash command with sentToModel: true → correctly classified as real user turn
  • Slash command with sentToModel: false → correctly excluded
  • Legacy slash command (no sentToModel) → falls back to lexical classifier
  • Regular user text → real turn
  • Corrupted sentToModel: "true" (string) → falls back to lexical, excluded
  • ?-prefix commands → excluded
  • Non-user type items → excluded
  • Path-like slash prompts (e.g., /api/v1/test) → correctly classified as real turn

buildResumedHistoryItems (9 tests):

  • Resume preserves sentToModel: true from persisted SlashCommandRecordPayload
  • Resume preserves sentToModel: false for local-only commands
  • Legacy records (no sentToModel field) → property omitted, no pollution
  • Corrupted sentToModel: "true" (string) → property omitted

Code Review

Architecture (18 files, +452/-71):

  1. HistoryItemUser.sentToModel — New optional boolean metadata on user history items. Well-typed, properly guarded with typeof === 'boolean' check at all consumption sites.

  2. HistoryItemWithoutId type alias — Replaces Omit<HistoryItem, 'id'> throughout for better TypeScript discriminated union inference. Root cause documented at types.ts:531.

  3. isRealUserTurn fallback chainsentToModel takes priority when set; lexical classifier (isSlashCommand) serves as fallback for legacy sessions. Coupling documented in both historyMapping.ts and commandUtils.ts comments.

  4. Recursive invocation deduplicationexistingInvocationItemId threaded through confirm_shell_commands and confirm_action recursive calls to prevent duplicate user history entries. Two dedicated tests verify this.

  5. delegatedToRecursiveInvocation flag — Prevents double recording/event emission when outer call delegates to inner recursive call. Guards both recordSlashCommand and makeSlashCommandEvent calls.

  6. Resume roundtripchatRecordingService.ts records sentToModel in SlashCommandRecordPayload; resumeHistoryUtils.ts reads it back with strict typeof === 'boolean' validation and conditional spread.

  7. Unicode regex fix/\s+//\s+/u in both commandUtils.ts and slashCommandProcessor.ts for consistent Unicode-aware whitespace splitting.

  8. updateItem no-op optimization — Returns prevHistory (same reference) when target ID not found, avoiding unnecessary React re-renders. Debug-logged for observability.

Security: No issues found. No new external inputs, no auth changes, no injection vectors.

Backwards compatibility: Legacy sessions without sentToModel metadata are handled correctly — the lexical fallback in isRealUserTurn preserves existing behavior for old session data.

Verdict

APPROVED — All 93 unit tests pass, 17/17 E2E functional tests pass, typecheck and formatting clean. The sentToModel metadata tracking is correctly implemented with proper fallback for legacy sessions, deduplication for recursive invocations, and validated resume roundtrip. No security or correctness concerns.

@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 ✅ — qwen3.7-max via Qwen Code /review

@DennisYu07 DennisYu07 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.

LGTM

@yiliang114
yiliang114 merged commit 27b0629 into QwenLM:main May 29, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants