feat(consult): answer the sentence asked — no motive guessing, follow-up turns answer directly, first turn says each thing once (BUG-1070~1073)
- Voice: contrast limited to chart structures; drop 「这句我按『……』理解了」; forbid motive/need sentences; yes/no answered first (BUG-1070) - Follow-up turn (session already holds an answer): ≤200-char direct answer, no skeleton; decided from stored history, no intent regex; tool still runs each turn (BUG-1071) - Thinking bar first section 「先回答你问的这件事」 (BUG-1072) - First turn: opener without actions, body headings 盘里支持 / 时间怎么看 / 这周可以做的一件事 only, actions once, ≤900 chars (BUG-1073, D8) - Checklist is a list to check, not paragraphs to write (T3) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
2ce3c2165c
commit
9a42333a55
@@ -9,13 +9,24 @@ export type ThinkingStepStatus = (typeof THINKING_STEP_STATUSES)[number];
|
||||
|
||||
export const VISIBLE_THINKING_STEP_LIMIT = 4;
|
||||
|
||||
/**
|
||||
* The natal body headings (TASK-consult-answer-the-question-20260927 D8 /
|
||||
* BUG-1073). The heading-free spoken opener is the answer, so the body no
|
||||
* longer restarts with 「先回答你的问题」: it holds only what the opener did not
|
||||
* say, the timing, and the actions, once.
|
||||
*/
|
||||
export const REPORT_HEADING = {
|
||||
question: "先回答你的问题",
|
||||
support: "盘里支持这个判断的地方",
|
||||
timing: "时间怎么看",
|
||||
action: "这周可以做的一件事",
|
||||
} as const;
|
||||
|
||||
/** The declared-window reply still opens its stable-layer section with this heading. */
|
||||
export const WINDOW_OPEN_HEADING = "先回答你的问题";
|
||||
|
||||
/** The thinking-bar section that stands for the spoken opener (D5: it answers the question, it does not guess it). */
|
||||
export const OPENER_THINKING_TITLE = "先回答你问的这件事";
|
||||
|
||||
export const DAILY_HEADING = {
|
||||
trend: "今日趋势",
|
||||
actAvoid: "适合推进 / 需要避开",
|
||||
@@ -74,7 +85,6 @@ function layerSteps(layers: readonly string[] | undefined, prefix: string): Arra
|
||||
|
||||
export function consultationReportHeadings(_domains: readonly ConsultationDomain[] = []): string[] {
|
||||
return [
|
||||
REPORT_HEADING.question,
|
||||
REPORT_HEADING.support,
|
||||
REPORT_HEADING.timing,
|
||||
REPORT_HEADING.action,
|
||||
@@ -91,6 +101,8 @@ export function natalConsultationThinkingPlan(input: {
|
||||
domains: readonly ConsultationDomain[];
|
||||
requiredBlocks?: readonly string[];
|
||||
mustUseLayers?: readonly string[];
|
||||
/** A follow-up turn (the session already holds a natal answer): the opener step is the whole answer. */
|
||||
followUp?: boolean;
|
||||
}): PublicThinkingSection[] {
|
||||
const domains = input.domains.slice(0, 6);
|
||||
const blocks = new Set(input.requiredBlocks ?? Object.keys(BLOCK_STEP_LABELS));
|
||||
@@ -111,12 +123,18 @@ export function natalConsultationThinkingPlan(input: {
|
||||
]);
|
||||
|
||||
return [
|
||||
// The opener has no heading of its own; it is finished when the first body
|
||||
// H2 appears, so this section shares the support heading (BUG-1072/1073).
|
||||
publicThinkingSectionSchema.parse({
|
||||
id: "question",
|
||||
title: "先抓住你真正在问的事",
|
||||
heading: REPORT_HEADING.question,
|
||||
title: OPENER_THINKING_TITLE,
|
||||
heading: REPORT_HEADING.support,
|
||||
steps: [
|
||||
{ id: "question-read", label: "用盘上的反差回答这个问题", status: "pending" },
|
||||
{
|
||||
id: "question-read",
|
||||
label: input.followUp ? "直接回答这句话" : "用盘上的反差回答这个问题",
|
||||
status: "pending",
|
||||
},
|
||||
],
|
||||
}),
|
||||
publicThinkingSectionSchema.parse({
|
||||
@@ -194,7 +212,7 @@ export function windowConsultationThinkingPlan(): PublicThinkingSection[] {
|
||||
return [publicThinkingSectionSchema.parse({
|
||||
id: "window",
|
||||
title: "接下来根据声明窗口整理稳定层",
|
||||
heading: REPORT_HEADING.question,
|
||||
heading: WINDOW_OPEN_HEADING,
|
||||
steps: [
|
||||
{ id: "compare", label: "比较声明窗口内的稳定层", status: "pending" },
|
||||
{ id: "boundary", label: "核对应答边界", status: "pending" },
|
||||
@@ -276,7 +294,7 @@ export function consultationSpokenHeadingRule(kind: "natal" | "general" | "windo
|
||||
const activity = "Do not invent a thinking-process checklist. Activity, progress, and receipts are server-owned.";
|
||||
if (kind === "natal") {
|
||||
return [
|
||||
`After a 3-6 sentence spoken reply with no heading that answers the user's question (反差(表面 A,底下 B,命名成一个格局)→ 谁在推、谁在修 → 别去应 X 的象、去扮演 Y 的象 → 最多三条短行动,各 ≤ 20 characters; total ≤ 400 characters), write the rest with these exact Markdown H2 headings in order: ## ${REPORT_HEADING.question}, then ## ${REPORT_HEADING.support}, then ## ${REPORT_HEADING.timing}, then ## ${REPORT_HEADING.action}. Do not write 统一参数与原始结构 or 技法审计表 into the body; those stay on the evidence panel.`,
|
||||
`On the first natal answer of a session: after a 3-6 sentence spoken reply with no heading that answers the user's question (反差(表面 A,底下 B,都是盘上的结构,命名成一个格局)→ 谁在推、谁在修 → 别去应 X 的象、去扮演 Y 的象; no action list in the opener; total ≤ 400 characters), write the rest with these exact Markdown H2 headings in order: ## ${REPORT_HEADING.support}, then ## ${REPORT_HEADING.timing}, then ## ${REPORT_HEADING.action}. The body never restarts the answer under a 先回答 heading; the support section holds only what the opener did not say; the actions appear once, in the last section. Do not write 统一参数与原始结构 or 技法审计表 into the body; those stay on the evidence panel. A follow-up turn follows the shape instruction in the user turn instead.`,
|
||||
"Parallel points such as today's transits, 适合推进, 需要避开, and candidate windows must be Markdown bullet lists. Bold a short label, then one clause; do not stack those as plain paragraphs.",
|
||||
activity,
|
||||
secrets,
|
||||
@@ -292,7 +310,7 @@ export function consultationSpokenHeadingRule(kind: "natal" | "general" | "windo
|
||||
}
|
||||
if (kind === "window") {
|
||||
return [
|
||||
`When describing stable window structure, start with ## ${REPORT_HEADING.question}.`,
|
||||
`When describing stable window structure, start with ## ${WINDOW_OPEN_HEADING}.`,
|
||||
activity,
|
||||
secrets,
|
||||
].join(" ");
|
||||
@@ -324,7 +342,7 @@ export function consultationContinuePrompt(output: string): string {
|
||||
return [
|
||||
"上一轮用户可见正文因长度在标题处停下。从最后一个完整二级标题之后继续写完,不要重复已写出的段落,不要写思考过程清单。",
|
||||
last ? `最后一个完整标题是:${last}` : "上一轮还没有写出完整的二级标题。",
|
||||
`必须继续使用这些二级标题(尚未写到的才写):## ${REPORT_HEADING.question}、## ${REPORT_HEADING.support}、## ${REPORT_HEADING.timing}、## ${REPORT_HEADING.action}。不要写统一参数或技法审计表。`,
|
||||
`必须继续使用这些二级标题(尚未写到的才写):## ${REPORT_HEADING.support}、## ${REPORT_HEADING.timing}、## ${REPORT_HEADING.action}。不要写「先回答你的问题」这个标题,不要写统一参数或技法审计表。`,
|
||||
"已写出的末尾摘录:",
|
||||
output.slice(-800),
|
||||
].join("\n");
|
||||
@@ -338,15 +356,55 @@ export function consultationContinuePrompt(output: string): string {
|
||||
*/
|
||||
const ANSWER_FROM_EVIDENCE = "拿到本轮计算结果后直接写给用户的回答,只用结果里的盘面事实下判断;调用工具前后都不要写过程说明。";
|
||||
|
||||
/** Natal answer shape (VOICE §7): heading-free opener, then the four headings in one pass. */
|
||||
/** Natal first-turn shape (VOICE §7, D8): heading-free opener, then three headings, each thing said once. */
|
||||
export function natalAnswerShapeInstruction(): string {
|
||||
return [
|
||||
ANSWER_FROM_EVIDENCE,
|
||||
`开场不要标题,先用口语回答问题;然后一次写完这四个二级标题:## ${REPORT_HEADING.question}、## ${REPORT_HEADING.support}、## ${REPORT_HEADING.timing}、## ${REPORT_HEADING.action}。`,
|
||||
"开场不要标题,先用不超过 400 字口语回答问题:反差只写盘上的结构(表面 A、底下 B 都是星、宫、大运或分盘),说清谁在推谁在修,然后「别去应 X 的象,去扮演 Y 的象」;开场不写行动清单。",
|
||||
`然后一次写完这三个二级标题:## ${REPORT_HEADING.support}(只写开场没说过的依据,表格与正文二选一,不把开场的判断再列一遍)、## ${REPORT_HEADING.timing}、## ${REPORT_HEADING.action}(最多三条行动,只在这里出现一次)。全文不超过 900 字。`,
|
||||
"不要写「先回答你的问题」这个标题,不要写「统一参数与原始结构」,不要写技法审计表,不要写思考过程清单,不要复述内部 JSON 字段。",
|
||||
].join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow-up turn shape (TASK-consult-answer-the-question-20260927 D3 /
|
||||
* BUG-1071): the session already holds a natal answer, so this turn answers
|
||||
* the sentence that was asked and does not reopen the skeleton. The server
|
||||
* decides that this is a follow-up from the stored history alone
|
||||
* (`hasPriorAssistantAnswer`); whether the sentence is a yes/no question or a
|
||||
* change of domain is the model's reading, never a regex.
|
||||
*/
|
||||
export function followUpAnswerShapeInstruction(): string {
|
||||
return [
|
||||
ANSWER_FROM_EVIDENCE,
|
||||
"这是追问轮:本会话前面已经写过完整解读。不超过 200 字,第一句就回答用户这句话(是非题先答「是」「不是」或「看情况:……」,再给盘上的依据);只引与这句话直接相关的盘面事实,最多三处;不写二级标题,不写行动清单(用户问了才给);不重述上一轮已经写过的结构;不解释用户为什么问,不替用户说他想要什么。",
|
||||
`只有用户明确换了领域或要求完整看一下,才回到口语开场加三个二级标题(## ${REPORT_HEADING.support}、## ${REPORT_HEADING.timing}、## ${REPORT_HEADING.action})的完整形状。`,
|
||||
"不要写「统一参数与原始结构」,不要写技法审计表,不要写思考过程清单,不要复述内部 JSON 字段。",
|
||||
].join("");
|
||||
}
|
||||
|
||||
/** The stored history already holds an assistant answer: this turn is a follow-up (D6, no intent regex). */
|
||||
export function hasPriorAssistantAnswer(
|
||||
history: ReadonlyArray<Readonly<{ role: "user" | "assistant"; text: string }>>,
|
||||
): boolean {
|
||||
return history.some((message) => message.role === "assistant" && message.text.trim().length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The shape instruction the natal user turn carries: the daily entrypoint keeps
|
||||
* its three sections; a natal follow-up turn answers directly; the first natal
|
||||
* answer of a session writes the opener and the three-heading skeleton.
|
||||
*/
|
||||
export function natalUserTurnShape(input: {
|
||||
entrypoint?: string;
|
||||
history: ReadonlyArray<Readonly<{ role: "user" | "assistant"; text: string }>>;
|
||||
}): string {
|
||||
if (input.entrypoint === "daily_starlanguage") return dailyAnswerShapeInstruction();
|
||||
return hasPriorAssistantAnswer(input.history)
|
||||
? followUpAnswerShapeInstruction()
|
||||
: natalAnswerShapeInstruction();
|
||||
}
|
||||
|
||||
/** Daily entrypoint answer shape: three sections, audit table and boundary line in the last. */
|
||||
export function dailyAnswerShapeInstruction(): string {
|
||||
return [
|
||||
|
||||
Reference in New Issue
Block a user