fix(consult): complete natal chart tool on homepage topic cards (BUG-630)
Thinking models were spending the first natal step on skill_read or a Level 2 draft, so home chips such as 家庭 never satisfied the calculation contract. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# 印度占星 Skill 更新日志
|
||||
|
||||
## 2026-09-09 — 初始化后点「家庭」会真正排盘,不再报步骤没完成
|
||||
|
||||
填完生辰和地址后,从首页点「家庭」等主题,会先按该主题排盘再写回答,不再出现「Agent 未完成必要的方法与计算步骤,本次不会扣点」。点卡不会改你看到的那句问题。自己在输入框里问的普通问题仍可一次看多个领域。Skill 版本不变。
|
||||
|
||||
|
||||
## 2026-09-09 — 一小时窗不再丢掉后段簇;中途说出时段固定回复不改窗口
|
||||
|
||||
生时校正在一小时搜索窗里不再按时间只留前 12 个签名簇。61 分钟网格上的全部簇都会进入比较;超过 64 个时合并相邻低分簇,不会把窗口尾巴整段扔掉。可信区间按簇实际覆盖的分钟来读,不再缩成代表那一分钟。
|
||||
|
||||
@@ -9715,3 +9715,19 @@
|
||||
- 相关记录:BUG-623、BUG-572
|
||||
- 复发自:无
|
||||
- 修复版本:待发布
|
||||
|
||||
## BUG-630 | 初始化后点首页「家庭」报运行合同未完成(runtime_contract_incomplete)
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-09
|
||||
- 最近更新:2026-09-09
|
||||
- 影响面:首页主题卡、「家庭」等 onboarding 建议、`POST /api/consult` 本命 agentic 路径、`run-jyotish-consultation` 合同门禁
|
||||
- 用户现象:填完生辰和地址后点「家庭」,对话里出现用户问题(例如「请帮我看看我家庭关系的整体模式和特点」),随后红字「Agent 未完成必要的方法与计算步骤,本次不会扣点。」不扣点是对的,但没有回答。
|
||||
- 触发条件:已有可用出生分钟的账号完成初始化,从首页「从一个主题开始」点家庭(或其它主题卡);模型为 thinking 模式(事故为 `deepseek-v3-flash`)。
|
||||
- 根因:本命合同要求本轮成功调用一次 `run-jyotish-consultation`。Skill 已由服务端写入系统提示,用户回合仍写「先加载 Jyotish Skill」,thinking 模型会把第一步花在 `skill_read` 或直接按 Level 2 骨架空写。家庭路由没有 named strict checklist,模型更容易不调计算工具。主题卡也不带入口身份,领域钉死只覆盖「深入看今日」和生时校正,家庭卡的 `theme: family` 可被改写成别的合法领域。Python 家庭 workflow 本身能算完,不是计算 400/500。
|
||||
- 修复:本命 Agent 第一步 `prepareStep` 只开放 `run-jyotish-consultation`,`toolChoice: "auto"`(thinking 提供商拒绝 named/required)。合同补跑与空回答补跑用同一钩子。分段写作和续写仍用原来的 `streamOptions`,禁止把该钩子接到无分钟 / 声明窗口 Agent。首页主题卡带 `entrypoint: guided_topic`,钉死路由主题且不改写可见问题。钉死入口的用户回合去掉「先加载」,并要求调用时不要填 `domains`。普通手输问题仍可自选领域。
|
||||
- 验证:`frontend/tests/consultation-agentic-runtime.test.ts` 家庭 `guided_topic` 忽略 `domains: ["general"]`、第一步工具 id;`consultation-workflow-contract.test.ts` 锁 natal `prepareStep` 不进共享 `streamOptions`、分段仍 `toolChoice: "none"`;`consultation-entrypoint.test.ts` / `starter-questions.test.ts` 锁入口枚举与主题卡接线。
|
||||
- 防复发:本命 `requireTool: true` 的第一步必须能调到计算工具。`prepareStep` 不得套到 general/window Agent,也不得套到 `composeSection`。主题卡必须带 `guided_topic`。不得把 `domains` 改成自由字符串来绕过 BUG-255 的枚举拒绝。
|
||||
- 相关记录:BUG-205、BUG-214、BUG-255、BUG-257、BUG-612
|
||||
- 复发自:BUG-214(同一句用户文案和合同门;本条是模型根本没成功调计算工具,不是失败计数把成功次数算超)
|
||||
- 修复版本:待发布
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
| `TASK-agent-voice-20260901.md` | — | Agent 输出语气 | 已合入 | `ed9497e7`(PR #48) |
|
||||
| `TASK-chat-streaming-ux-20260901.md` | `PROGRESS-chat-streaming-ux-20260901.md` | 流式体验与双会话面统一 | 已验收 | `4dc0c8c7`(BUG-473~478) |
|
||||
| `TASK-unified-loading-20260902.md` | `PROGRESS-unified-loading-20260902.md` | 首页一次等待一次揭幕 | 已验收 | `482796fc`(BUG-479) |
|
||||
| — | — | 初始化后点首页「家庭」报运行合同未完成:本命第一步未强制计算工具、主题卡无入口钉死 | 待验收 | `codex/consultation-family-contract-incomplete-20260909`(BUG-630);走查 `docs/testing/consultation-family-contract-incomplete-20260909.md` |
|
||||
|
||||
### 个人报告
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Staging 人肉复核 · 初始化后点「家庭」必须写出回答(2026-09-09)
|
||||
|
||||
给产品负责人。不要把真实出生资料或对话正文写进任何记录。
|
||||
|
||||
对应 BUG-630。测之前先做第 0 条。
|
||||
|
||||
## 0. 确认测的是新版本
|
||||
|
||||
浏览器打开 `https://staging.jyotisha.chat/api/health`,看 `deployment.gitCommit` 前 8 位是否等于本单合入 staging 后的提交。不一致 = 先别测。
|
||||
|
||||
账号需要已填出生日期、时间和地址,且标准咨询计费已配置(否则会先撞到计费 503,不是本单)。
|
||||
|
||||
## 1. 点首页「家庭」必须得到回答(P0)
|
||||
|
||||
1. 登录后完成初始化(生辰 + 地址),回到首页。
|
||||
2. 在「从一个主题开始」点 **家庭**。
|
||||
3. 等活动区走完计算。
|
||||
|
||||
- ✅ 预期:助手写出家庭关系的回答,不是红字「Agent 未完成必要的方法与计算步骤,本次不会扣点。」用户气泡仍是个性化那句问题,不会被改成别的提示词。
|
||||
- ❌ 失败:只有那句红字;或活动区走完却没有正文。
|
||||
|
||||
## 2. 其它主题卡同样不能报步骤没完成(P1)
|
||||
|
||||
同一账号再开一次新对话,点 **事业** 或 **关系**。
|
||||
|
||||
- ✅ 预期:同样先计算再回答,不出现第 1 条那句红字。
|
||||
- ❌ 失败:只有家庭修好,其它主题卡仍报步骤没完成。
|
||||
|
||||
## 3. 输入框里的普通问题仍可一次看多个领域(P1)
|
||||
|
||||
在对话里手打一句「事业和财富一起看」,不要点首页主题卡。
|
||||
|
||||
- ✅ 预期:可以按多个领域作答,不会被钉成单一「家庭」或「时运三节」。
|
||||
- ❌ 失败:手打问题也被写成首页主题卡的固定骨架,或再报步骤没完成。
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "@/lib/agent-observability";
|
||||
import {
|
||||
consultationEntrypointSchema,
|
||||
pinsConsultationDomains,
|
||||
resolveConsultationQuestion,
|
||||
shouldLoadGeneralDailyPanchanga,
|
||||
} from "@/lib/consultation-entrypoint";
|
||||
@@ -46,6 +47,7 @@ import {
|
||||
AGENT_SLICE_MAX_STEPS,
|
||||
consultationContinueGenerationSettings,
|
||||
consultationGenerationSettings,
|
||||
consultationNatalPrepareStep,
|
||||
consultationSliceGenerationSettings,
|
||||
createConsultationAgentContext,
|
||||
createWindowConsultationAgentContext,
|
||||
@@ -127,7 +129,7 @@ const generalChatRequestSchema = z.object({
|
||||
consultationMode: z.literal("general_no_birth_time"),
|
||||
question: z.string().trim().min(1).max(500),
|
||||
theme: consultationDomainSchema,
|
||||
entrypoint: z.literal("daily_starlanguage").optional(),
|
||||
entrypoint: consultationEntrypointSchema.exclude(["birth_time_rectification"]).optional(),
|
||||
}).strict();
|
||||
|
||||
const windowChatRequestSchema = z.object({
|
||||
@@ -135,7 +137,7 @@ const windowChatRequestSchema = z.object({
|
||||
consultationMode: z.literal("declared_birth_window"),
|
||||
question: z.string().trim().min(1).max(500),
|
||||
theme: consultationDomainSchema,
|
||||
entrypoint: z.literal("daily_starlanguage").optional(),
|
||||
entrypoint: consultationEntrypointSchema.exclude(["birth_time_rectification"]).optional(),
|
||||
}).strict();
|
||||
|
||||
const chatRequestSchema = z.union([
|
||||
@@ -838,9 +840,12 @@ export async function POST(request: Request) {
|
||||
}
|
||||
};
|
||||
const cacheBoundary = cachedSystemMessage("【上下文缓存边界】后续内容为本轮请求输入。", selectedModel.model);
|
||||
const natalToolInstruction = pinsConsultationDomains(consultEntrypoint)
|
||||
? "如需新的个人星盘结论,必须调用服务器绑定的排盘工具。调用时不要填写 domains,沿用服务器已选定的主题。"
|
||||
: "如需新的个人星盘结论,必须调用服务器绑定的排盘工具。";
|
||||
const natalInstruction = consultEntrypoint === "daily_starlanguage"
|
||||
? "先加载 Jyotish Skill;如需新的个人星盘结论,必须调用服务器绑定的排盘工具。按三节写:今日趋势、适合推进 / 需要避开、一个行动(把技法审计表和「探索性日提示,不是确定预测」放进最后一节)。不要复述内部 JSON 字段。"
|
||||
: "先加载 Jyotish Skill;如需新的个人星盘结论,必须调用服务器绑定的排盘工具。事业/财富/婚恋/家庭按 skill Level 2 模板写:原始结构、六步宫位、Yoga 表、时机、综合、文末技法审计表,然后才是现代生活措辞。不要复述内部 JSON 字段。";
|
||||
? `${natalToolInstruction}按三节写:今日趋势、适合推进 / 需要避开、一个行动(把技法审计表和「探索性日提示,不是确定预测」放进最后一节)。不要复述内部 JSON 字段。`
|
||||
: `${natalToolInstruction}事业/财富/婚恋/家庭按 skill Level 2 模板写:原始结构、六步宫位、Yoga 表、时机、综合、文末技法审计表,然后才是现代生活措辞。不要复述内部 JSON 字段。`;
|
||||
const adoptedRangeNote = consultationMode === "verified_chart"
|
||||
&& prepared.serverChart?.truth.birthTimeStatus === "accepted"
|
||||
&& prepared.serverChart.toolInput.candidate_range
|
||||
@@ -884,14 +889,21 @@ export async function POST(request: Request) {
|
||||
hooks,
|
||||
...consultationGenerationSettings(selectedModel.model),
|
||||
};
|
||||
async function streamWithOverflowRetry(agent: {
|
||||
stream: (
|
||||
messages: typeof baseMessages,
|
||||
options: typeof streamOptions,
|
||||
) => Promise<{ fullStream: AsyncIterable<unknown> | ReadableStream<unknown>; totalUsage: Promise<Usage> }>;
|
||||
}) {
|
||||
const natalStreamOptions = {
|
||||
...streamOptions,
|
||||
prepareStep: consultationNatalPrepareStep,
|
||||
};
|
||||
async function streamWithOverflowRetry(
|
||||
agent: {
|
||||
stream: (
|
||||
messages: typeof baseMessages,
|
||||
options: typeof streamOptions | typeof natalStreamOptions,
|
||||
) => Promise<{ fullStream: AsyncIterable<unknown> | ReadableStream<unknown>; totalUsage: Promise<Usage> }>;
|
||||
},
|
||||
options: typeof streamOptions | typeof natalStreamOptions = streamOptions,
|
||||
) {
|
||||
try {
|
||||
const result = await agent.stream(baseMessages, streamOptions);
|
||||
const result = await agent.stream(baseMessages, options);
|
||||
usages.push(result.totalUsage);
|
||||
return result;
|
||||
} catch (error) {
|
||||
@@ -900,7 +912,7 @@ export async function POST(request: Request) {
|
||||
// clients cannot parse rectification `attempt.reset`, so the retry stays
|
||||
// server-side and never opens a second user-visible wait.
|
||||
baseMessages = consultationBaseMessages(true);
|
||||
const overflow = await agent.stream(baseMessages, streamOptions);
|
||||
const overflow = await agent.stream(baseMessages, options);
|
||||
usages.push(overflow.totalUsage);
|
||||
return overflow;
|
||||
}
|
||||
@@ -1114,7 +1126,7 @@ export async function POST(request: Request) {
|
||||
state,
|
||||
});
|
||||
const agent = getJyotishAgent(selectedModel, agentContext);
|
||||
const result = await streamWithOverflowRetry(agent);
|
||||
const result = await streamWithOverflowRetry(agent, natalStreamOptions);
|
||||
const retry = async () => {
|
||||
const retried = await agent.stream([
|
||||
...baseMessages,
|
||||
@@ -1122,7 +1134,7 @@ export async function POST(request: Request) {
|
||||
role: "user" as const,
|
||||
content: "运行合同不完整:本次尚未取得服务器计算结果。请调用 run-jyotish-consultation 完成计算,再据此回答;不要在工具参数中添加出生资料。",
|
||||
},
|
||||
], streamOptions);
|
||||
], natalStreamOptions);
|
||||
usages.push(retried.totalUsage);
|
||||
return retried.fullStream;
|
||||
};
|
||||
@@ -1136,7 +1148,7 @@ export async function POST(request: Request) {
|
||||
role: "user" as const,
|
||||
content: "服务器计算已经完成,但上一轮没有输出任何回答文本。请重新取回本次计算结果,然后直接给出回答;不要只描述过程或工具调用。",
|
||||
},
|
||||
], streamOptions);
|
||||
], natalStreamOptions);
|
||||
usages.push(retried.totalUsage);
|
||||
return retried.fullStream;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
import type { ConsultationEntrypoint } from "@/lib/consultation-entrypoint";
|
||||
import type { DailyStarlanguageState, Theme } from "@/lib/home-types";
|
||||
import type { RectificationEntrySummary } from "@/lib/rectification-entry";
|
||||
|
||||
@@ -35,7 +36,11 @@ export type StarterHomeProps = {
|
||||
readonly rectificationErrorMessage: string;
|
||||
readonly starterThemes: readonly StarterHomeTheme[];
|
||||
readonly starterSuggestions: readonly StarterHomeSuggestion[];
|
||||
readonly startSuggestedConsultation: (text: string, theme: Theme) => void;
|
||||
readonly startSuggestedConsultation: (
|
||||
text: string,
|
||||
theme: Theme,
|
||||
entrypoint?: ConsultationEntrypoint | null,
|
||||
) => void;
|
||||
readonly onboardingError: string;
|
||||
};
|
||||
|
||||
@@ -141,7 +146,7 @@ export function StarterHome({
|
||||
type="button"
|
||||
aria-label={`${theme?.label || "开始"}:${item.text}`}
|
||||
disabled={productEntrypointsDisabled}
|
||||
onClick={() => void startSuggestedConsultation(item.text, item.theme)}
|
||||
onClick={() => void startSuggestedConsultation(item.text, item.theme, "guided_topic")}
|
||||
>
|
||||
<span className="starter-content">
|
||||
<b>{theme?.label || "开始"}</b>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { z } from "zod";
|
||||
export const consultationEntrypointSchema = z.enum([
|
||||
"daily_starlanguage",
|
||||
"birth_time_rectification",
|
||||
"guided_topic",
|
||||
]);
|
||||
|
||||
export type ConsultationEntrypoint = z.infer<typeof consultationEntrypointSchema>;
|
||||
@@ -61,7 +62,9 @@ function publicDailyExpansion(currentDate: string): ResolvedConsultationQuestion
|
||||
}
|
||||
|
||||
export function pinsConsultationDomains(entrypoint: ConsultationEntrypoint | undefined): boolean {
|
||||
return entrypoint === "daily_starlanguage" || entrypoint === "birth_time_rectification";
|
||||
return entrypoint === "daily_starlanguage"
|
||||
|| entrypoint === "birth_time_rectification"
|
||||
|| entrypoint === "guided_topic";
|
||||
}
|
||||
|
||||
export function resolveConsultationQuestion(
|
||||
@@ -91,6 +94,8 @@ export function resolveConsultationQuestion(
|
||||
"候选时间必须标为待验证,不能声称是出生记录中的确定分钟,也不能在没有新证据时循环重启相同流程。",
|
||||
].join("\n"),
|
||||
};
|
||||
case "guided_topic":
|
||||
return { kind: "plain", modelQuestion: input.visibleQuestion };
|
||||
default: {
|
||||
const exhaustive: never = input.entrypoint;
|
||||
return exhaustive;
|
||||
|
||||
@@ -56,6 +56,27 @@ export { AGENT_MAX_OUTPUT_TOKENS as CONSULTATION_MAX_OUTPUT_TOKENS } from "../li
|
||||
export const AGENT_MAX_STEPS = 8;
|
||||
export const AGENT_TIMEOUT_MS = 110_000;
|
||||
export const AGENT_SLICE_MAX_STEPS = 1;
|
||||
export const CONSULTATION_NATAL_CALC_TOOL_ID = "run-jyotish-consultation";
|
||||
|
||||
/**
|
||||
* Thinking-mode providers reject named/required tool_choice. Restrict the first
|
||||
* natal model step to the chart tool and keep tool_choice auto so the run cannot
|
||||
* spend that step on skill_read or a spoken Level 2 draft before any evidence
|
||||
* exists. Later steps leave the rest of the bound tools available.
|
||||
*
|
||||
* Window and general agents must not share this hook: they do not own this tool.
|
||||
*/
|
||||
export function consultationNatalPrepareStep(input: { stepNumber: number }) {
|
||||
return input.stepNumber === 0
|
||||
? {
|
||||
activeTools: [CONSULTATION_NATAL_CALC_TOOL_ID],
|
||||
toolChoice: "auto" as const,
|
||||
}
|
||||
: {
|
||||
toolChoice: "auto" as const,
|
||||
};
|
||||
}
|
||||
|
||||
const CONSULTATION_DOMAIN_DURATION_MS = 21_000;
|
||||
const CONSULTATION_ANSWER_RESERVE_MS = 45_000;
|
||||
export const CONSULTATION_DOMAIN_WALL_CLOCK_MS = AGENT_TIMEOUT_MS - CONSULTATION_ANSWER_RESERVE_MS;
|
||||
|
||||
@@ -4,12 +4,14 @@ import {
|
||||
AGENT_MAX_STEPS,
|
||||
AGENT_TIMEOUT_MS,
|
||||
CONSULTATION_MAX_OUTPUT_TOKENS,
|
||||
CONSULTATION_NATAL_CALC_TOOL_ID,
|
||||
mergeConsultationAnswerPolicies,
|
||||
CONSULTATION_DOMAIN_WALL_CLOCK_MS,
|
||||
MAX_CONSULTATION_DOMAINS,
|
||||
appendConsultationRuntimeStep,
|
||||
canonicalDomainPlan,
|
||||
consultationGenerationSettings,
|
||||
consultationNatalPrepareStep,
|
||||
consultationSliceGenerationSettings,
|
||||
consultationModelStepTelemetry,
|
||||
consultationStepBudgetReceipt,
|
||||
@@ -570,6 +572,12 @@ test("the single-value domain form stays available to callers without the model
|
||||
}),
|
||||
["timing"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
canonicalDomainPlan({ domains: ["general"] }, {
|
||||
plan, theme: "family", entrypoint: "guided_topic",
|
||||
}),
|
||||
["family"],
|
||||
);
|
||||
assert.throws(
|
||||
() => canonicalDomainPlan({ domains: ["career"], theme: "career" }, {}),
|
||||
/invalid_consultation_domain_plan/,
|
||||
@@ -618,6 +626,43 @@ test("daily entrypoint ignores a model domain rewrite instead of executing it",
|
||||
]);
|
||||
});
|
||||
|
||||
test("guided-topic entrypoint ignores a model domain rewrite instead of executing it", async () => {
|
||||
const calls: string[] = [];
|
||||
const state = createConsultationRuntimeState();
|
||||
const plan = createConsultationPlan({
|
||||
userIntent: "请帮我看看我家庭关系的整体模式和特点",
|
||||
theme: "family",
|
||||
consultationMode: "verified_chart",
|
||||
modelCreditCost: 1,
|
||||
});
|
||||
const result = await createConsultationTools({
|
||||
userId: "u", sessionId: "s", requestId: "r-family-pin", consultationMode: "verified_chart",
|
||||
plan, theme: "family", entrypoint: "guided_topic", serverChart, state,
|
||||
runWorkflow: async (input) => {
|
||||
calls.push(input.theme);
|
||||
return workflow(input.theme);
|
||||
},
|
||||
})["run-jyotish-consultation"].execute!(
|
||||
modelInput({ question: "请帮我看看我家庭关系的整体模式和特点", domains: ["general"] }),
|
||||
toolContext,
|
||||
) as PlanResult;
|
||||
assert.deepEqual(calls, ["family"]);
|
||||
assert.deepEqual(result.domains, ["family"]);
|
||||
assert.equal(state.planOverrideIgnored, true);
|
||||
assert.equal(state.workflowReceipt?.planOverrideIgnored, true);
|
||||
});
|
||||
|
||||
test("natal first step exposes only the chart calculation tool", () => {
|
||||
assert.equal(CONSULTATION_NATAL_CALC_TOOL_ID, "run-jyotish-consultation");
|
||||
assert.deepEqual(consultationNatalPrepareStep({ stepNumber: 0 }), {
|
||||
activeTools: ["run-jyotish-consultation"],
|
||||
toolChoice: "auto",
|
||||
});
|
||||
assert.deepEqual(consultationNatalPrepareStep({ stepNumber: 1 }), {
|
||||
toolChoice: "auto",
|
||||
});
|
||||
});
|
||||
|
||||
test("matching the pinned theme does not record a plan override", async () => {
|
||||
const state = createConsultationRuntimeState();
|
||||
await createConsultationTools({
|
||||
|
||||
@@ -30,6 +30,7 @@ test("plain consultation questions remain user-authored", () => {
|
||||
test("homepage and rectification entrypoints pin the server-selected domain", () => {
|
||||
assert.equal(pinsConsultationDomains("daily_starlanguage"), true);
|
||||
assert.equal(pinsConsultationDomains("birth_time_rectification"), true);
|
||||
assert.equal(pinsConsultationDomains("guided_topic"), true);
|
||||
assert.equal(pinsConsultationDomains(undefined), false);
|
||||
});
|
||||
|
||||
@@ -116,9 +117,22 @@ test("birth-time entrypoint selects a private server expansion", () => {
|
||||
assert.notEqual(resolved.modelQuestion, visibleQuestion);
|
||||
});
|
||||
|
||||
test("guided-topic entrypoint keeps the visible question and pins the theme", () => {
|
||||
const visibleQuestion = "请帮我看看我家庭关系的整体模式和特点";
|
||||
const resolved = resolveConsultationQuestion({
|
||||
visibleQuestion,
|
||||
entrypoint: "guided_topic",
|
||||
currentDate: "2026-09-09",
|
||||
});
|
||||
|
||||
assert.deepEqual(resolved, { kind: "plain", modelQuestion: visibleQuestion });
|
||||
assert.equal(pinsConsultationDomains("guided_topic"), true);
|
||||
});
|
||||
|
||||
test("consultation entrypoints form a closed public request enum", () => {
|
||||
assert.equal(consultationEntrypointSchema.safeParse("daily_starlanguage").success, true);
|
||||
assert.equal(consultationEntrypointSchema.safeParse("birth_time_rectification").success, true);
|
||||
assert.equal(consultationEntrypointSchema.safeParse("guided_topic").success, true);
|
||||
assert.equal(consultationEntrypointSchema.safeParse("client_prompt").success, false);
|
||||
});
|
||||
|
||||
@@ -315,6 +329,8 @@ test("consult route expands an optional entrypoint for both Agent and tool input
|
||||
const source = readFileSync(new URL("../src/app/api/consult/route.ts", import.meta.url), "utf8");
|
||||
|
||||
assert.match(source, /entrypoint:\s*consultationEntrypointSchema\.optional\(\)/);
|
||||
assert.match(source, /entrypoint: consultationEntrypointSchema\.exclude\(\["birth_time_rectification"\]\)\.optional\(\)/);
|
||||
assert.doesNotMatch(source, /entrypoint: z\.literal\("daily_starlanguage"\)\.optional\(\)/);
|
||||
assert.match(source, /question:\s*resolvedQuestion\.modelQuestion/);
|
||||
assert.match(source, /先用 3–6 句口语直接回答下面的问题[\s\S]*?resolvedQuestion\.modelQuestion/);
|
||||
assert.doesNotMatch(source, /JSON\.stringify\(toolInput\)/);
|
||||
|
||||
@@ -92,6 +92,30 @@ test("consult streams reserve an answer budget and keep provider thinking on a s
|
||||
assert.match(route, /toolChoice: "none"/);
|
||||
assert.match(route, /entrypoint: consultEntrypoint/);
|
||||
assert.match(route, /entrypoint: parsed\.data\.entrypoint/);
|
||||
assert.match(tools, /export function consultationNatalPrepareStep/);
|
||||
assert.match(tools, /activeTools: \[CONSULTATION_NATAL_CALC_TOOL_ID\]/);
|
||||
const sharedStreamOptions = route.slice(
|
||||
route.indexOf("const streamOptions = {"),
|
||||
route.indexOf("const natalStreamOptions = {"),
|
||||
);
|
||||
assert.doesNotMatch(sharedStreamOptions, /prepareStep/);
|
||||
assert.match(route, /prepareStep: consultationNatalPrepareStep/);
|
||||
assert.match(route, /streamWithOverflowRetry\(agent, natalStreamOptions\)/);
|
||||
assert.doesNotMatch(route, /先加载 Jyotish Skill/);
|
||||
assert.match(route, /pinsConsultationDomains\(consultEntrypoint\)/);
|
||||
assert.match(route, /调用时不要填写 domains,沿用服务器已选定的主题/);
|
||||
const natalRetry = route.slice(
|
||||
route.indexOf("if (!prepared.serverChart)"),
|
||||
route.indexOf("const continueAfterLength = async (output: string) => {", route.indexOf("if (!prepared.serverChart)")),
|
||||
);
|
||||
assert.match(natalRetry, /], natalStreamOptions\)/);
|
||||
assert.doesNotMatch(natalRetry, /], streamOptions\)/);
|
||||
const composeBlock = route.slice(
|
||||
route.indexOf("const composeSection = async ("),
|
||||
route.indexOf("const executionReceipt = (): AgentExecutionReceipt => ({", route.indexOf("const composeSection = async (")),
|
||||
);
|
||||
assert.match(composeBlock, /\.\.\.streamOptions,/);
|
||||
assert.doesNotMatch(composeBlock, /natalStreamOptions/);
|
||||
assert.match(stream, /section-empty-retry/);
|
||||
assert.match(tools, /dailyConsultationThinkingPlan/);
|
||||
assert.match(tools, /pinsConsultationDomains/);
|
||||
|
||||
@@ -49,7 +49,7 @@ test("default starter questions derive every canonical domain with evidence and
|
||||
assert.match(pageSource, /defaultGuidedJyotishTopics/);
|
||||
assert.match(pageSource, /starterSuggestions\.map/);
|
||||
assert.match(pageSource, /starterThemes\.find\(\(candidate\) => candidate\.id === item\.theme\)/);
|
||||
assert.match(pageSource, /void startSuggestedConsultation\(item\.text, item\.theme\)/);
|
||||
assert.match(pageSource, /void startSuggestedConsultation\(item\.text, item\.theme, "guided_topic"\)/);
|
||||
assert.equal(consultationDomainIds.length, 10);
|
||||
assert.deepEqual(consultationDomainRegistry.map((domain) => domain.id), [...consultationDomainIds]);
|
||||
assert.match(guidedTopicsSource, /consultationDomainRegistry\.map/);
|
||||
@@ -121,7 +121,7 @@ test("homepage daily and topic cards start a consultation instead of filling the
|
||||
);
|
||||
|
||||
assert.match(pageSource, /onClick=\{startDailyStarlanguageConsultation\}/);
|
||||
assert.match(pageSource, /onClick=\{\(\) => void startSuggestedConsultation\(item\.text, item\.theme\)\}/);
|
||||
assert.match(pageSource, /onClick=\{\(\) => void startSuggestedConsultation\(item\.text, item\.theme, "guided_topic"\)\}/);
|
||||
assert.doesNotMatch(pageSource, /draftDailyStarlanguageQuestion/);
|
||||
assert.doesNotMatch(pageSource, /onClick=\{\(\) => chooseSuggestedQuestion\(item\.text, item\.theme\)\}/);
|
||||
assert.match(starterSend, /await startNewChat\(\)/);
|
||||
|
||||
Reference in New Issue
Block a user