fix: isolate delivered agent chats by project
This commit is contained in:
@@ -135,31 +135,32 @@ export class CodingTeacherService {
|
||||
this.assertAccount(account);
|
||||
return { items: items.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)), lastSelectedTopicId: null };
|
||||
}
|
||||
private conversationStore(account: TeacherAccount, agentId: string) {
|
||||
private conversationStore(account: TeacherAccount, projectId: string, agentId: string) {
|
||||
if (!agentId || agentId.length > 128 || !/^[a-zA-Z0-9_-]+$/.test(agentId))
|
||||
throw new TeacherError(400, 'teacher_agent_invalid', '智能体标识无效。');
|
||||
const key = account.id + ':' + agentId;
|
||||
teacherTopicId(projectId);
|
||||
const key = account.id + ':' + projectId + ':' + agentId;
|
||||
let store = this.conversations.get(key);
|
||||
if (!store) {
|
||||
store = new TeacherConversationStore(path.join(this.options.userDataDir, 'agent-conversations', account.id, agentId), account.id, agentId);
|
||||
store = new TeacherConversationStore(path.join(this.options.userDataDir, 'agent-conversations', account.id, agentId, 'projects', projectId), account.id, agentId, projectId);
|
||||
this.conversations.set(key, store);
|
||||
}
|
||||
return store;
|
||||
}
|
||||
private async importConversations(account: TeacherAccount, agentId: string) {
|
||||
const store = this.conversationStore(account, agentId);
|
||||
if (![...this.active.keys()].some(key => key.startsWith(account.id + ':agent:' + agentId + ':'))) {
|
||||
for (const project of await this.options.projects.listProjects()) {
|
||||
this.assertAccount(account);
|
||||
await store.importProject(project);
|
||||
}
|
||||
private async importConversations(account: TeacherAccount, projectId: string, agentId: string) {
|
||||
const project = await this.options.projects.getProject(projectId);
|
||||
const store = this.conversationStore(account, projectId, agentId);
|
||||
if (![...this.active.keys()].some(key => key.startsWith(account.id + ':agent:' + projectId + ':' + agentId + ':'))) {
|
||||
this.assertAccount(account);
|
||||
await store.importGlobal(path.join(this.options.userDataDir, 'agent-conversations', account.id, agentId));
|
||||
await store.importProject(project);
|
||||
}
|
||||
return store;
|
||||
}
|
||||
async conversation(agentId: string, before?: string) {
|
||||
async conversation(projectId: string, agentId: string, before?: string) {
|
||||
const account = await this.account();
|
||||
return this.serialize(account.id + ':agent:' + agentId + ':acceptance', async () => {
|
||||
const store = await this.importConversations(account, agentId);
|
||||
return this.serialize(this.acceptanceKey(account, { projectId, agentId, sourceId: 'project' }), async () => {
|
||||
const store = await this.importConversations(account, projectId, agentId);
|
||||
if (!await store.exists()) return { topic: null, before: null };
|
||||
const topic = await store.read();
|
||||
const page = await store.page(before);
|
||||
@@ -172,7 +173,7 @@ export class CodingTeacherService {
|
||||
const account = await this.account();
|
||||
const scope: TeacherScope = { projectId: input.projectId, sourceId: 'project', agentId };
|
||||
return this.serialize(this.acceptanceKey(account, scope), async () => {
|
||||
const store = await this.importConversations(account, agentId);
|
||||
const store = await this.importConversations(account, scope.projectId, agentId);
|
||||
if (!await store.exists()) {
|
||||
const catalog = await (this.options.catalog ?? teacherCatalog)(account);
|
||||
const selected = catalog.items.find(item => item.teacher_id === agentId);
|
||||
@@ -185,11 +186,11 @@ export class CodingTeacherService {
|
||||
return this.sendRequest(account, scope, topic.id, input);
|
||||
});
|
||||
}
|
||||
async markConversationSeen(agentId: string, requestId: string) {
|
||||
async markConversationSeen(projectId: string, agentId: string, requestId: string) {
|
||||
const account = await this.account();
|
||||
const scope = { agentId, projectId: '', sourceId: 'project' };
|
||||
const scope = { agentId, projectId, sourceId: 'project' };
|
||||
return this.serialize(this.acceptanceKey(account, scope), async () => {
|
||||
const store = this.conversationStore(account, agentId);
|
||||
const store = this.conversationStore(account, projectId, agentId);
|
||||
this.assertAccount(account);
|
||||
await store.markSeen(requestId);
|
||||
const topic = await store.read();
|
||||
@@ -201,7 +202,7 @@ export class CodingTeacherService {
|
||||
account: TeacherAccount,
|
||||
scope: TeacherScope
|
||||
): Promise<TeacherTopicStore | TeacherConversationStore> {
|
||||
if (scope.agentId) return this.conversationStore(account, scope.agentId);
|
||||
if (scope.agentId) return this.conversationStore(account, scope.projectId, scope.agentId);
|
||||
if (this.deletingSources.has(scope.projectId + ':' + scope.sourceId))
|
||||
throw new TeacherError(404, 'teacher_source_not_found', '来源会话已删除。');
|
||||
let directory: string;
|
||||
@@ -227,11 +228,11 @@ export class CodingTeacherService {
|
||||
return store;
|
||||
}
|
||||
private key(account: TeacherAccount, scope: TeacherScope, id: string) {
|
||||
if (scope.agentId) return account.id + ':agent:' + scope.agentId + ':' + id;
|
||||
if (scope.agentId) return account.id + ':agent:' + scope.projectId + ':' + scope.agentId + ':' + id;
|
||||
return account.id + ':' + scope.projectId + ':' + scope.sourceId + ':' + (scope.role ?? 'teacher') + ':' + id;
|
||||
}
|
||||
private acceptanceKey(account: TeacherAccount, scope: TeacherScope) {
|
||||
if (scope.agentId) return account.id + ':agent:' + scope.agentId + ':acceptance';
|
||||
if (scope.agentId) return this.key(account, scope, 'acceptance');
|
||||
return account.id + ':' + scope.projectId + ':teacher-acceptance';
|
||||
}
|
||||
private async serialize<T>(key: string, operation: () => Promise<T>): Promise<T> {
|
||||
@@ -317,8 +318,8 @@ export class CodingTeacherService {
|
||||
const topic = (await store.read(id)) as PreviewTopic;
|
||||
if (
|
||||
topic.accountId !== account.id ||
|
||||
(scope.agentId ? topic.conversation?.agentId !== scope.agentId :
|
||||
topic.projectId !== scope.projectId || topic.sourceConversationId !== scope.sourceId) ||
|
||||
topic.projectId !== scope.projectId ||
|
||||
(scope.agentId ? topic.conversation?.agentId !== scope.agentId : topic.sourceConversationId !== scope.sourceId) ||
|
||||
(topic.role ?? 'teacher') !== (scope.role ?? 'teacher')
|
||||
)
|
||||
throw new TeacherError(404, 'teacher_topic_not_found', '智能体话题不存在。');
|
||||
@@ -712,6 +713,7 @@ export class CodingTeacherService {
|
||||
const next = structuredClone(topic);
|
||||
if (next.conversation) {
|
||||
if (!input.projectId) throw new TeacherError(422, 'teacher_project_required', '请选择讨论所属的项目。');
|
||||
if (input.projectId !== scope.projectId) throw new TeacherError(422, 'teacher_project_mismatch', '讨论不属于当前项目。');
|
||||
await this.options.projects.getProject(input.projectId);
|
||||
next.discussion = next.conversation.discussions[input.projectId];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user