-
Notifications
You must be signed in to change notification settings - Fork 3k
feat(cli): add /chat file commands for session management #3190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
45c6304
42ebcd8
d3ed275
d6a7932
d22cb29
bba7b46
6934e97
b05c5db
c3d95f5
50f8a8c
90a1089
411ffe1
3b2677b
9fc07a3
2fc927f
5df87de
ab20329
76125a8
898aa20
b3f255d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # chat-delete.md — Remove a Session Name from Index | ||
|
|
||
| **Note**: Direct invocation (`/chat-delete name`) bypasses the router's argument parsing, locale detection, and name validation. Use `/chat -d name` instead. | ||
|
|
||
| 1. **Validate `{{name}}`**: `^[a-zA-Z0-9_.-]+$`, ≤128, ≠ `.`/`..`/`__proto__`/`constructor`/`prototype`. Invalid → error, stop. | ||
| 2. **Read index**: Read `.qwen/chat-index.json` (project root, NOT runtime base). **JSON parse error → output `"chat-index.json is malformed. Fix it manually before deleting."` and stop. Do NOT proceed.** | ||
| 3. If `{{name}}` NOT found: show list + "Session not in index", stop. | ||
| 4. **Confirmation**: If user provided `-y` or `--force` flag, SKIP confirmation and delete immediately. Otherwise: | ||
| - **STOP and output this exact question:** | ||
| ``` | ||
| ⚠️ Delete session "{{name}}"? | ||
| Type "yes" to confirm, or anything else to cancel: | ||
| ``` | ||
| - **WAIT for user's response.** DO NOT proceed until user responds. | ||
| - If response = `"yes"` → Continue to delete | ||
| - If response ≠ `"yes"` → Output `"Delete cancelled."` and STOP immediately | ||
| 5. **Delete**: Remove `{{name}}` from index, write back. | ||
| 6. **Confirm result**: Output: `Session "{{name}}" removed from index.` + note: "Session file NOT deleted." | ||
|
|
||
| **Why file NOT deleted?** | ||
|
|
||
| - **Safety**: Deletion is irreversible; removing a name reference is low-risk. | ||
| - **Shared reference**: Multiple names can point to the same session. Deleting one name should not destroy data others reference. | ||
|
|
||
| **Important**: The index is stored in the **current project's root directory**, NOT the user's home directory or runtime base. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # chat-list.md — List All Saved Sessions | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] — gpt-5.5 via Qwen Code /review |
||
|
|
||
| **Note**: Direct invocation (`/chat-list`) bypasses the router's argument parsing, locale detection, and name validation. Use `/chat -l` instead. | ||
|
|
||
| 1. Read `.qwen/chat-index.json` (project root, NOT runtime base). File not found → "No saved sessions." **JSON parse error → output `"chat-index.json is malformed. Fix it manually before listing."` and stop. Do NOT treat as empty.** | ||
| 2. Display sorted alphabetically: `• <name> (ID: <first8>...)` | ||
|
|
||
| **Validation inherited from common rules**: `^[a-zA-Z0-9_.-]+$`, ≤128, ≠ `.`/`..`/`__proto__`/`constructor`/`prototype`. | ||
|
|
||
| **Important**: The index is stored in the **current project's root directory**, NOT the user's home directory or runtime base. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # chat-resume.md — Resume a Saved Session | ||
|
|
||
| **Note**: Direct invocation (`/chat-resume name`) bypasses the router's argument parsing, locale detection, and name validation. Use `/chat -r name` instead. | ||
|
|
||
| 1. Validate `{{name}}` (Common rules): `^[a-zA-Z0-9_.-]+$`, ≤128, ≠ `.`/`..`/`__proto__`/`constructor`/`prototype`. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] This validation does not match the real session ID format accepted by the CLI. — gpt-5.5 via Qwen Code /review |
||
| 2. Look up ID in index (`.qwen/chat-index.json` in project root, NOT runtime base). **JSON parse error → output `"chat-index.json is malformed. Fix it manually before resuming."` and stop. Do NOT proceed.** Missing/not found → show list + "Session not found", stop. | ||
| 3. **Validate loaded ID**: The ID from index must match UUID format (`^[a-fA-F0-9-]+$`, allows hyphens). If ID contains any shell metacharacters (`$`, `` ` ``, `;`, `|`, `>`, `<`, `&`, `(`, `)`, spaces), reject it: "Error: Invalid session ID from index. Aborted." — **DO NOT execute any shell command with this ID**. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Session-ID validation regex Real session UUIDs don't start with Suggested fix — tighten the validator to a real UUID shape: At minimum: reject any ID starting with via Qwen Code /review |
||
| 4. **Get session project directory**: Read the first line of `<runtimeBase>/projects/<sanitizeCwd>/chats/<id>.jsonl`. | ||
| - File missing → "Session file missing", stop. | ||
| - File 0 bytes → "Session file empty (likely interrupted save). Aborted.", stop. | ||
| - First line not valid JSON → "Session file corrupt at line 1. Aborted.", stop. | ||
| - JSON has no `cwd` field → "Session record missing project context. Aborted.", stop. | ||
| - Set `<projectRoot>` = the `cwd` field value from the JSON record. | ||
| - Verify `<projectRoot>` directory exists on disk. Missing → "Error: original project directory '<projectRoot>' no longer exists. Aborted.", stop. | ||
| 5. **Verify session belongs to current project**: Apply `sanitizeCwd(<projectRoot>)` and compare with current project's `<sanitizeCwd>`. If they don't match → "Error: Session belongs to another project. Aborted.", stop. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Project-identity verification uses Consider computing SHA-256 via — glm-5.1 via Qwen Code /review |
||
|
|
||
| 6. **Validate projectRoot for shell safety**: <projectRoot> must match `^[a-zA-Z0-9/._-]+$` — reject any path containing characters outside this set. Reject: "Error: Session path contains unsafe characters. Aborted." | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] Windows path validation blocks all native Windows resume The whitelist regex Fix: Use platform-aware validation. On Windows, allow — qwen3.7-max via Qwen Code /review |
||
| - For Windows: also reject `^`, `%`, `\` | ||
| 7. **Execute a shell command** to launch a NEW terminal window with cd to project directory: | ||
| - Windows (PowerShell): `start pwsh -NoExit -Command "cd '<projectRoot>'; qwen --resume <id>"` | ||
| - Windows (CMD fallback): `start cmd /k "cd /d \"<projectRoot>\" && qwen --resume <id>"` (use if PowerShell unavailable) | ||
| - macOS: `osascript -e "tell app \"Terminal\" to do script \"cd '$(echo "<projectRoot>" | sed "s/'/'\\\\''/g")' && qwen --resume <id>\""` | ||
| - Linux (WSL): If platform is linux and `/proc/version` contains "Microsoft" or "WSL": | ||
| - Convert path: Run `wslpath -w "<projectRoot>"` to get Windows path | ||
| - Use: `cmd.exe /c "start cmd /k cd /d \"<windowsPath>\" && qwen --resume <id>"` or prefer `wt.exe -d "<windowsPath>" -- qwen.exe --resume <id>` | ||
| - Linux (native): detect terminal with `command -v` (gnome-terminal, xterm, alacritty, kitty in order), then run: `<terminal> -- bash -c "cd '<projectRoot>' && qwen --resume <id>"` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] macOS/Linux terminals close after qwen exits; Windows doesn't Windows uses This means:
Fix: For macOS, add — qwen3.7-max via Qwen Code /review |
||
| 8. Output: `Session "{{name}}" resumed in new window. (ID: <id>)` | ||
|
|
||
| **Runtime Base Resolution** (in priority order): | ||
|
|
||
| - `$QWEN_RUNTIME_DIR` (if set) | ||
| - `~/.qwen` (default fallback) | ||
|
|
||
| **Note**: If user has configured `advanced.runtimeOutputDir` in settings.json, sessions are stored under that path. /chat commands cannot read settings.json (credential leak risk) and will not find those sessions. | ||
|
|
||
| **Note**: `<sanitizeCwd>` is the project directory name derived from `sanitizeCwd(projectRoot)`, which replaces all non-alphanumeric characters with `-`. On Windows, the path is also normalized to lowercase before sanitization. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # chat-save.md — Save Current Session | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] The "Runtime Base Resolution + sanitizeCwd + runtimeOutputDir" block at the end of this file is already present verbatim in Suggested fix: Replace the trailing block with: — pai/glm-5.1 via Qwen Code /review
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Step 5 reads the Suggested fix: Add to step 5: "If first line is not valid JSON → skip verification (corrupt session, allow save with warning)" or "First line not valid JSON → Aborted", consistent with chat-resume. — pai/glm-5.1 via Qwen Code /review
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Options: (a) move sub-commands to a non-scanned location (e.g., — glm-5.1 via Qwen Code /review |
||
|
|
||
| **Note**: Direct invocation (`/chat-save name`) bypasses the router's argument parsing, locale detection, and name validation. Use `/chat -s name` instead. | ||
|
|
||
| 1. Validate `{{name}}`: `^[a-zA-Z0-9_.-]+$`, ≤128, ≠ `.`/`..`/`__proto__`/`constructor`/`prototype`. Invalid → error, stop. | ||
| 2. Read `.qwen/chat-index.json` (project root, NOT runtime base). File not found → `{}`. **JSON parse error → output `"chat-index.json is malformed. Fix it manually before saving."` and stop. Do NOT overwrite.** | ||
| 3. If `{{name}}` in index → ask "Overwrite? (yes/no)". ≠ yes → stop. | ||
| 4. Session ID = **newest `.jsonl` file by modification time** in `<runtimeBase>/projects/<sanitizeCwd>/chats/`. The filename (without `.jsonl`) IS the session UUID. ⚠️ **IMPORTANT**: If wrong session is saved, resume the target session first, then run `/chat -s`. No .jsonl found → "No session found. Start a conversation first.", stop. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Consider adding: after selecting the newest — glm-5.1 via Qwen Code /review |
||
| 5. **Verify session belongs to current project**: Read the first line of the selected `.jsonl` file. | ||
| - If JSON has no `cwd` field → skip verification (legacy session, allow save). | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] Save/resume asymmetry: save allows sessions that resume will always reject chat-save step 5 allows saving sessions with no Users get a successful save confirmation ( Fix: Make verification symmetric — either both allow with warning, or both abort. Recommended: chat-save should warn "Warning: session has no cwd field — this session will NOT be resumable. Save anyway? (yes/no)" before proceeding. — qwen3.7-max via Qwen Code /review |
||
| - First line not valid JSON → skip verification (corrupt session, allow save with warning "Warning: session file corrupt, skipping project verification."). | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] Saves corrupt sessions without warning they're unresumable "First line not valid JSON → skip verification (corrupt session, allow save with warning)." The warning is ephemeral — shown once in the chat output. But the index entry persists permanently. When the user runs Fix: Either refuse to save corrupt sessions (matching resume's strictness), or change the warning to explicitly state the consequence: "Warning: session file corrupt — this session will NOT be resumable via /chat -r. Save anyway? (yes/no)" — qwen3.7-max via Qwen Code /review |
||
| - Apply `sanitizeCwd(<cwd>)` and compare with current project's `<sanitizeCwd>`. If they don't match → "Error: Selected session belongs to another project. Aborted. Please resume the session from its original project first.", stop. | ||
| 6. Add or update `{{name}}` key in existing index object (2-space indent). **Write atomically**: write to `.qwen/.chat-index.json.tmp` first, then rename/move to `.qwen/chat-index.json`. Do NOT write directly to the index file. | ||
| 7. Output: `Saved: {{name}} → <id>` (or `Overwritten: ...`) | ||
|
|
||
| Runtime Base / sanitizeCwd: see chat.md Common Rules. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| --- | ||
| description: Chat session manager. /chat [-s|-l|-r|-d|-h] [name] [-y|--force] | ||
| --- | ||
|
|
||
| # CRITICAL: First check {{args}}, then route | ||
|
|
||
| ## Step 0: Immediate Validation (MUST execute FIRST) | ||
|
|
||
| **Check `{{args}}` right now, before doing anything else:** | ||
|
|
||
| 1. Is `{{args}}` empty? → **Show Help immediately, STOP** | ||
| 2. Is `{{args}}` only whitespace? → **Show Help immediately, STOP** | ||
| 3. Does the first token look like a valid flag? (`-s`, `--save`, `-l`, `--list`, `-r`, `--resume`, `-d`, `--delete`, `-h`, `--help`) | ||
| - **NO** → invalid flag/unrecognized → **Show Help immediately, STOP** | ||
| - **YES** → Continue to Step 1 | ||
|
|
||
| **⚠️ DO NOT skip this step. DO NOT proceed with any action until you verify `{{args}}`.** | ||
|
|
||
| --- | ||
|
|
||
| ## Step 1: Detect Environment | ||
|
|
||
| ### Language | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] Reading the full user-level — gpt-5.5 via Qwen Code /review |
||
| Run `node -e "console.log(Intl.DateTimeFormat().resolvedOptions().locale)"` to get system locale. | ||
| Use the language code (first 2 chars, e.g., "en", "zh", "ja") to determine response language. | ||
| If locale detection fails, match the language the user used in their prompt. | ||
|
|
||
| ### OS Detection (ONLY for `-r`/`--resume`) | ||
|
|
||
| **Skip this step for other flags.** Only run when `-r` is detected. | ||
|
|
||
| Run `node -e "console.log(process.platform)"`. Works across all shells. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] OS detection runs WSL is a non-trivial slice of "Windows" qwen-code users. Repro path: "I Suggested fix — when Document this branch in via Qwen Code /review |
||
|
|
||
| - `win32` → Windows | ||
| - `linux` → Linux (including WSL — detect WSL separately, see chat-resume.md) | ||
| - `darwin` → macOS | ||
|
|
||
| **WSL Detection**: If platform is `linux`, additionally read `/proc/version`. If it contains "Microsoft" or "WSL" (case-insensitive), treat as Windows for resume — use Windows Terminal or CMD. | ||
|
|
||
| --- | ||
|
|
||
| ## Step 2: Parse and Route | ||
|
|
||
| Split `{{args}}` by whitespace. First token = flag. Remaining = raw_args. | ||
|
|
||
| | Flag | Action | Sub-Command File | | ||
| | ----------------- | ----------------------------------------- | ---------------- | | ||
| | `-s` / `--save` | Go to Step 3 | `chat-save.md` | | ||
| | `-l` / `--list` | Read `chat-list.md` and execute its logic | `chat-list.md` | | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] The routing table instructs the LLM to "Read Future-maintainer hazard: someone adding Suggested fix — pick one:
via Qwen Code /review |
||
| | `-r` / `--resume` | Go to Step 3 | `chat-resume.md` | | ||
| | `-d` / `--delete` | Go to Step 3 | `chat-delete.md` | | ||
| | `-h` / `--help` | **Show Help immediately, STOP** | — | | ||
|
|
||
| ### Step 3: Validate name (for `-s`, `-r`, `-d`) | ||
|
|
||
| **For delete (`-d`):** | ||
|
|
||
| 1. Parse raw_args to extract name: Filter out `-y` and `--force` flags first, the first remaining token is the name. | ||
| 2. If name is missing, empty, or whitespace only → **Show Help immediately, STOP** | ||
| 3. If extra non-flag tokens remain after the first name → **Show Help immediately, STOP** | ||
| 4. If `-y` or `--force` was found → Set `forceDelete = true` | ||
|
|
||
| **For save/resume (`-s`, `-r`):** | ||
|
|
||
| 1. Parse raw_args to extract name: the first remaining token is the name. | ||
| - **Reject any token starting with `-`** (e.g., `-y`, `--force` are delete-only options) | ||
| - If extra non-flag tokens remain after the first name → Output: `Error: Unexpected token: <token>. /chat -s|-r takes only a single name.` and STOP | ||
| 2. If name is missing, empty, or whitespace only → **Show Help immediately, STOP** | ||
|
|
||
| **Common validation:** | ||
|
|
||
| - Does name match `^[a-zA-Z0-9_.-]+$` and length ≤ 128? | ||
| - **NO** → Output error: `Invalid name. Must match: ^[a-zA-Z0-9_.-]+$ (max 128 chars)` and STOP | ||
| - **YES** → Check if name is reserved (`.`, `..`, `__proto__`, `constructor`, `prototype`) | ||
| - **YES, reserved** → Output error: `Invalid name. Reserved: ., .., __proto__, constructor, prototype` and STOP | ||
| - **NO, not reserved** → Read corresponding sub-command file and execute | ||
|
|
||
| --- | ||
|
|
||
| ## Common Rules | ||
|
|
||
| | Rule | Value | | ||
| | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| | **Valid name regex** | `^[a-zA-Z0-9_.-]+$` | | ||
| | **Max length** | 128 characters | | ||
| | **Reserved names** | `.`, `..`, `__proto__`, `constructor`, `prototype` | | ||
| | **Index path** | `.qwen/chat-index.json` (project root) | | ||
| | **Index format** | `{"name": "sessionId", ...}` | | ||
| | **Session ID source** | Filename (no extension) of `.jsonl` in `<runtimeBase>/projects/<sanitizeCwd>/chats/`. runtimeBase priority: `$QWEN_RUNTIME_DIR` > `~/.qwen` (default) | | ||
| | **Project dir** | `sanitizeCwd(projectRoot)` replaces all non-alphanumeric characters with `-`. On Windows, also lowercase. E.g., `D:\code\qwen-code` → `d--code-qwen-code` | | ||
|
|
||
| --- | ||
|
|
||
| ## Help Text | ||
|
|
||
| **Show this when:** | ||
|
|
||
| - `{{args}}` is empty or whitespace only | ||
| - First token is NOT a valid flag | ||
| - Flag requires name but name is missing/empty | ||
| - User explicitly requests `-h` or `--help` | ||
|
|
||
| **Display this exact text and STOP all processing:** | ||
|
|
||
| ``` | ||
| Chat Session Manager | ||
|
|
||
| Usage: /chat <flag> [name] [-y|--force] | ||
|
|
||
| Flags: | ||
| -s, --save <name> Save current session with a name | ||
| -l, --list List all saved sessions | ||
| -r, --resume <name> Resume a saved session | ||
| -d, --delete <name> Delete a saved session from index | ||
| -h, --help Show this help | ||
|
|
||
| Options: | ||
| -y, --force Skip confirmation prompt (for -d) | ||
|
|
||
| Examples: | ||
| /chat -s my-session | ||
| /chat -l | ||
| /chat -r my-session | ||
| /chat -d my-session | ||
| /chat -d my-session -y # Delete without confirmation | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Critical] Missing atomic write — save has it, delete doesn't
Step 5 says "Remove
{{name}}from index, write back" with no atomic-write instruction. Compare withchat-save.mdstep 6 which explicitly requires: "Write atomically: write to.qwen/.chat-index.json.tmpfirst, then rename/move to.qwen/chat-index.json."Both operations modify the same
chat-index.jsonfile. An interrupted delete (kill -9, crash, power loss) corrupts the index. All four sub-commands then report "chat-index.json is malformed. Fix it manually." with no self-repair path.Fix: Mirror chat-save.md step 6: "Write atomically: write to
.qwen/.chat-index.json.tmpfirst, then rename/move to.qwen/chat-index.json. Do NOT write directly to the index file."— qwen3.7-max via Qwen Code /review