feat: Add Welcome Back Dialog, Project Summary, and Enhanced Quit Options (#553)

This commit is contained in:
pomelo
2025-09-11 16:59:26 +08:00
committed by GitHub
parent 03eb1faf0a
commit b1a1ce530e
28 changed files with 1322 additions and 24 deletions

View File

@@ -540,3 +540,33 @@ The structure MUST be as follows:
</state_snapshot>
`.trim();
}
/**
* Provides the system prompt for generating project summaries in markdown format.
* This prompt instructs the model to create a structured markdown summary
* that can be saved to a file for future reference.
*/
export function getProjectSummaryPrompt(): string {
return `Please analyze the conversation history above and generate a comprehensive project summary in markdown format. Focus on extracting the most important context, decisions, and progress that would be valuable for future sessions. Generate the summary directly without using any tools.
You are a specialized context summarizer that creates a comprehensive markdown summary from chat history for future reference. The markdown format is as follows:
# Project Summary
## Overall Goal
<!-- A single, concise sentence describing the user's high-level objective -->
## Key Knowledge
<!-- Crucial facts, conventions, and constraints the agent must remember -->
<!-- Include: technology choices, architecture decisions, user preferences, build commands, testing procedures -->
## Recent Actions
<!-- Summary of significant recent work and outcomes -->
<!-- Include: accomplishments, discoveries, recent changes -->
## Current Plan
<!-- The current development roadmap and next steps -->
<!-- Use status markers: [DONE], [IN PROGRESS], [TODO] -->
<!-- Example: 1. [DONE] Set up WebSocket server -->
`.trim();
}

View File

@@ -44,6 +44,7 @@ export * from './utils/textUtils.js';
export * from './utils/formatters.js';
export * from './utils/filesearch/fileSearch.js';
export * from './utils/errorParsing.js';
export * from './utils/projectSummary.js';
// Export services
export * from './services/fileDiscoveryService.js';

View File

@@ -0,0 +1,119 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as fs from 'fs/promises';
import * as path from 'path';
export interface ProjectSummaryInfo {
hasHistory: boolean;
content?: string;
timestamp?: string;
timeAgo?: string;
goalContent?: string;
planContent?: string;
totalTasks?: number;
doneCount?: number;
inProgressCount?: number;
todoCount?: number;
pendingTasks?: string[];
}
/**
* Reads and parses the project summary file to extract structured information
*/
export async function getProjectSummaryInfo(): Promise<ProjectSummaryInfo> {
const summaryPath = path.join(process.cwd(), '.qwen', 'PROJECT_SUMMARY.md');
try {
await fs.access(summaryPath);
} catch {
return {
hasHistory: false,
};
}
try {
const content = await fs.readFile(summaryPath, 'utf-8');
// Extract timestamp if available
const timestampMatch = content.match(/\*\*Update time\*\*: (.+)/);
const timestamp = timestampMatch
? timestampMatch[1]
: new Date().toISOString();
// Calculate time ago
const getTimeAgo = (timestamp: string) => {
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMinutes = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffDays > 0) {
return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
} else if (diffHours > 0) {
return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
} else if (diffMinutes > 0) {
return `${diffMinutes} minute${diffMinutes > 1 ? 's' : ''} ago`;
} else {
return 'just now';
}
};
const timeAgo = getTimeAgo(timestamp);
// Parse Overall Goal section
const goalSection = content.match(
/## Overall Goal\s*\n?([\s\S]*?)(?=\n## |$)/,
);
const goalContent = goalSection ? goalSection[1].trim() : '';
// Parse Current Plan section
const planSection = content.match(
/## Current Plan\s*\n?([\s\S]*?)(?=\n## |$)/,
);
const planContent = planSection ? planSection[1] : '';
const planLines = planContent.split('\n').filter((line) => line.trim());
const doneCount = planLines.filter((line) =>
line.includes('[DONE]'),
).length;
const inProgressCount = planLines.filter((line) =>
line.includes('[IN PROGRESS]'),
).length;
const todoCount = planLines.filter((line) =>
line.includes('[TODO]'),
).length;
const totalTasks = doneCount + inProgressCount + todoCount;
// Extract pending tasks
const pendingTasks = planLines
.filter(
(line) => line.includes('[TODO]') || line.includes('[IN PROGRESS]'),
)
.map((line) => line.replace(/^\d+\.\s*/, '').trim())
.slice(0, 3);
return {
hasHistory: true,
content,
timestamp,
timeAgo,
goalContent,
planContent,
totalTasks,
doneCount,
inProgressCount,
todoCount,
pendingTasks,
};
} catch (_error) {
return {
hasHistory: false,
};
}
}