feat: auto-name sessions from first prompt

This commit is contained in:
codeman-local
2026-09-03 18:14:29 +08:00
parent 1e24817b51
commit 268e4819ff
9 changed files with 229 additions and 7 deletions
+27
View File
@@ -20,4 +20,31 @@ describe('session listener wiring', () => {
);
expect(registerAttachment).toHaveBeenNthCalledWith(2, 'wiring-attach-source-test', '/tmp/report.pdf', 'external');
});
it('renames an eligible session when its first prompt is submitted', () => {
const session = new Session({ id: 'wiring-auto-name-test', workingDir: '/tmp', name: 'w1-demo' });
const updateSessionName = vi.fn(() => true);
const persistSessionState = vi.fn();
const broadcast = vi.fn();
const getSessionStateWithRespawn = vi.fn(() => session.toState());
const deps = {
updateSessionName,
persistSessionState,
broadcast,
getSessionStateWithRespawn,
} as unknown as Parameters<typeof createSessionListeners>[1];
const refs = createSessionListeners(session, deps);
refs.promptSubmitted('整理登录模块并补充测试');
expect(session.name).toBe('整理登录模块并补充测试');
expect(updateSessionName).toHaveBeenCalledWith('wiring-auto-name-test', '整理登录模块并补充测试');
expect(persistSessionState).toHaveBeenCalledWith(session);
expect(broadcast).toHaveBeenCalled();
session.name = '人工命名';
refs.promptSubmitted('新的任务不能覆盖人工命名');
expect(session.name).toBe('人工命名');
expect(updateSessionName).toHaveBeenCalledTimes(1);
});
});
+29
View File
@@ -12,6 +12,7 @@
import { describe, it, expect } from 'vitest';
import { Session } from '../src/session.js';
import { deriveAutoSessionName, SubmittedPromptTracker } from '../src/session-auto-name.js';
describe('session submit anchor', () => {
it('records the pane Enter and carries it into persisted state', () => {
@@ -53,3 +54,31 @@ describe('session submit anchor', () => {
expect(recovered.lastSubmitAt).toBe(0);
});
});
describe('automatic session names', () => {
it('builds a bounded title from the first sentence without exposing controls', () => {
expect(deriveAutoSessionName(' 修复登录跳转问题。\n不要改数据库')).toBe('修复登录跳转问题。');
expect(deriveAutoSessionName('/clear')).toBeNull();
expect(deriveAutoSessionName('\x1b[31m整理项目文档\x1b[0m')).toBe('整理项目文档');
expect(Array.from(deriveAutoSessionName('a'.repeat(200)) ?? '')).toHaveLength(72);
});
it('tracks chunked typing, backspace, and Enter without treating arrows as prompt text', () => {
const tracker = new SubmittedPromptTracker();
expect(tracker.feed('修复登')).toEqual([]);
expect(tracker.feed('录跳转\x7f问题\r')).toEqual(['修复登录跳问题']);
expect(tracker.feed('旧内容\x1b[A新内容\r')).toEqual(['新内容']);
});
it('keeps manual names protected while generated names remain eligible', () => {
const generated = new Session({ workingDir: '/tmp', name: 'w1-demo' });
expect(generated.nameSource).toBe('auto');
expect(generated.applyAutoName('修复登录')).toBe(true);
expect(generated.applyAutoName('继续重命名')).toBe(true);
const manual = new Session({ workingDir: '/tmp', name: '我的工作窗口' });
expect(manual.nameSource).toBe('manual');
expect(manual.applyAutoName('不应覆盖')).toBe(false);
expect(manual.name).toBe('我的工作窗口');
});
});