mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
🐛 fix: workspace error (#15701)
feat: support workspace (page author, copyTo/transferTo, notifications, i18n & fixes) Squashed 13 commits from fix/workspace-error for clean rebase onto main's submodule base.
This commit is contained in:
@@ -58,6 +58,7 @@ export interface DocumentHistoryListItem {
|
||||
isCurrent: boolean;
|
||||
savedAt: string;
|
||||
saveSource: DocumentHistorySaveSource;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export interface ListHistoryOutput {
|
||||
|
||||
@@ -85,6 +85,7 @@ export const agentSignalRouter = router({
|
||||
return enqueueAgentSignalSourceEvent(sourceEvent, {
|
||||
agentId: input.agentId,
|
||||
userId: ctx.userId,
|
||||
workspaceId: ctx.workspaceId ?? undefined,
|
||||
});
|
||||
}),
|
||||
listReceipts: agentSignalProcedure
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { wsCompatProcedure } from '@/business/server/trpc-middlewares/workspaceAuth';
|
||||
import { AgentOperationModel } from '@/database/models/agentOperation';
|
||||
import { LlmGenerationTracingModel } from '@/database/models/llmGenerationTracing';
|
||||
import { VerifyCheckResultModel } from '@/database/models/verifyCheckResult';
|
||||
import { VerifyCriterionModel } from '@/database/models/verifyCriterion';
|
||||
import { VerifyRubricModel } from '@/database/models/verifyRubric';
|
||||
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
||||
import { router } from '@/libs/trpc/lambda';
|
||||
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
|
||||
import {
|
||||
VerifyExecutorService,
|
||||
@@ -35,18 +36,19 @@ const checkItemSchema = z.object({
|
||||
verifierType: verifierTypeSchema,
|
||||
});
|
||||
|
||||
const verifyProcedure = authedProcedure.use(serverDatabase).use(async (opts) => {
|
||||
const verifyProcedure = wsCompatProcedure.use(serverDatabase).use(async (opts) => {
|
||||
const { ctx } = opts;
|
||||
const workspaceId = ctx.workspaceId ?? undefined;
|
||||
return opts.next({
|
||||
ctx: {
|
||||
criterionModel: new VerifyCriterionModel(ctx.serverDB, ctx.userId),
|
||||
executorService: new VerifyExecutorService(ctx.serverDB, ctx.userId),
|
||||
tracingModel: new LlmGenerationTracingModel(ctx.serverDB, ctx.userId),
|
||||
feedbackService: new VerifyFeedbackService(ctx.serverDB, ctx.userId),
|
||||
operationModel: new AgentOperationModel(ctx.serverDB, ctx.userId),
|
||||
planGenerator: new VerifyPlanGeneratorService(ctx.serverDB, ctx.userId),
|
||||
resultModel: new VerifyCheckResultModel(ctx.serverDB, ctx.userId),
|
||||
rubricModel: new VerifyRubricModel(ctx.serverDB, ctx.userId),
|
||||
criterionModel: new VerifyCriterionModel(ctx.serverDB, ctx.userId, workspaceId),
|
||||
executorService: new VerifyExecutorService(ctx.serverDB, ctx.userId, workspaceId),
|
||||
tracingModel: new LlmGenerationTracingModel(ctx.serverDB, ctx.userId, workspaceId),
|
||||
feedbackService: new VerifyFeedbackService(ctx.serverDB, ctx.userId, workspaceId),
|
||||
operationModel: new AgentOperationModel(ctx.serverDB, ctx.userId, workspaceId),
|
||||
planGenerator: new VerifyPlanGeneratorService(ctx.serverDB, ctx.userId, workspaceId),
|
||||
resultModel: new VerifyCheckResultModel(ctx.serverDB, ctx.userId, workspaceId),
|
||||
rubricModel: new VerifyRubricModel(ctx.serverDB, ctx.userId, workspaceId),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -231,6 +231,57 @@ describe('AgentService', () => {
|
||||
// Avatar should not be present for non-builtin agents
|
||||
expect((result as any)?.avatar).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should NOT inherit the member personal default model for a workspace inbox', async () => {
|
||||
// Workspace inbox is persisted with an empty model/provider.
|
||||
const mockAgent = {
|
||||
id: 'agent-1',
|
||||
slug: 'inbox',
|
||||
};
|
||||
const serverDefaultConfig = { model: 'system-default-model', provider: 'system-provider' };
|
||||
|
||||
const mockAgentModel = {
|
||||
getBuiltinAgent: vi.fn().mockResolvedValue(mockAgent),
|
||||
};
|
||||
|
||||
(AgentModel as any).mockImplementation(() => mockAgentModel);
|
||||
(parseAgentConfig as any).mockReturnValue(serverDefaultConfig);
|
||||
// The member opening the workspace inbox has a personal default model.
|
||||
mockUserModel.getUserSettingsDefaultAgentConfig.mockResolvedValueOnce({
|
||||
config: { model: 'opus-4.6', provider: 'anthropic' },
|
||||
});
|
||||
|
||||
const workspaceService = new AgentService(mockDb, mockUserId, mockWorkspaceId);
|
||||
const result = await workspaceService.getBuiltinAgent('inbox');
|
||||
|
||||
// Should fall back to the system default, NOT the member's personal model.
|
||||
expect(result?.model).toBe('system-default-model');
|
||||
expect(result?.provider).toBe('system-provider');
|
||||
});
|
||||
|
||||
it('should still apply the personal default model for a personal inbox', async () => {
|
||||
const mockAgent = {
|
||||
id: 'agent-1',
|
||||
slug: 'inbox',
|
||||
};
|
||||
|
||||
const mockAgentModel = {
|
||||
getBuiltinAgent: vi.fn().mockResolvedValue(mockAgent),
|
||||
};
|
||||
|
||||
(AgentModel as any).mockImplementation(() => mockAgentModel);
|
||||
(parseAgentConfig as any).mockReturnValue({});
|
||||
mockUserModel.getUserSettingsDefaultAgentConfig.mockResolvedValueOnce({
|
||||
config: { model: 'user-preferred-model', provider: 'user-provider' },
|
||||
});
|
||||
|
||||
// No workspaceId → personal scope keeps the personal default behavior.
|
||||
const newService = new AgentService(mockDb, mockUserId);
|
||||
const result = await newService.getBuiltinAgent('inbox');
|
||||
|
||||
expect(result?.model).toBe('user-preferred-model');
|
||||
expect(result?.provider).toBe('user-provider');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAgentConfig', () => {
|
||||
|
||||
@@ -174,6 +174,13 @@ export class AgentService {
|
||||
* 2. serverDefaultAgentConfig - from environment variable
|
||||
* 3. userDefaultAgentConfig - from user settings (defaultAgent.config)
|
||||
* 4. agent - actual agent config from database
|
||||
*
|
||||
* Workspace exception: a workspace is a shared resource, so its agents must
|
||||
* NOT inherit any individual member's *personal* default model. Otherwise a
|
||||
* shared agent persisted with an empty model (e.g. the workspace inbox)
|
||||
* resolves to whoever opens it — the creator's personal default leaks in and
|
||||
* the workspace looks "initialized" with their model. For workspace-scoped
|
||||
* reads we skip the user layer and fall back to the system default instead.
|
||||
*/
|
||||
private mergeDefaultConfig(
|
||||
agent: any,
|
||||
@@ -181,12 +188,17 @@ export class AgentService {
|
||||
): LobeAgentConfig | null {
|
||||
if (!agent) return null;
|
||||
|
||||
const userDefaultAgentConfig =
|
||||
(defaultAgentConfig as { config?: PartialDeep<LobeAgentConfig> })?.config || {};
|
||||
|
||||
// Merge configs in order: DEFAULT -> server -> user -> agent
|
||||
// Merge configs in order: DEFAULT -> server -> [user] -> agent
|
||||
const serverDefaultAgentConfig = getServerDefaultAgentConfig();
|
||||
const baseConfig = merge(DEFAULT_AGENT_CONFIG, serverDefaultAgentConfig);
|
||||
|
||||
// Skip the personal default layer for workspace-scoped agents (see above).
|
||||
if (this.workspaceId) {
|
||||
return merge(baseConfig, cleanObject(agent));
|
||||
}
|
||||
|
||||
const userDefaultAgentConfig =
|
||||
(defaultAgentConfig as { config?: PartialDeep<LobeAgentConfig> })?.config || {};
|
||||
const withUserConfig = merge(baseConfig, userDefaultAgentConfig);
|
||||
|
||||
return merge(withUserConfig, cleanObject(agent));
|
||||
|
||||
@@ -344,11 +344,16 @@ export class CompletionLifecycle {
|
||||
metadata?.assistantMessageId,
|
||||
metadata?.userId || this.userId,
|
||||
);
|
||||
void runVerifyOnCompletion(this.serverDB, metadata?.userId || this.userId, {
|
||||
deliverable: event.lastAssistantContent ?? '',
|
||||
goal,
|
||||
operationId,
|
||||
});
|
||||
void runVerifyOnCompletion(
|
||||
this.serverDB,
|
||||
metadata?.userId || this.userId,
|
||||
{
|
||||
deliverable: event.lastAssistantContent ?? '',
|
||||
goal,
|
||||
operationId,
|
||||
},
|
||||
this.workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
if (reason === 'error') {
|
||||
|
||||
@@ -990,6 +990,7 @@ export class BotMessageRouter {
|
||||
agentId,
|
||||
db: serverDB,
|
||||
userId,
|
||||
workspaceId: workspaceId ?? undefined,
|
||||
},
|
||||
{ ignoreError: true },
|
||||
);
|
||||
@@ -1175,6 +1176,7 @@ export class BotMessageRouter {
|
||||
agentId,
|
||||
db: serverDB,
|
||||
userId,
|
||||
workspaceId: workspaceId ?? undefined,
|
||||
},
|
||||
{ ignoreError: true },
|
||||
);
|
||||
@@ -1392,6 +1394,7 @@ export class BotMessageRouter {
|
||||
agentId,
|
||||
db: serverDB,
|
||||
userId,
|
||||
workspaceId: workspaceId ?? undefined,
|
||||
},
|
||||
{ ignoreError: true },
|
||||
);
|
||||
|
||||
@@ -185,6 +185,7 @@ export class DocumentHistoryService {
|
||||
isCurrent: true,
|
||||
saveSource: 'system',
|
||||
savedAt: headDocument.updatedAt,
|
||||
userId: headDocument.userId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -193,6 +194,7 @@ export class DocumentHistoryService {
|
||||
isCurrent: false,
|
||||
saveSource: row.saveSource as DocumentHistorySaveSource,
|
||||
savedAt: row.savedAt,
|
||||
userId: row.userId,
|
||||
}));
|
||||
|
||||
// If head consumed a slot and we fetched a full page of history rows,
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface DocumentHistoryListItem {
|
||||
isCurrent: boolean;
|
||||
savedAt: Date;
|
||||
saveSource: DocumentHistorySaveSource;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export interface DocumentHistoryItemResult {
|
||||
|
||||
@@ -10,6 +10,7 @@ interface LobeDeliveryCheckerRuntimeContext {
|
||||
operationId?: string;
|
||||
serverDB: LobeChatDatabase;
|
||||
userId: string;
|
||||
workspaceId?: string;
|
||||
}
|
||||
|
||||
const buildError = (content: string, code: string): BuiltinServerRuntimeOutput => ({
|
||||
@@ -28,11 +29,13 @@ class LobeDeliveryCheckerExecutionRuntime {
|
||||
private operationId?: string;
|
||||
private db: LobeChatDatabase;
|
||||
private userId: string;
|
||||
private workspaceId?: string;
|
||||
|
||||
constructor(context: LobeDeliveryCheckerRuntimeContext) {
|
||||
this.operationId = context.operationId;
|
||||
this.db = context.serverDB;
|
||||
this.userId = context.userId;
|
||||
this.workspaceId = context.workspaceId;
|
||||
}
|
||||
|
||||
generateVerifyPlan = async (params: {
|
||||
@@ -64,7 +67,7 @@ class LobeDeliveryCheckerExecutionRuntime {
|
||||
// criteria + a rubric, snapshot it onto this operation, and confirm it. The
|
||||
// tool call is human-reviewed (humanIntervention); this runs post-approval.
|
||||
const { VerifyPlanGeneratorService } = await import('@/server/services/verify');
|
||||
const planGenerator = new VerifyPlanGeneratorService(this.db, this.userId);
|
||||
const planGenerator = new VerifyPlanGeneratorService(this.db, this.userId, this.workspaceId);
|
||||
const { items, rubricId } = await planGenerator.createPlanFromCriteria({
|
||||
criteria,
|
||||
operationId: this.operationId,
|
||||
@@ -110,6 +113,7 @@ export const lobeDeliveryCheckerRuntime: ServerRuntimeRegistration = {
|
||||
operationId: context.operationId,
|
||||
serverDB: context.serverDB,
|
||||
userId: context.userId,
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
},
|
||||
identifier: LobeDeliveryCheckerIdentifier,
|
||||
|
||||
@@ -15,6 +15,7 @@ interface VerifyResultRuntimeContext {
|
||||
operationId?: string;
|
||||
serverDB: LobeChatDatabase;
|
||||
userId: string;
|
||||
workspaceId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,11 +28,13 @@ class VerifyResultExecutionRuntime {
|
||||
private operationId?: string;
|
||||
private db: LobeChatDatabase;
|
||||
private userId: string;
|
||||
private workspaceId?: string;
|
||||
|
||||
constructor(context: VerifyResultRuntimeContext) {
|
||||
this.operationId = context.operationId;
|
||||
this.db = context.serverDB;
|
||||
this.userId = context.userId;
|
||||
this.workspaceId = context.workspaceId;
|
||||
}
|
||||
|
||||
submitVerifyResult = async (params: SubmitVerifyResultParams) => {
|
||||
@@ -47,11 +50,13 @@ class VerifyResultExecutionRuntime {
|
||||
}
|
||||
|
||||
// The verifier runs as a sub-agent; the row to update belongs to the parent run.
|
||||
const op = await new AgentOperationModel(this.db, this.userId).findById(this.operationId);
|
||||
const op = await new AgentOperationModel(this.db, this.userId, this.workspaceId).findById(
|
||||
this.operationId,
|
||||
);
|
||||
const targetOperationId = op?.parentOperationId ?? this.operationId;
|
||||
|
||||
const status = params.verdict === 'passed' ? 'passed' : 'failed';
|
||||
await new VerifyCheckResultModel(this.db, this.userId).updateByCheckItem(
|
||||
await new VerifyCheckResultModel(this.db, this.userId, this.workspaceId).updateByCheckItem(
|
||||
targetOperationId,
|
||||
params.checkItemId,
|
||||
{
|
||||
@@ -66,10 +71,12 @@ class VerifyResultExecutionRuntime {
|
||||
verdict: params.verdict,
|
||||
},
|
||||
);
|
||||
await new VerifyStatusService(this.db, this.userId).recompute(targetOperationId);
|
||||
await new VerifyStatusService(this.db, this.userId, this.workspaceId).recompute(
|
||||
targetOperationId,
|
||||
);
|
||||
// This may be the last check to resolve — kick auto-repair if the run failed
|
||||
// with auto_repair checks (no-op until everything has a terminal result).
|
||||
await maybeAutoRepair(this.db, this.userId, targetOperationId);
|
||||
await maybeAutoRepair(this.db, this.userId, targetOperationId, this.workspaceId);
|
||||
|
||||
log(
|
||||
'submitted verdict %s for check %s (op %s)',
|
||||
@@ -94,6 +101,7 @@ export const verifyResultRuntime: ServerRuntimeRegistration = {
|
||||
operationId: context.operationId,
|
||||
serverDB: context.serverDB,
|
||||
userId: context.userId,
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
},
|
||||
identifier: VerifyToolIdentifier,
|
||||
|
||||
@@ -52,18 +52,20 @@ export const createVerifierAgentRunner = (params: {
|
||||
provider?: string | null;
|
||||
topicId?: string | null;
|
||||
userId: string;
|
||||
workspaceId?: string;
|
||||
}): VerifierAgentRunner | undefined => {
|
||||
const { db, deliverable, model, provider, topicId, userId } = params;
|
||||
const { db, deliverable, model, provider, topicId, userId, workspaceId } = params;
|
||||
if (!topicId) return undefined;
|
||||
|
||||
return async ({ checkItem, goal, operationId }) => {
|
||||
// The detailed instruction is the criterion's rule body, stored in a document.
|
||||
const instruction = checkItem.documentId
|
||||
? ((await new DocumentModel(db, userId).findById(checkItem.documentId))?.content ?? undefined)
|
||||
? ((await new DocumentModel(db, userId, workspaceId).findById(checkItem.documentId))
|
||||
?.content ?? undefined)
|
||||
: undefined;
|
||||
|
||||
// Materialize the builtin verify agent (idempotent) to get an id for the thread.
|
||||
const verifyAgent = await new AgentModel(db, userId).getBuiltinAgent(
|
||||
const verifyAgent = await new AgentModel(db, userId, workspaceId).getBuiltinAgent(
|
||||
BUILTIN_AGENT_SLUGS.verifyAgent,
|
||||
);
|
||||
if (!verifyAgent) {
|
||||
@@ -71,7 +73,7 @@ export const createVerifierAgentRunner = (params: {
|
||||
return null;
|
||||
}
|
||||
|
||||
const thread = await new ThreadModel(db, userId).create({
|
||||
const thread = await new ThreadModel(db, userId, workspaceId).create({
|
||||
agentId: verifyAgent.id,
|
||||
title: `Verify: ${checkItem.title}`,
|
||||
topicId,
|
||||
@@ -85,7 +87,7 @@ export const createVerifierAgentRunner = (params: {
|
||||
// Dynamic import breaks the static cycle: aiAgent → agentRuntime completion
|
||||
// → verify lifecycle → this runner → aiAgent.
|
||||
const { AiAgentService } = await import('@/server/services/aiAgent');
|
||||
const result = await new AiAgentService(db, userId).execAgent({
|
||||
const result = await new AiAgentService(db, userId, { workspaceId }).execAgent({
|
||||
appContext: { threadId: thread.id, topicId },
|
||||
autoStart: true,
|
||||
// Inherit the parent run's model/provider so the verifier uses a provider
|
||||
|
||||
@@ -80,13 +80,13 @@ export class VerifyExecutorService {
|
||||
private readonly statusService: VerifyStatusService;
|
||||
private readonly documentModel: DocumentModel;
|
||||
|
||||
constructor(db: LobeChatDatabase, userId: string) {
|
||||
constructor(db: LobeChatDatabase, userId: string, workspaceId?: string) {
|
||||
this.db = db;
|
||||
this.userId = userId;
|
||||
this.operationModel = new AgentOperationModel(db, userId);
|
||||
this.resultModel = new VerifyCheckResultModel(db, userId);
|
||||
this.statusService = new VerifyStatusService(db, userId);
|
||||
this.documentModel = new DocumentModel(db, userId);
|
||||
this.operationModel = new AgentOperationModel(db, userId, workspaceId);
|
||||
this.resultModel = new VerifyCheckResultModel(db, userId, workspaceId);
|
||||
this.statusService = new VerifyStatusService(db, userId, workspaceId);
|
||||
this.documentModel = new DocumentModel(db, userId, workspaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,8 +23,8 @@ export const computeFalseFlags = (
|
||||
export class VerifyFeedbackService {
|
||||
private readonly resultModel: VerifyCheckResultModel;
|
||||
|
||||
constructor(db: LobeChatDatabase, userId: string) {
|
||||
this.resultModel = new VerifyCheckResultModel(db, userId);
|
||||
constructor(db: LobeChatDatabase, userId: string, workspaceId?: string) {
|
||||
this.resultModel = new VerifyCheckResultModel(db, userId, workspaceId);
|
||||
}
|
||||
|
||||
/** Record a user's decision on a result and precompute its FP/FN flags. */
|
||||
|
||||
@@ -33,9 +33,10 @@ export const runVerifyOnCompletion = async (
|
||||
db: LobeChatDatabase,
|
||||
userId: string,
|
||||
params: RunVerifyOnCompletionParams,
|
||||
workspaceId?: string,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const operationModel = new AgentOperationModel(db, userId);
|
||||
const operationModel = new AgentOperationModel(db, userId, workspaceId);
|
||||
const state = await operationModel.getVerifyState(params.operationId);
|
||||
|
||||
// Opt-in gate: only runs with a confirmed plan that hasn't been verified yet.
|
||||
@@ -48,7 +49,7 @@ export const runVerifyOnCompletion = async (
|
||||
return;
|
||||
}
|
||||
|
||||
const executor = new VerifyExecutorService(db, userId);
|
||||
const executor = new VerifyExecutorService(db, userId, workspaceId);
|
||||
await executor.execute({
|
||||
deliverable: params.deliverable,
|
||||
goal: params.goal,
|
||||
@@ -63,13 +64,14 @@ export const runVerifyOnCompletion = async (
|
||||
provider: op.provider,
|
||||
topicId: op.topicId,
|
||||
userId,
|
||||
workspaceId,
|
||||
}),
|
||||
});
|
||||
|
||||
// Auto-repair once verification has fully resolved. For runs with only inline
|
||||
// (LLM/program) checks, everything is resolved now; runs with async agent
|
||||
// checks no-op here and re-trigger from the verifier's writeback path.
|
||||
await maybeAutoRepair(db, userId, params.operationId);
|
||||
await maybeAutoRepair(db, userId, params.operationId, workspaceId);
|
||||
} catch (error) {
|
||||
log('runVerifyOnCompletion failed for op %s (non-fatal): %O', params.operationId, error);
|
||||
}
|
||||
|
||||
@@ -75,13 +75,13 @@ export class VerifyPlanGeneratorService {
|
||||
private readonly operationModel: AgentOperationModel;
|
||||
private readonly documentModel: DocumentModel;
|
||||
|
||||
constructor(db: LobeChatDatabase, userId: string) {
|
||||
constructor(db: LobeChatDatabase, userId: string, workspaceId?: string) {
|
||||
this.db = db;
|
||||
this.userId = userId;
|
||||
this.criterionModel = new VerifyCriterionModel(db, userId);
|
||||
this.rubricModel = new VerifyRubricModel(db, userId);
|
||||
this.operationModel = new AgentOperationModel(db, userId);
|
||||
this.documentModel = new DocumentModel(db, userId);
|
||||
this.criterionModel = new VerifyCriterionModel(db, userId, workspaceId);
|
||||
this.rubricModel = new VerifyRubricModel(db, userId, workspaceId);
|
||||
this.operationModel = new AgentOperationModel(db, userId, workspaceId);
|
||||
this.documentModel = new DocumentModel(db, userId, workspaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,11 +22,12 @@ const resolveMaxRepairRounds = async (
|
||||
db: LobeChatDatabase,
|
||||
userId: string,
|
||||
plan: VerifyCheckItem[],
|
||||
workspaceId?: string,
|
||||
): Promise<number> => {
|
||||
const rubricId = plan.find((i) => i.sourceRubricId)?.sourceRubricId;
|
||||
if (!rubricId) return DEFAULT_MAX_REPAIR_ROUNDS;
|
||||
|
||||
const rubric = await new VerifyRubricModel(db, userId).findById(rubricId);
|
||||
const rubric = await new VerifyRubricModel(db, userId, workspaceId).findById(rubricId);
|
||||
return rubric?.config?.maxRepairRounds ?? DEFAULT_MAX_REPAIR_ROUNDS;
|
||||
};
|
||||
|
||||
@@ -79,12 +80,13 @@ export const createRepairRunner = (params: {
|
||||
provider?: string | null;
|
||||
topicId?: string | null;
|
||||
userId: string;
|
||||
workspaceId?: string;
|
||||
}): RepairSpawner | undefined => {
|
||||
const { agentId, db, maxRepairRounds, model, provider, topicId, userId } = params;
|
||||
const { agentId, db, maxRepairRounds, model, provider, topicId, userId, workspaceId } = params;
|
||||
if (!agentId || !topicId) return undefined;
|
||||
|
||||
return async ({ instruction, operationId, verifyMessageId }) => {
|
||||
const operationModel = new AgentOperationModel(db, userId);
|
||||
const operationModel = new AgentOperationModel(db, userId, workspaceId);
|
||||
|
||||
const round = await countRepairRounds(operationModel, operationId);
|
||||
if (round >= maxRepairRounds) {
|
||||
@@ -98,7 +100,7 @@ export const createRepairRunner = (params: {
|
||||
// for the operation title / logs. `verifyMessageId` parents the new turn under
|
||||
// the verify card it responds to.
|
||||
const { AiAgentService } = await import('@/server/services/aiAgent');
|
||||
const result = await new AiAgentService(db, userId).execAgent({
|
||||
const result = await new AiAgentService(db, userId, { workspaceId }).execAgent({
|
||||
agentId,
|
||||
appContext: { topicId },
|
||||
autoStart: true,
|
||||
@@ -138,13 +140,16 @@ export const maybeAutoRepair = async (
|
||||
db: LobeChatDatabase,
|
||||
userId: string,
|
||||
operationId: string,
|
||||
workspaceId?: string,
|
||||
): Promise<void> => {
|
||||
const operationModel = new AgentOperationModel(db, userId);
|
||||
const operationModel = new AgentOperationModel(db, userId, workspaceId);
|
||||
const state = await operationModel.getVerifyState(operationId);
|
||||
const plan = (state?.verifyPlan ?? []) as VerifyCheckItem[];
|
||||
if (plan.length === 0) return;
|
||||
|
||||
const results = await new VerifyCheckResultModel(db, userId).listByOperation(operationId);
|
||||
const results = await new VerifyCheckResultModel(db, userId, workspaceId).listByOperation(
|
||||
operationId,
|
||||
);
|
||||
const byItem = new Map(results.map((r) => [r.checkItemId, r]));
|
||||
|
||||
// Wait until every required check has a terminal result (don't repair early).
|
||||
@@ -160,13 +165,14 @@ export const maybeAutoRepair = async (
|
||||
const spawner = createRepairRunner({
|
||||
agentId: op?.agentId,
|
||||
db,
|
||||
maxRepairRounds: await resolveMaxRepairRounds(db, userId, plan),
|
||||
maxRepairRounds: await resolveMaxRepairRounds(db, userId, plan, workspaceId),
|
||||
model: op?.model,
|
||||
provider: op?.provider,
|
||||
topicId: op?.topicId,
|
||||
userId,
|
||||
workspaceId,
|
||||
});
|
||||
await new VerifyRepairService(db, userId).triggerAutoRepair(operationId, spawner);
|
||||
await new VerifyRepairService(db, userId, workspaceId).triggerAutoRepair(operationId, spawner);
|
||||
};
|
||||
|
||||
const isFailed = (r: VerifyCheckResultItem | undefined): boolean =>
|
||||
@@ -191,11 +197,11 @@ export class VerifyRepairService {
|
||||
private readonly resultModel: VerifyCheckResultModel;
|
||||
private readonly statusService: VerifyStatusService;
|
||||
|
||||
constructor(db: LobeChatDatabase, userId: string) {
|
||||
this.messageModel = new MessageModel(db, userId);
|
||||
this.operationModel = new AgentOperationModel(db, userId);
|
||||
this.resultModel = new VerifyCheckResultModel(db, userId);
|
||||
this.statusService = new VerifyStatusService(db, userId);
|
||||
constructor(db: LobeChatDatabase, userId: string, workspaceId?: string) {
|
||||
this.messageModel = new MessageModel(db, userId, workspaceId);
|
||||
this.operationModel = new AgentOperationModel(db, userId, workspaceId);
|
||||
this.resultModel = new VerifyCheckResultModel(db, userId, workspaceId);
|
||||
this.statusService = new VerifyStatusService(db, userId, workspaceId);
|
||||
}
|
||||
|
||||
/** Collect the auto-repairable failures for a run. */
|
||||
|
||||
@@ -17,9 +17,9 @@ export class VerifyStatusService {
|
||||
private readonly operationModel: AgentOperationModel;
|
||||
private readonly resultModel: VerifyCheckResultModel;
|
||||
|
||||
constructor(db: LobeChatDatabase, userId: string) {
|
||||
this.operationModel = new AgentOperationModel(db, userId);
|
||||
this.resultModel = new VerifyCheckResultModel(db, userId);
|
||||
constructor(db: LobeChatDatabase, userId: string, workspaceId?: string) {
|
||||
this.operationModel = new AgentOperationModel(db, userId, workspaceId);
|
||||
this.resultModel = new VerifyCheckResultModel(db, userId, workspaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user