feat(coding): keep one ongoing conversation per delivered agent

This commit is contained in:
2026-09-28 14:19:02 +08:00
parent e54f3d234d
commit 7aa81da616
23 changed files with 1205 additions and 160 deletions

View File

@@ -1,5 +1,5 @@
import path from 'node:path';
import type { TeacherRequest, TeacherSourceContext } from '../../shared/coding-teacher';
import type { TeacherRequest, TeacherSourceContext, TeacherSourceMessage } from '../../shared/coding-teacher';
import { CodingProjectFileService } from '../coding-projects/project-files';
import { excerptTeacherText, teacherHistoryMessages } from './context';
import { readInteger, readPage, textChunks } from './read-page';
@@ -14,6 +14,7 @@ export interface TeacherReadAccess {
projectPath: string;
source: TeacherSourceContext;
history?: TeacherRequest[];
archive?: { messages: TeacherSourceMessage[]; read(id: string): Promise<TeacherSourceMessage | undefined> };
assertCurrent(): void;
}
@@ -32,7 +33,7 @@ export const teacherReadToolDefinitions = [
parameters: { type: 'object', properties: { path: { type: 'string' }, ...lineParameters }, required: ['path'], additionalProperties: false },
} },
{ type: 'function', function: {
name: 'read_conversation', description: 'Read the captured active coding conversation and current teacher topic. Omit message_id to list messages; supply it to read continuous original text. Results give start_line/start_column and next; do not reread unchanged ranges.',
name: 'read_conversation', description: 'Read the captured active coding conversation and this agent chat history. Historical messages can belong to earlier projects and are labelled as such; files remain limited to the current project. Omit message_id to list messages; supply it to read continuous original text. Results give start_line/start_column and next; do not reread unchanged ranges.',
parameters: { type: 'object', properties: { message_id: { type: 'string' }, ...lineParameters }, additionalProperties: false },
} },
];
@@ -50,6 +51,7 @@ function projectPath(value: unknown): string {
export function createTeacherReadTools(access: TeacherReadAccess) {
const files = new CodingProjectFileService();
const messages = [...access.source.messages, ...teacherHistoryMessages(access.history ?? [])];
const index = [...access.source.messages, ...(access.archive?.messages ?? teacherHistoryMessages(access.history ?? []))];
return {
definitions: teacherReadToolDefinitions,
async execute(name: string, rawArguments: string, signal: AbortSignal, maxBytes = TEACHER_READ_PAGE_BYTES): Promise<string> {
@@ -96,9 +98,10 @@ export function createTeacherReadTools(access: TeacherReadAccess) {
break;
}
case 'read_conversation': {
const message = args.message_id === undefined ? undefined : messages.find(item => item.id === args.message_id);
const message = args.message_id === undefined ? undefined : messages.find(item => item.id === args.message_id)
?? (typeof args.message_id === 'string' ? await access.archive?.read(args.message_id) : undefined);
if (args.message_id !== undefined && !message) throw new Error('Message is not in the current conversation.');
const text = message?.text ?? (messages.map(item =>
const text = message?.text ?? (index.map(item =>
`${item.id} ${item.role}: ${excerptTeacherText(item.text, 180).replaceAll('\n', ' ')}`).join('\n')
|| '(no completed text messages in this conversation)');
({ content: result, truncated } = await readPage(message ? message.id + ' ' + message.role : 'conversation message index (snippets)',