Files
Jyotisha/frontend/src/lib/rectification-agentic/v9/agent-run-messages.ts
T
66f087b588 feat(rectification): submit varga-resolution implementation for review
Review-only snapshot for BUG-1115 through BUG-1117; not merge-ready. New opening and append-turn PostgreSQL permission failures remain blocked. Persisted joint replay has zero completed questions; segment ordering remains off by default. The existing offline replay JSON is retained stale and unchanged after a denied overwrite, including its CRLF line endings. Browser/provider validation and final serial gates remain pending. No deployment, role permission changes, or staging/main push.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-01 08:04:04 +08:00

87 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* The two messages a rectification agent attempt sends the model: the bound
* Skill bootstrap (with the retry constraint on a second attempt) and the user
* turn (the opening brief, or the typed message), both behind the server time
* and Case id lines. Moved out of agent-run.ts unchanged
* (TASK-rectification-code-split-20260926); agent-run.ts re-exports both
* builders.
*/
import type { V9CaseDossier } from "./tool-service";
import { cachedSystemMessage } from "../../agent-generation-settings.ts";
import { OPENING_COLLECT_DOMAINS } from "../user-copy";
import { retryConstraintForAttempt } from "./host-fallback";
import { sliceRectificationSkill } from "./skill-slice";
import type { V9AgentRunOptions } from "./agent-run";
import { productDeliveryForDomain } from "./product-delivery.ts";
function clockWindow(range: { start_time?: string | null; end_time?: string | null } | null | undefined): string | null {
const start = range?.start_time?.trim().slice(0, 5) || "";
const end = range?.end_time?.trim().slice(0, 5) || "";
return start && end ? `${start}–${end}` : null;
}
export function buildOpeningBrief(dossier: V9CaseDossier, birthTimeClue?: string | null): string {
const confirmed = dossier.evidence.filter((item) => item.status === "confirmed");
const pending = dossier.evidence.filter((item) => item.status === "draft" || item.status === "pending_confirmation");
const domains = [...new Set(confirmed.map((item) => item.domain))].slice(0, 6);
const range = dossier.case.candidateRange;
const window = clockWindow(range);
const productDelivery = productDeliveryForDomain(dossier.case.rectificationDomain);
const openingGoal = productDelivery
? `我们先判断现在的出生时间能不能选定用来解读的星盘${window ? `,核对窗口是 ${window}` : ""}。`
: `我们来把你的出生时间缩小到更准的范围${window ? `,现在先在 ${window} 之间找` : ""}。`;
const uncertaintyType = window
? `当前搜索窗口 ${window},来自用户在资料里声明的不确定档`
: "用户的出生时间精度仍需通过经历证据核对";
const clue = typeof birthTimeClue === "string" && birthTimeClue.trim()
? birthTimeClue.trim()
: "";
return [
"【服务端 opening brief】",
`Case 状态:${dossier.case.status}。`,
`当前搜索窗口:${window ?? "尚未锁定"}。来源:intake 声明的不确定档。`,
`出生时间不确定类型:${uncertaintyType}。`,
`已有证据摘要:已确认 ${confirmed.length} 条,待澄清或待确认 ${pending.length} 条${domains.length ? `;已覆盖 ${domains.join("、")}` : ""}。`,
...(clue
? [`家人或本人关于出生时段的线索(仅旁白建议,不得改搜索窗口):${clue}`]
: []),
`做法要点:正文两句大白话,照这个意思写:「${openingGoal}」「做法很简单:你说几件人生里的大事和大概年月,我拿去和星盘对照。」正文不用大运、盘面、分盘、候选、区间、代表分钟、精确到秒这类词,不写具体年份,不要提问,不列举例子,不要求先准备材料。一条消息可以报多件,想到几件说几件。先用 rectification-set-focus 的 spokenPrompt 写出当前采集题,写「先说一两件你记得的大事」即可:开场题干由服务端固定,会列出${OPENING_COLLECT_DOMAINS.join("、")},并给一个带大概年月的回答示例,正文不要重复这些例子。`,
].join("\n");
}
export function buildAgentMessages(
options: V9AgentRunOptions,
attempt: number,
dossier: V9CaseDossier,
skillInstructions: string,
birthTimeClue: string | null = null,
previousErrorCode: string | null = null,
): unknown[] {
const timeContext = options.timeContext
?? `服务端当前时间(权威):${new Date().toISOString()}。涉及“现在、今天、今年、未来几个月”等相对时间时,以此为准。`;
const caseContext = `【服务端 Case ID】${options.caseId}。所有 rectification 工具调用的 caseId 必须原样使用此值。`;
// T6 (TASK-rectification-grounding-20260927): an evidence turn gets only
// the Skill sections it uses; other actions keep the whole bound body.
const boundSkill = sliceRectificationSkill(skillInstructions, options.action).text;
const bootstrapContent = [
"【服务器已绑定当前 Case 的精确 Skill】运行器已在本 attempt 内加载并核验下列指令;不要重复调用 skill。第一步必须调用 rectification-read-case。",
boundSkill,
...(productDeliveryForDomain(dossier.case.rectificationDomain) ? [
`【服务端持久产品身份】${JSON.stringify(productDeliveryForDomain(dossier.case.rectificationDomain))}。本轮按目标盘型交付,不能因摘要缺失退分钟优先;盘型事实只读工具返回的segment_summary,代表签名仅诊断,不能作实际采用盘。`,
] : []),
...(attempt > 1 ? [retryConstraintForAttempt(previousErrorCode)] : []),
].join("\n\n");
const bootstrap = cachedSystemMessage(bootstrapContent, options.generationModel)
?? { role: "system" as const, content: bootstrapContent };
if (options.action === "opening") {
return [bootstrap, {
role: "user",
content: [timeContext, caseContext, buildOpeningBrief(dossier, birthTimeClue)].join("\n"),
}];
}
return [bootstrap, {
role: "user",
content: [timeContext, caseContext, options.message ?? ""].join("\n"),
}];
}