fix(agents): add payment gate on invoke_assessment_expert + progress streaming for assessment
Two hardening fixes for the professional assessment pipeline: 1. Code-level payment verification before dispatching invoke_assessment_expert (prevents bypassing the prompt-only gate) 2. Thread onProgress callback through direct tool chain so run_professional_assessment streams agent_progress events during the 30-45s assessment expert execution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e809740fdb
commit
a72e718510
|
|
@ -65,6 +65,9 @@ import { ToolType } from '../hooks/tool-hooks.types';
|
||||||
// Redis (optional checkpoint persistence)
|
// Redis (optional checkpoint persistence)
|
||||||
import { RedisClientService } from '../../cache/redis-client.service';
|
import { RedisClientService } from '../../cache/redis-client.service';
|
||||||
|
|
||||||
|
// Payment (for code-level gate on assessment)
|
||||||
|
import { PaymentClientService } from '../../payment/payment-client.service';
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Compatibility Types (与 ClaudeAgentServiceV2 的 StreamChunk 兼容)
|
// Compatibility Types (与 ClaudeAgentServiceV2 的 StreamChunk 兼容)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -163,6 +166,8 @@ export class CoordinatorAgentService implements OnModuleInit {
|
||||||
private readonly toolHooks: ToolHooksService,
|
private readonly toolHooks: ToolHooksService,
|
||||||
// Redis (optional — for checkpoint persistence)
|
// Redis (optional — for checkpoint persistence)
|
||||||
@Optional() private readonly redisClient?: RedisClientService,
|
@Optional() private readonly redisClient?: RedisClientService,
|
||||||
|
// Payment (optional — for code-level gate on invoke_assessment_expert)
|
||||||
|
@Optional() private readonly paymentClient?: PaymentClientService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
onModuleInit() {
|
onModuleInit() {
|
||||||
|
|
@ -563,6 +568,23 @@ export class CoordinatorAgentService implements OnModuleInit {
|
||||||
let result: { output: string; isError: boolean };
|
let result: { output: string; isError: boolean };
|
||||||
|
|
||||||
if (toolType === 'agent') {
|
if (toolType === 'agent') {
|
||||||
|
// ── Fix 2: Code-level payment gate on invoke_assessment_expert ──
|
||||||
|
if (toolName === 'invoke_assessment_expert' && this.paymentClient) {
|
||||||
|
const orders = await this.paymentClient.getUserOrders(userId);
|
||||||
|
const hasPaid = orders.some(
|
||||||
|
(o) => o.serviceType === 'ASSESSMENT' && (o.status === 'PAID' || o.status === 'COMPLETED'),
|
||||||
|
);
|
||||||
|
if (!hasPaid) {
|
||||||
|
return {
|
||||||
|
output: JSON.stringify({
|
||||||
|
error: true,
|
||||||
|
message: '评估服务需要付费。请使用 run_professional_assessment 工具进行正式评估(会自动验证支付状态),或先引导用户完成支付。',
|
||||||
|
}),
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 为 Agent 调用创建 onProgress callback,将进度事件推送到 sink
|
// 为 Agent 调用创建 onProgress callback,将进度事件推送到 sink
|
||||||
const onProgress = progressEventSink
|
const onProgress = progressEventSink
|
||||||
? this.createAgentProgressCallback(toolName, progressEventSink)
|
? this.createAgentProgressCallback(toolName, progressEventSink)
|
||||||
|
|
@ -571,7 +593,11 @@ export class CoordinatorAgentService implements OnModuleInit {
|
||||||
} else if (toolType === 'mcp') {
|
} else if (toolType === 'mcp') {
|
||||||
result = await this.mcpClient.executeTool(toolName, toolInput);
|
result = await this.mcpClient.executeTool(toolName, toolInput);
|
||||||
} else {
|
} else {
|
||||||
result = await this.executeDirectTool(toolName, toolInput, userId, conversationId);
|
// ── Fix 1: Progress feedback for run_professional_assessment ──
|
||||||
|
const directOnProgress = (toolName === 'run_professional_assessment' && progressEventSink)
|
||||||
|
? this.createAgentProgressCallback('invoke_assessment_expert', progressEventSink)
|
||||||
|
: undefined;
|
||||||
|
result = await this.executeDirectTool(toolName, toolInput, userId, conversationId, directOnProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostToolUse hook — 成功
|
// PostToolUse hook — 成功
|
||||||
|
|
@ -725,6 +751,7 @@ export class CoordinatorAgentService implements OnModuleInit {
|
||||||
toolInput: Record<string, unknown>,
|
toolInput: Record<string, unknown>,
|
||||||
userId: string,
|
userId: string,
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
|
onProgress?: (text: string) => void,
|
||||||
): Promise<{ output: string; isError: boolean }> {
|
): Promise<{ output: string; isError: boolean }> {
|
||||||
// 构建旧版 ConversationContext(ImmigrationToolsService 需要)
|
// 构建旧版 ConversationContext(ImmigrationToolsService 需要)
|
||||||
const legacyContext: OldConversationContext = {
|
const legacyContext: OldConversationContext = {
|
||||||
|
|
@ -737,6 +764,7 @@ export class CoordinatorAgentService implements OnModuleInit {
|
||||||
toolName,
|
toolName,
|
||||||
toolInput,
|
toolInput,
|
||||||
legacyContext,
|
legacyContext,
|
||||||
|
onProgress,
|
||||||
);
|
);
|
||||||
|
|
||||||
// ImmigrationToolsService 返回结构化对象,序列化为 string 供 Claude 消费
|
// ImmigrationToolsService 返回结构化对象,序列化为 string 供 Claude 消费
|
||||||
|
|
|
||||||
|
|
@ -310,6 +310,7 @@ export class ImmigrationToolsService {
|
||||||
toolName: string,
|
toolName: string,
|
||||||
input: Record<string, unknown>,
|
input: Record<string, unknown>,
|
||||||
context: ConversationContext,
|
context: ConversationContext,
|
||||||
|
onProgress?: (text: string) => void,
|
||||||
): Promise<unknown> {
|
): Promise<unknown> {
|
||||||
console.log(`[Tool] Executing ${toolName} with input:`, JSON.stringify(input));
|
console.log(`[Tool] Executing ${toolName} with input:`, JSON.stringify(input));
|
||||||
|
|
||||||
|
|
@ -369,7 +370,7 @@ export class ImmigrationToolsService {
|
||||||
return this.queryUserArtifacts(input, context);
|
return this.queryUserArtifacts(input, context);
|
||||||
|
|
||||||
case 'run_professional_assessment':
|
case 'run_professional_assessment':
|
||||||
return this.runProfessionalAssessment(input, context);
|
return this.runProfessionalAssessment(input, context, onProgress);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return { error: `Unknown tool: ${toolName}` };
|
return { error: `Unknown tool: ${toolName}` };
|
||||||
|
|
@ -1434,6 +1435,7 @@ export class ImmigrationToolsService {
|
||||||
private async runProfessionalAssessment(
|
private async runProfessionalAssessment(
|
||||||
input: Record<string, unknown>,
|
input: Record<string, unknown>,
|
||||||
context: ConversationContext,
|
context: ConversationContext,
|
||||||
|
onProgress?: (text: string) => void,
|
||||||
): Promise<unknown> {
|
): Promise<unknown> {
|
||||||
const { userInfo, targetCategories, conversationContext } = input as {
|
const { userInfo, targetCategories, conversationContext } = input as {
|
||||||
userInfo: Record<string, unknown>;
|
userInfo: Record<string, unknown>;
|
||||||
|
|
@ -1543,11 +1545,12 @@ export class ImmigrationToolsService {
|
||||||
|
|
||||||
let assessmentResult: string;
|
let assessmentResult: string;
|
||||||
try {
|
try {
|
||||||
|
const opts = onProgress ? { onProgress } : undefined;
|
||||||
assessmentResult = await this.assessmentExpert.executeAssessment({
|
assessmentResult = await this.assessmentExpert.executeAssessment({
|
||||||
userInfo,
|
userInfo,
|
||||||
targetCategories,
|
targetCategories,
|
||||||
conversationContext,
|
conversationContext,
|
||||||
});
|
}, opts);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Tool:run_professional_assessment] Assessment expert failed:', error);
|
console.error('[Tool:run_professional_assessment] Assessment expert failed:', error);
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue