Skip to content

Commit 70f2436

Browse files
authored
feat: add session renaming with custom titles and git branch tracking (#781)
1 parent b1894ec commit 70f2436

6 files changed

Lines changed: 403 additions & 85 deletions

File tree

src/nodeBridge.types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,17 @@ type SessionsRemoveOutput = {
836836
error?: string;
837837
};
838838

839+
type SessionsRenameInput = {
840+
cwd: string;
841+
sessionId: string;
842+
title: string;
843+
};
844+
845+
type SessionsRenameOutput = {
846+
success: boolean;
847+
error?: string;
848+
};
849+
839850
// ============================================================================
840851
// Sessions Handlers
841852
// ============================================================================
@@ -851,6 +862,7 @@ type SessionsListOutput = {
851862
modified: Date;
852863
created: Date;
853864
messageCount: number;
865+
gitBranch?: string;
854866
summary: string;
855867
}>;
856868
};
@@ -1730,6 +1742,10 @@ export type HandlerMap = {
17301742
input: SessionsRemoveInput;
17311743
output: SessionsRemoveOutput;
17321744
};
1745+
'sessions.rename': {
1746+
input: SessionsRenameInput;
1747+
output: SessionsRenameOutput;
1748+
};
17331749

17341750
// Sessions handlers
17351751
'sessions.list': { input: SessionsListInput; output: SessionsListOutput };

src/nodeBridge/slices/session.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,36 @@ export function registerSessionHandlers(
779779
};
780780
});
781781

782+
messageBus.registerHandler('sessions.rename', async (data) => {
783+
const { cwd, sessionId, title } = data;
784+
try {
785+
const context = await getContext(cwd);
786+
const { appendFileSync, existsSync } = await import('fs');
787+
const logPath = context.paths.getSessionLogPath(sessionId);
788+
789+
if (!existsSync(logPath)) {
790+
return {
791+
success: false,
792+
error: `Session "${sessionId}" not found`,
793+
};
794+
}
795+
796+
const line = JSON.stringify({
797+
type: 'custom-title',
798+
customTitle: title,
799+
sessionId,
800+
});
801+
appendFileSync(logPath, `${line}\n`);
802+
803+
return { success: true };
804+
} catch (error: any) {
805+
return {
806+
success: false,
807+
error: error.message || 'Failed to rename session',
808+
};
809+
}
810+
});
811+
782812
messageBus.registerHandler('sessions.remove', async (data) => {
783813
const { cwd, sessionId } = data;
784814
try {

src/paths.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export class Paths {
7676
const jsonlFiles = fs
7777
.readdirSync(this.globalProjectDir)
7878
.filter((file) => file.endsWith('.jsonl'))
79+
.filter((file) => {
80+
const sessionId = path.basename(file, '.jsonl');
81+
return !sessionId.startsWith('agent-');
82+
})
7983
.map((file) => {
8084
const filePath = path.join(this.globalProjectDir, file);
8185
const stats = fs.statSync(filePath);
@@ -84,12 +88,15 @@ export class Paths {
8488
// Read message count and summary
8589
let messageCount = 0;
8690
let summary = '';
91+
let gitBranch: string | undefined;
92+
let customTitle: string | undefined;
8793
try {
8894
const content = fs.readFileSync(filePath, 'utf-8');
8995
const lines = content.split('\n').filter(Boolean);
9096
messageCount = lines.length;
9197

9298
// Extract summary: prioritize config.summary, fallback to first user message
99+
// Also scan all lines for custom-title and gitBranch
93100
if (lines.length > 0) {
94101
try {
95102
const firstEntry: LogEntry = JSON.parse(lines[0]);
@@ -101,6 +108,20 @@ export class Paths {
101108
} catch (e) {
102109
summary = extractFirstUserMessageSummary(lines);
103110
}
111+
112+
for (const line of lines) {
113+
try {
114+
const entry = JSON.parse(line);
115+
if (entry.type === 'custom-title' && entry.customTitle) {
116+
customTitle = entry.customTitle;
117+
}
118+
if (entry.gitBranch) {
119+
gitBranch = entry.gitBranch;
120+
}
121+
} catch (e) {
122+
// ignore parse error
123+
}
124+
}
104125
}
105126
} catch (e) {
106127
// ignore read error, message count is 0
@@ -111,7 +132,8 @@ export class Paths {
111132
modified: stats.mtime,
112133
created: stats.birthtime,
113134
messageCount,
114-
summary: normalizeSummary(summary),
135+
gitBranch,
136+
summary: normalizeSummary(customTitle ?? summary),
115137
};
116138
})
117139
.sort((a, b) => b.modified.getTime() - a.modified.getTime())

src/project.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { TOOL_NAMES } from './constants';
22
import type { Context } from './context';
33
import { JsonlLogger, RequestLogger } from './jsonl';
4+
import { getCurrentBranch } from './utils/git';
45
import { LlmsContext } from './llmsContext';
56
import { runLoop, type StreamResult, type ThinkingConfig } from './loop';
67
import type { ImagePart, NormalizedMessage, UserContent } from './message';
@@ -130,6 +131,7 @@ export class Project {
130131
const requestLogger = new RequestLogger({
131132
globalProjectDir: this.context.paths.globalProjectDir,
132133
});
134+
const gitBranch = await getCurrentBranch(this.context.cwd);
133135
if (message !== null) {
134136
message = await this.context.apply({
135137
hook: 'userPrompt',
@@ -185,6 +187,7 @@ export class Project {
185187
const userMessageWithSessionId = {
186188
...userMessage,
187189
sessionId: this.session.id,
190+
...(gitBranch ? { gitBranch } : {}),
188191
};
189192
jsonlLogger.addMessage({
190193
message: userMessageWithSessionId,
@@ -263,6 +266,7 @@ export class Project {
263266
const normalizedMessage = {
264267
...message,
265268
sessionId: this.session.id,
269+
...(gitBranch ? { gitBranch } : {}),
266270
};
267271
outputFormat.onMessage({
268272
message: normalizedMessage,

0 commit comments

Comments
 (0)