fix(rectification): restore message actions and retry receipts
This commit is contained in:
@@ -313,7 +313,7 @@ export async function runV9AgentTurn(options: V9AgentRunOptions): Promise<V9Agen
|
||||
}
|
||||
await finalize("completed", answerText);
|
||||
await persistPhase("run.completed", null);
|
||||
await emit({ type: "run.completed" });
|
||||
await emit({ type: "run.completed", turnId });
|
||||
return {
|
||||
ok: true, turnId, turnStatus: "completed", skillLoaded,
|
||||
answerText, phases, toolsUsed: [...toolsUsed], errorCode: null,
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import type { MessageListInput } from "@mastra/core/agent/message-list";
|
||||
import { isTerminalStatus, type RectificationCaseStatus } from "./case-status";
|
||||
import {
|
||||
loadV9CaseDossier,
|
||||
RectificationToolServiceError,
|
||||
type RectificationRpcClient,
|
||||
} from "./tool-service";
|
||||
|
||||
export type RectificationRegenerationAgent = Readonly<{
|
||||
getSkill(name: string): Promise<unknown>;
|
||||
generate(
|
||||
messages: MessageListInput,
|
||||
options: { abortSignal?: AbortSignal; maxSteps: number },
|
||||
): Promise<{ text: string }>;
|
||||
}>;
|
||||
|
||||
export type RegenerateV9AssistantTurnOptions = Readonly<{
|
||||
userId: string;
|
||||
caseId: string;
|
||||
sessionId: string;
|
||||
turnId: string;
|
||||
requestId: string;
|
||||
accounting: RectificationRpcClient;
|
||||
agent: RectificationRegenerationAgent;
|
||||
signal?: AbortSignal;
|
||||
}>;
|
||||
|
||||
export type RegenerateV9AssistantTurnResult = Readonly<{
|
||||
ok: true;
|
||||
turnId: string;
|
||||
assistantMessage: string;
|
||||
idempotent: boolean;
|
||||
}>;
|
||||
|
||||
function latestCompletedAssistantTurn(
|
||||
turns: Awaited<ReturnType<typeof loadV9CaseDossier>>["turns"],
|
||||
) {
|
||||
return [...turns]
|
||||
.reverse()
|
||||
.find((turn) => turn.role === "assistant" && turn.status === "completed" && Boolean(turn.text?.trim()));
|
||||
}
|
||||
|
||||
function regenerationPrompt(caseId: string, oldAssistantMessage: string): string {
|
||||
return [
|
||||
`当前校正 Case 引用:${caseId}`,
|
||||
"请先加载绑定 Skill,再调用 rectification-read-case。",
|
||||
"随后只重写下面这条最近的 Agent 正文,使其更自然、准确,并符合当前服务端事实。不要描述后台过程,不要执行或声称执行任何写操作。",
|
||||
"",
|
||||
"待替换的旧正文:",
|
||||
oldAssistantMessage,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function parseRpcResult(value: unknown): RegenerateV9AssistantTurnResult {
|
||||
const row = Array.isArray(value) ? value[0] : value;
|
||||
if (!row || typeof row !== "object") {
|
||||
throw new RectificationToolServiceError("agentic_rectification_regeneration_invalid_result");
|
||||
}
|
||||
const result = row as Record<string, unknown>;
|
||||
if (
|
||||
result.ok !== true
|
||||
|| typeof result.turn_id !== "string"
|
||||
|| typeof result.assistant_message !== "string"
|
||||
|| !result.assistant_message.trim()
|
||||
) {
|
||||
throw new RectificationToolServiceError("agentic_rectification_regeneration_invalid_result");
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
turnId: result.turn_id,
|
||||
assistantMessage: result.assistant_message,
|
||||
idempotent: result.idempotent === true,
|
||||
};
|
||||
}
|
||||
|
||||
export async function regenerateV9AssistantTurn(
|
||||
options: RegenerateV9AssistantTurnOptions,
|
||||
): Promise<RegenerateV9AssistantTurnResult> {
|
||||
const {
|
||||
userId,
|
||||
caseId,
|
||||
sessionId,
|
||||
turnId,
|
||||
requestId,
|
||||
accounting,
|
||||
agent,
|
||||
signal,
|
||||
} = options;
|
||||
const { data: existingData, error: existingError } = await accounting.rpc(
|
||||
"get_agentic_rectification_turn_regeneration",
|
||||
{
|
||||
p_user_id: userId,
|
||||
p_case_id: caseId,
|
||||
p_turn_id: turnId,
|
||||
p_request_id: requestId,
|
||||
},
|
||||
);
|
||||
if (existingError) throw new RectificationToolServiceError(existingError.message);
|
||||
if (existingData) return parseRpcResult(existingData);
|
||||
|
||||
const dossier = await loadV9CaseDossier(accounting, userId, caseId);
|
||||
if (dossier.case.sessionId !== sessionId) {
|
||||
throw new RectificationToolServiceError("agentic_rectification_case_session_mismatch");
|
||||
}
|
||||
if (isTerminalStatus(dossier.case.status as RectificationCaseStatus)) {
|
||||
throw new RectificationToolServiceError("agentic_rectification_case_terminal");
|
||||
}
|
||||
|
||||
const target = dossier.turns.find((turn) => (
|
||||
turn.id === turnId
|
||||
&& turn.role === "assistant"
|
||||
&& turn.status === "completed"
|
||||
&& Boolean(turn.text?.trim())
|
||||
));
|
||||
if (!target?.text) {
|
||||
throw new RectificationToolServiceError("agentic_rectification_turn_not_found");
|
||||
}
|
||||
const latest = latestCompletedAssistantTurn(dossier.turns);
|
||||
if (!latest || latest.id !== turnId) {
|
||||
throw new RectificationToolServiceError("agentic_rectification_turn_not_latest");
|
||||
}
|
||||
|
||||
let skill: unknown = null;
|
||||
try {
|
||||
skill = await agent.getSkill(dossier.case.skillName);
|
||||
} catch {
|
||||
skill = null;
|
||||
}
|
||||
if (!skill) {
|
||||
throw new RectificationToolServiceError("agentic_rectification_skill_not_loaded");
|
||||
}
|
||||
|
||||
const generated = await agent.generate(
|
||||
[{ role: "user", content: regenerationPrompt(caseId, target.text) }],
|
||||
{ abortSignal: signal, maxSteps: 6 },
|
||||
);
|
||||
const assistantMessage = generated.text.trim();
|
||||
if (!assistantMessage) {
|
||||
throw new RectificationToolServiceError("agentic_rectification_regeneration_empty");
|
||||
}
|
||||
|
||||
const { data, error } = await accounting.rpc(
|
||||
"regenerate_agentic_rectification_turn",
|
||||
{
|
||||
p_user_id: userId,
|
||||
p_case_id: caseId,
|
||||
p_session_id: sessionId,
|
||||
p_turn_id: turnId,
|
||||
p_request_id: requestId,
|
||||
p_assistant_message: assistantMessage,
|
||||
},
|
||||
);
|
||||
if (error) throw new RectificationToolServiceError(error.message);
|
||||
return parseRpcResult(data);
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export type PublicPhaseStreamEvent = Readonly<{
|
||||
text?: string;
|
||||
tool?: PublicRectificationTool;
|
||||
methods?: readonly PublicRectificationMethod[];
|
||||
turnId?: string;
|
||||
}>;
|
||||
|
||||
export type PublicStreamEvent = PublicPhaseStreamEvent | RectificationActivityEvent;
|
||||
@@ -162,6 +163,7 @@ export function safePublicEvent(value: unknown): PublicStreamEvent | null {
|
||||
text?: unknown;
|
||||
tool?: unknown;
|
||||
methods?: unknown;
|
||||
turnId?: unknown;
|
||||
};
|
||||
if (event.type === "tool.activity") {
|
||||
if (!isPublicRectificationTool(event.tool) || !isRectificationActivityStatus(event.status)) return null;
|
||||
@@ -186,10 +188,16 @@ export function safePublicEvent(value: unknown): PublicStreamEvent | null {
|
||||
const methods = tool && METHOD_TOOLS.has(tool) && Array.isArray(event.methods)
|
||||
? [...new Set(event.methods.filter(isPublicRectificationMethod))]
|
||||
: [];
|
||||
const turnId = type === "run.completed"
|
||||
&& typeof event.turnId === "string"
|
||||
&& /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(event.turnId)
|
||||
? event.turnId
|
||||
: undefined;
|
||||
return {
|
||||
type,
|
||||
...(text !== undefined ? { text } : {}),
|
||||
...(tool ? { tool } : {}),
|
||||
...(methods.length > 0 ? { methods } : {}),
|
||||
...(turnId ? { turnId } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user