Skip to content

feat(session): add rename, delete, and auto-title generation for session#3093

Open
qqqys wants to merge 9 commits intoQwenLM:mainfrom
qqqys:feat/rename_session
Open

feat(session): add rename, delete, and auto-title generation for session#3093
qqqys wants to merge 9 commits intoQwenLM:mainfrom
qqqys:feat/rename_session

Conversation

@qqqys
Copy link
Copy Markdown
Collaborator

@qqqys qqqys commented Apr 10, 2026

TLDR

Add session rename, delete, and auto-title generation across CLI, VSCode extension, and WebUI. Users can now /rename sessions (with optional LLM-generated titles), /delete sessions, and resume sessions by custom title via --resume <title>. The session name is displayed as a tag in the CLI input prompt and persists across resume.

Screenshots / Video Demo

cli
ide

Dive Deeper

Architecture

Custom titles are stored as append-only system records in the session JSONL file:

{"uuid":"...","parentUuid":"<previous-record-uuid>","type":"system","subtype":"custom_title","systemPayload":{"customTitle":"my-feature"}}

Key design decisions:

  • parentUuid must correctly chain to the previous record — without this, reconstructHistory() (which walks from the tail record upward) would sever the chain and the session would appear empty on next load
  • Title is read via efficient 64KB tail-read (readCustomTitleFromFile), not full file scan
  • The CLI's /rename command uses ChatRecordingService.recordCustomTitle() which inherits correct parentUuid from lastRecordUuid. The ACP/VSCode path uses SessionService.renameSession() which explicitly reads the last record's UUID before writing

Security hardening

  • All SessionService methods that construct file paths from sessionId now validate against SESSION_FILE_PATTERN (defense-in-depth against path traversal)
  • VSCode qwenSessionReader delete/rename verify project ownership via projectHash
  • readCustomTitleFromFile uses try/finally to prevent fd leaks

Auto-title generation

When /rename is called without arguments, it extracts the last ~1000 chars of conversation history and sends a single request to the current model asking for a kebab-case title (e.g., fix-login-bug). A pending indicator ("Generating session name…") is shown during the request.

Resume by title

--resume <title> performs a case-insensitive exact match against custom titles. If multiple sessions match, the interactive picker is shown. If no match and the input isn't a valid UUID, exits with code 1.

Reviewer Test Plan

CLI

  1. Start a new session, have a conversation, then run /rename — should auto-generate a kebab-case title and show it as a tag in the input border
  2. Run /rename my-custom-name — should set the title and show tag
  3. Run /resume — the renamed session should show its custom title in the picker
  4. Select the renamed session — tag should appear, conversation history should load correctly
  5. Run /delete — select a non-current session, confirm deletion
  6. Exit and run qwen --resume my-custom-name — should resume by title
  7. Run qwen --resume nonexistent — should exit with code 1

VSCode Extension

  1. Open session picker, hover over a session — rename/delete icons should appear
  2. Click rename icon, type new name, press Enter — title should update in the list
  3. Switch away from the renamed session and back — history should load correctly (not empty)
  4. Click delete icon → "Delete?" appears → click again to confirm → session removed from list
  5. Cannot delete the currently active session (error message shown)

WebUI

  1. Same rename/delete flow as VSCode
  2. Rename input has maxLength={200}
  3. Blurring rename input without changes does NOT send a rename request
  4. Delete confirmation button stays visible even when mouse leaves the row (while in confirm state)

Edge cases

  • /rename with empty input (just Enter) — should trigger auto-generate, not error
  • /rename with very long name (>200 chars) — should show error
  • Rename a session, compact it, resume — title should survive compaction
  • Delete a session while another tab has it open — tab keeps its local state

Testing Matrix

🍏 🪟 🐧
npm run
npx
Docker
Podman - -
Seatbelt - -

Linked issues / bugs

resolves: #2619 #2999 #3032 #3078 #3234

…ions

- Add /rename command with LLM auto-title generation when no args provided
- Add /delete command to remove sessions from the session picker
- Display session name tag embedded in input prompt top border
- Restore session name on /resume and --resume <title> CLI flag
- Support rename and delete via ACP extMethod for VSCode extension
- Add rename/delete UI to WebUI SessionSelector with two-click delete confirmation
- Fix parentUuid chain: custom_title records now correctly reference the
  previous record's UUID, preventing session history from appearing empty
  after rename
- Add SESSION_FILE_PATTERN validation to all SessionService methods that
  construct file paths from sessionId (defense-in-depth against path traversal)
- Fix fd leak in readCustomTitleFromFile with try/finally
- Fix --resume <title> exit code (exit 1 when no match found)
- Add project ownership checks to VSCode qwenSessionReader delete/rename

Co-Authored-By: Qwen-Coder <noreply@qwen.com>
@qqqys qqqys requested review from tanzhenxin and yiliang114 April 10, 2026 07:45
@github-actions
Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR introduces session rename, delete, and auto-title generation features across CLI, VSCode extension, and WebUI. The implementation is well-architected with proper security hardening, efficient file I/O using tail-read strategies, and comprehensive test coverage. Overall, this is a solid implementation with thoughtful design decisions around data persistence and parent chain integrity.

🔍 General Feedback

Positive aspects:

  • Excellent security hardening with SESSION_FILE_PATTERN validation on all SessionService methods
  • Smart use of append-only system records for custom titles with proper parentUuid chaining
  • Efficient 64KB tail-read strategy (readCustomTitleFromFile) instead of full file scans
  • Comprehensive test coverage across CLI commands and core services
  • Proper fd leak prevention with try/finally blocks in file operations
  • Good cross-platform support (CLI, VSCode, WebUI) with consistent behavior

Architectural decisions:

  • Storing custom titles as system records in the JSONL file is elegant and ensures persistence
  • The dual-path approach (ChatRecordingService for recording sessions, SessionService fallback) handles edge cases well
  • Case-insensitive title matching for --resume is user-friendly

Recurring patterns:

  • Consistent validation patterns across all three platforms (CLI, VSCode, WebUI)
  • Proper error handling with user-friendly messages throughout

🎯 Specific Feedback

🔴 Critical

  • File: packages/cli/src/gemini.tsx:353-361 - The logic flow for handling --resume has a subtle issue. When argv.resume !== '' and !cliConfig.isValidSessionId(argv.resume) and matches.length === 0, the code falls through to the final else block which calls process.exit(1). However, this exit happens BEFORE loadCliConfig is called, which means any cleanup or initialization that would happen in the normal flow is skipped. Consider whether this early exit could leave resources in an inconsistent state.

  • File: packages/core/src/services/sessionService.ts:672-680 - The renameSession method reads the first record to verify project ownership, but if jsonl.readLines throws an error other than ENOENT, it's re-thrown. This could expose internal errors to callers. Consider catching and converting to a false return for all non-critical errors to maintain consistent error handling with removeSession.

🟡 High

  • File: packages/cli/src/ui/commands/renameCommand.ts:72-76 - The generateSessionTitle function extracts conversation text but has a potential issue: history could be empty or contain no text parts, resulting in an empty string being sent to the LLM. While the function returns null in this case, it would be better to check earlier and avoid the API call entirely. Also, the system instruction says "Reply with ONLY the kebab-case name" but there's no validation that the response is actually kebab-case.

  • File: packages/vscode-ide-companion/src/webview/components/SessionSelector.tsx:233-238 - The rename input's onBlur handler calls handleRenameSubmit, which will submit even if the user just wants to cancel. The Escape key handler sets renamingSessionId(null) but doesn't restore the original value in the UI. This could lead to accidental renames with partial edits.

  • File: packages/core/src/services/sessionService.ts:648-656 - The readLastRecordUuid function doesn't validate SESSION_FILE_PATTERN before reading the file. While callers should validate, adding this check would provide defense-in-depth consistent with other methods like renameSession and removeSession.

🟢 Medium

  • File: packages/cli/src/ui/components/BaseTextInput.tsx:247-253 - The label width calculation assumes the label won't exceed available columns. If topRightLabel is very long (close to or exceeding columns), dashCount could become negative or zero, resulting in visual glitches. Consider adding Math.max(0, ...) for the dash count calculation.

  • File: packages/cli/src/ui/commands/renameCommand.ts:40-43 - The extractConversationText function walks backwards through history with texts.length < 6, but the comment says "~1000 chars". These two constraints could conflict - 6 messages could be much more or less than 1000 chars. Consider making the character limit the primary constraint instead of message count.

  • File: packages/core/src/services/sessionService.ts:707-715 - The findSessionsByTitle method paginates through ALL sessions to find matches. For users with many sessions, this could be slow. Consider adding an optional limit parameter or early termination after finding N matches (since multiple matches just trigger the picker anyway).

  • File: packages/vscode-ide-companion/src/services/qwenSessionReader.ts:423-426 - The renameSession method uses crypto.randomUUID() but doesn't import it at the top of the file (uses namespace import * as crypto). While this works, consider using the same randomUUID import pattern as sessionService.ts for consistency.

🔵 Low

  • File: packages/cli/src/ui/commands/renameCommand.ts:1 - Missing type import comment. The file imports Content from @google/genai but this import appears unused (the function uses config.getGeminiClient().getHistory(true) which should already return the proper type).

  • File: packages/cli/src/config/config.ts:574 - The comment // --resume accepts either a session UUID or a custom title is helpful, but consider adding a brief note about the validation that happens in gemini.tsx before this point for future maintainers.

  • File: packages/cli/src/ui/components/SessionPicker.tsx:130 - The title prop defaults to "Resume Session" via nullish coalescing, but the delete dialog also uses this component with title={t('Delete Session')}. Consider extracting the default to a constant or making it a required prop for clarity.

  • File: packages/core/src/services/chatRecordingService.ts:60 - The new custom_title subtype is added to the union type, but there's no JSDoc comment explaining when this subtype is used (unlike other subtypes which have more context). Consider adding a brief comment.

  • File: packages/cli/src/ui/hooks/useDeleteCommand.ts:44-52 - The check sessionId === config.getSessionId() prevents deleting the current session, but this comparison might fail if one is a UUID and the other has additional suffix (e.g., -agent-{suffix} mentioned in the isValidSessionId comment). Consider normalizing both IDs before comparison.

✅ Highlights

  • Security hardening - All SessionService methods that construct file paths now validate against SESSION_FILE_PATTERN, providing excellent defense-in-depth against path traversal attacks

  • Parent UUID chaining - The careful handling of parentUuid in renameSession (reading the last record's UUID before appending the title record) shows deep understanding of the history reconstruction algorithm

  • Efficient I/O - The 64KB tail-read strategy in readCustomTitleFromFile and readLastRecordUuid demonstrates performance-conscious design, avoiding full file scans for common operations

  • FD leak prevention - Consistent use of try/finally blocks to ensure fs.closeSync is called even if read operations fail

  • Test coverage - Comprehensive test files for both CLI commands (renameCommand.test.ts, deleteCommand.test.ts) and core services (sessionService.rename.test.ts, chatRecordingService.customTitle.test.ts) covering edge cases like empty input, multiple matches, and file errors

  • Cross-platform consistency - The same patterns and validations are implemented across CLI, VSCode, and WebUI, ensuring consistent user experience

@@ -113,6 +117,8 @@ const MAX_FILES_TO_PROCESS = 10000;
const SESSION_FILE_PATTERN = /^[0-9a-fA-F-]{32,36}\.jsonl$/;
/** Maximum number of lines to scan when looking for the first prompt text. */
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.

[P0 · 正确性] TAIL_READ_SIZE = 64KB 会导致长会话丢失自定义标题

readCustomTitleFromFile 只读文件最后 64KB。如果用户 /rename 后继续大量对话,custom_title 记录会被推到距文件末尾 >64KB 的位置,tail-read 就找不到它了。

64KB ≈ 600-700 条典型 chat record,长对话很容易超过。影响范围:

  • getSessionTitle() 返回 undefined → CLI 标签消失
  • listSessions()customTitle 丢失 → picker 不显示自定义名称
  • findSessionsByTitle() 依赖 listSessions--resume <title> 查找失败

建议方案:

  1. 将 custom_title 同时写入独立 sidecar 文件(如 <sessionId>.title),读标题时优先查 sidecar
  2. 或者 readCustomTitleFromFile 未找到时 fallback 到全文扫描
  3. 或者改变追加策略:每次 rename 同时在文件末尾追加新记录(已经是这样),但读取时增大 buffer / 全文扫

}),
getSessionId: vi.fn().mockReturnValue('test-session-id'),
getSessionService: vi.fn().mockReturnValue({
renameSession: vi.fn().mockResolvedValue(true),
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.

[P1 · 测试] 断言值与代码实际行为不匹配,测试应该会 fail

测试期望空输入返回 'Please provide a name. Usage: /rename <name>'。但实际代码路径:

  1. name = ''.trim()''
  2. if (!name) → 进入自动生成分支
  3. generateSessionTitle(config) → mock 没有 getGeminiClient/getContentGenerator → catch 后返回 null
  4. 最终返回 'Could not generate a title. Usage: /rename <name>'

期望值应改为 'Could not generate a title. Usage: /rename <name>'

下面 line 107 的 whitespace-only test 也有相同问题。

另外建议补充一个正向的 auto-generate 测试(mock LLM 返回生成标题的场景)。

const title = params['title'] as string;
if (!sessionId || !SESSION_ID_RE.test(sessionId)) {
throw RequestError.invalidParams(
undefined,
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.

[P1 · 安全] ACP renameSession 缺少 title 长度校验

CLI (renameCommand.ts) 限制了 MAX_TITLE_LENGTH = 200SessionMessageHandler.ts 也做了 200 字符检查。但此处只验证了 title 非空和类型是 string,没有长度上限。

恶意 ACP 客户端可发送超长 title → 完整写入 JSONL → readCustomTitleFromFile 的 64KB 读取只拿到 title 的一部分 → JSON.parse 失败 → 标题丢失。

建议加长度检查:

if (title.length > 200) {
  throw RequestError.invalidParams(undefined, 'Title too long (max 200 chars)');
}

} catch {
return null;
}
}
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.

[P1 · 代码质量] console.log 残留在生产代码中

console.log('[QwenSessionReader] Renaming session:', sessionId, title);

会在 VSCode 输出面板打印用户的 session title。应删除或改为 console.debug

*/
private readLastRecordUuid(filePath: string): string | null {
try {
const TAIL_SIZE = 64 * 1024;
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.

[P2 · 代码质量] readLastRecordUuid 在两个包中完全重复

sessionService.tsqwenSessionReader.tsreadLastRecordUuid 逻辑完全一致(64KB tail-read + 反向遍历 + JSON.parse)。readCustomTitleFromFile 的 buffer 读取模式也类似。

建议抽取到 packages/core 的共享工具函数,如 readTailLines(filePath, maxBytes): string[]

writeStderrLine(
`Multiple sessions found with title "${argv.resume}". Please select one:`,
);
resolvedSessionId = await showResumeSessionPicker();
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.

[P2 · UX] 多个 title 匹配时 picker 显示所有 session

matches.length > 1 时,提示 Multiple sessions found with title "xxx",然后 showResumeSessionPicker() 显示全部 session。用户看到上百个 session 却不知道哪些匹配。

建议传入匹配列表做预过滤,或高亮匹配项。

// Close dialog immediately.
closeDeleteDialog();

// Prevent deleting the current session.
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.

[P2 · UX] CLI /delete 没有确认步骤,删除不可恢复

WebUI/VSCode 有两步确认(点 delete → 再点 "Delete?"),但 CLI 的流程是 SessionPicker → 选中 → 直接 removeSessionfs.unlinkSync)。

用户可能误选 session 就被永久删除了。建议:

  1. 选中后 addItem 显示确认提示,要求二次输入确认
  2. 或者在 handleDelete 前加一个 "Are you sure?" dialog

const [renamingSessionId, setRenamingSessionId] = useState<string | null>(
null,
);
const [renameValue, setRenameValue] = useState('');
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.

[P2 · 一致性] 标题长度限制 200 在多处硬编码,没有共享常量

maxLength={200} 这个值分散在 4 个地方:

  • renameCommand.ts: MAX_TITLE_LENGTH = 200
  • SessionMessageHandler.ts: trimmedTitle.length > 200
  • SessionSelector.tsx: maxLength={200}
  • ACP 端:缺失(见另一条评论)

建议在 packages/core 定义共享常量 SESSION_TITLE_MAX_LENGTH,各处引用。

return true;
} catch (error) {
console.error('[QwenSessionReader] Failed to delete session:', error);
return false;
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.

[P2 · 一致性] VSCode rename record 缺少 gitBranch,且 version: 'vscode' 不是语义化版本

CLI 通过 ChatRecordingService.recordCustomTitle() 创建的 record 包含 gitBranch 和真实版本号。但 VSCode 路径创建的 record:

  • 没有 gitBranch
  • version: 'vscode' 不是版本号

sessionService.ts:renameSession 也缺少 gitBranch。record 格式不一致可能给未来迁移/分析带来困惑。

gitBranch: firstRecord.gitBranch,
filePath,
messageCount,
customTitle: this.readCustomTitleFromFile(filePath),
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.

[P2 · 性能] listSessions 为每个 session 同步调用 readCustomTitleFromFile

listSessions 遍历时为每个文件执行同步 I/O(openSync + readSync + closeSync)。100 个 session = 100 次额外同步磁盘读取,可能在 session picker 等交互场景造成 UI 卡顿。

建议考虑:

  1. 将标题读取与现有的 readLines 调用合并(已经在读文件了)
  2. 或改为 async I/O
  3. 或按需 lazy-load(仅对 viewport 内的 session 读取标题)

);

const columns = process.stdout.columns || 80;
// Build the top border line: ─────── label ──
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.

[P2 · Correctness] topRightLabel.length is wrong for non-ASCII characters — border will be misaligned

labelWidth = topRightLabel.length + 4 uses JavaScript's .length which counts UTF-16 code units, not terminal display columns. CJK characters (e.g. Chinese session names) occupy 2 terminal columns but .length returns 1, so the dash count will be too high and the border line will overflow/wrap.

Example: a label "修复登录" has .length === 4 but takes 8 terminal columns.

Since /rename accepts arbitrary user input (not just kebab-case), this is a real display bug for international users.

Fix: use a string-width library (e.g. string-width which is already commonly available in Ink-based projects) to calculate the actual display width:

import stringWidth from 'string-width';
const labelWidth = topRightLabel ? stringWidth(topRightLabel) + 4 : 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.

[P2 · Robustness] recordCustomTitle silently swallows errors, but setSessionName is called unconditionally — UI/data inconsistency

recordCustomTitle() has an internal try-catch that swallows write errors (just logs to debug). But context.ui.setSessionName(name) on line 151 runs regardless of whether the file write succeeded. This means:

  • Disk full / permission error → file write fails silently
  • UI tag shows the new name → user thinks rename worked
  • On next resume, the title is gone (never persisted)

Two options to fix:

  1. Make recordCustomTitle return a boolean indicating success, and only call setSessionName on success
  2. Or change recordCustomTitle to rethrow, and wrap the call here in a try-catch

The current code is a silent data-loss scenario.

<SessionPicker
sessionService={config.getSessionService()}
currentBranch={uiState.branchName}
onSelect={uiActions.handleDelete}
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.

[P3 · UX] Delete picker shows the current active session as selectable

The SessionPicker shows all sessions including the current one. If the user selects the current session, handleDelete rejects it with an info message "Cannot delete the current active session." — but this happens after the picker closes.

Better UX: pass the current session ID to SessionPicker and visually disable or hide it in the delete view, so users don't waste a click on an un-deletable item. The resume picker doesn't have this problem because resuming the current session is a valid (albeit no-op) action.

@@ -69,6 +69,9 @@ export interface BaseTextInputProps {
prefix?: React.ReactNode;
/** Border color for the input box. */
borderColor?: string;
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.

[P3 · Nit] Duplicate JSDoc comment

Two consecutive doc-comments for topRightLabel:

/** Label overlaid on the top border (right-aligned). */
/** Label rendered on the top border line (right-aligned). Plain string for width calculation. */

Keep only the second one.

…itle feature

- Fix renameCommand.ts import path to use barrel export instead of deep path
- Add setSessionName to mock CommandContext
- Add getSessionTitle to SessionService mock in useResumeCommand tests
- Update renameCommand tests for auto-generate title behavior
- Update InputPrompt snapshots

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@lnxsun
Copy link
Copy Markdown

lnxsun commented Apr 11, 2026

我的#3105 被毙了,期待qqqys你的成果

toggleVimEnabled,
setGeminiMdFileCount,
reloadCommands,
setSessionName: setSessionName ?? (() => {}),
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.

[P1 · 正确性] 新 session 后旧的 title tag 不会被清掉

现在 sessionName 被挂进 ui 上下文了,但 ui.clear() 这里只清了 history/screen,没有一起 setSessionName(null)

/clear/new 会先 startNewSession(),然后走 context.ui.clear(),所以新会话仍然会显示上一条会话的自定义标题,直到再次 /rename/resume

建议把 sessionName 也纳入 clear/reset 路径,一起在这里清空,避免新会话带着旧标签。


if (matches.length > 1) {
// Multiple matches — show picker to let user choose
return { type: 'dialog', dialog: 'resume' };
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.

[P2 · 交互一致性] 多个同名 session 时,这里打开的是“全量 picker”,不是“匹配结果 picker”

findSessionsByTitle(arg) 已经把匹配集算出来了,但这里直接回退到通用 resume dialog,实际会展示所有 session。

这样用户输入 /resume my-title 且命中多个结果时,还得重新在全量列表里找一遍,甚至可能误选到不相关的 session。PR 描述里写的是“multiple sessions match, the interactive picker is shown”,按这个语义更像是应该只在匹配结果里二次选择。

建议把匹配集传给 picker,或者单独做一个 choose from matches 分支;gemini.tsx--resume <title> 路径也有同样问题,最好共用一套实现。

Copy link
Copy Markdown
Collaborator

@yiliang114 yiliang114 left a comment

Choose a reason for hiding this comment

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

整体方向是对的,session rename/delete/auto-title 这套功能也补得比较完整。

不过我这边又看到一个阻塞性的状态问题:新 session 之后旧的 title tag 还会残留在输入框上;另外 multiple title matches 的 resume 交互也还需要再收一下。加上前面已有的几个阻塞点,这轮我先挂 request changes,具体看 inline comments。

qqqys and others added 5 commits April 12, 2026 15:22
…finalize mechanism

- Add sessionStorageUtils with extractLastJsonStringField() for fast
  string-level JSON field extraction without full parse
- Add readHeadAndTailSync() to read first and last 64KB of session files
- Replace readCustomTitleFromFile() with readSessionTitleFromFile() using
  head+tail dual-read (tail customTitle > head customTitle)
- Add finalize() to ChatRecordingService as single entry point for
  re-appending session metadata on any session departure
- Call finalize() on resume, session switch, and shutdown
- Export sessionStorageUtils from core package

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…ple sessions

Previously, multiple title matches opened the full session picker,
forcing the user to re-find their session. Now the matched sessions
are passed through as initialSessions to the picker, skipping the
full listSessions() load and showing only the relevant results.

Also clears sessionName on /clear so new sessions don't carry stale
title tags from the previous session.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
topRightLabel.length counts UTF-16 code units, not terminal columns.
CJK characters take 2 columns but .length returns 1, causing the
border line to overflow. Use string-width for correct display width.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
- Add SESSION_TITLE_MAX_LENGTH shared constant in core, replace
  hardcoded 200 in CLI/ACP/VSCode/WebUI
- Add title length validation to ACP renameSession endpoint
- Make recordCustomTitle return boolean; renameCommand checks it
  before updating UI to prevent silent data loss
- Add gitBranch to VSCode rename record for consistency with CLI
- Remove misleading "enforce kebab-case" comment
- Remove duplicate JSDoc on topRightLabel

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
The static "Generating session name…" text gave no visual feedback that
the operation was in progress. Cycle through ".", "..", "..." every
500ms so users can tell the LLM call is still running.

Co-Authored-By: Qwen-Coder <noreply@qwen.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

[VS Code Chat] Allow name change for session

6 participants