Files
Jyotisha/frontend/tests/consultation-workflow-contract.test.ts
T
Jesse_ChenandClaude Opus 5.5 cb3ee55837 feat(consult): one read-only evidence lookup per turn; settle open clauses at tool calls (BUG-1059)
read-consultation-evidence returns one closed-enum section (a formal varga,
research/extended vargas, a Western layer, yogas, Ashtakavarga, Shadbala,
transits, Chara Dasha, arudha, karakas, KP, gulika, kakshya, mahadashas,
thematic evidence) from this request's finished calculation, never
recalculates, answers unavailable on a cache miss and refuses a second call.
The receipt records the step and the write row shows 「正在多看一眼:…」.
A lookup after answer text went out keeps the released text whole: a verbatim
restart is dropped as it arrives (40-char confirmation), a continuation is
kept, and settlement still reads the step that wrote the answer; the lookup
runs on the answer clock without resetting it. A length continuation carries
the lookup result with the card. BUG-1059: the visible-text transformer's open
clause is settled at each tool call, so unpunctuated narration no longer
leaks into the answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-09-27 02:54:11 +08:00

410 lines
23 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.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
consultationNatalPrepareStep,
consultationWindowPrepareStep,
} from "../src/mastra/consultation-tools.ts";
const route = readFileSync(new URL("../src/app/api/consult/route.ts", import.meta.url), "utf8");
const reportsRoute = readFileSync(new URL("../src/app/api/reports/route.ts", import.meta.url), "utf8");
const mastra = readFileSync(new URL("../src/mastra/index.ts", import.meta.url), "utf8");
const tools = readFileSync(new URL("../src/mastra/consultation-tools.ts", import.meta.url), "utf8");
const stream = readFileSync(new URL("../src/lib/stream-agent-response.ts", import.meta.url), "utf8");
const workflow = readFileSync(new URL("../src/mastra/consultation-workflow.ts", import.meta.url), "utf8");
const plan = readFileSync(new URL("../src/lib/consultation-plan.ts", import.meta.url), "utf8");
const routeService = readFileSync(new URL("../src/lib/consultation-route-service.ts", import.meta.url), "utf8");
const pythonPlanContract = readFileSync(new URL("../../scripts/consultation_plan_contract.py", import.meta.url), "utf8");
const stagingCompose = readFileSync(new URL("../../deploy/docker-compose.staging.yml", import.meta.url), "utf8");
test("natal and window instructions require a chart tool every turn (BUG-922)", () => {
assert.doesNotMatch(mastra, /follow-ups may use the existing/);
assert.match(mastra, /Call run-jyotish-consultation before answering every turn/);
assert.match(mastra, /Call run-jyotish-window-consultation before answering every turn/);
});
test("window instructions still require the tool for timing questions (BUG-958)", () => {
const window = mastra.slice(
mastra.indexOf("const windowJyotishInstructions"),
mastra.indexOf("export function getWindowJyotishAgent"),
);
assert.match(window, /Timing questions still require calling the tool first/);
assert.match(window, /Do not skip the calculation or refuse the whole question because precise timing is unavailable/);
assert.match(window, /jyotishSkillMethodCoreBlock/);
assert.doesNotMatch(window, /\$\{jyotishSkillMethodBlock\}/);
});
function agentConstructorBodies(source: string): string[] {
const bodies: string[] = [];
const needle = "new Agent(";
let search = 0;
while (true) {
const start = source.indexOf(needle, search);
if (start < 0) break;
const openParen = start + needle.length - 1;
let depth = 0;
let inStr: string | null = null;
let escaped = false;
let i = openParen;
for (; i < source.length; i += 1) {
const ch = source[i];
if (inStr) {
if (escaped) {
escaped = false;
continue;
}
if (ch === "\\") {
escaped = true;
continue;
}
if (ch === inStr) inStr = null;
continue;
}
if (ch === "'" || ch === '"' || ch === "`") {
inStr = ch;
continue;
}
if (ch === "(") depth += 1;
else if (ch === ")") {
depth -= 1;
if (depth === 0) {
i += 1;
break;
}
}
}
bodies.push(source.slice(start, i));
search = i;
}
return bodies;
}
function constTemplateBody(file: string, name: string): string {
return new RegExp(`(?:const|let) ${name} = \`([\\s\\S]*?)\`;`).exec(file)?.[1] ?? "";
}
function instructionSource(agentBody: string, file: string): string {
const named = /instructions:\s*([A-Za-z_][A-Za-z0-9_]*)/.exec(agentBody);
let text = named
? `${agentBody}\n${constTemplateBody(file, named[1])}`
: (/instructions:\s*`([\s\S]*?)`/.exec(agentBody)?.[1] ?? agentBody);
for (const match of text.matchAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g)) {
text += `\n${constTemplateBody(file, match[1])}`;
}
return text;
}
test("every agent that attaches jyotishSkillBinding carries the method marker (BUG-954)", () => {
const boundAgents = agentConstructorBodies(mastra).filter((body) => body.includes("...jyotishSkillBinding()"));
assert.ok(boundAgents.length >= 3, `expected natal, legacy, and window agents, got ${boundAgents.length}`);
for (const body of boundAgents) {
const instructions = instructionSource(body, mastra);
const hasMarker = instructions.includes("jyotishSkillMethodBlock")
|| instructions.includes("jyotishSkillMethodCoreBlock")
|| instructions.includes("<jyotish-skill name=");
assert.equal(hasMarker, true, body.slice(0, 160));
}
});
test("natal and window first steps require the chart tool (BUG-923)", () => {
// 原值: 第 0 步 toolChoice "required" / 新值: "auto"
// 原因: BUG-282 供应商拒收 thinking 模式下的 required,BUG-937 撤回
assert.deepEqual(consultationNatalPrepareStep({ stepNumber: 0 }), {
activeTools: ["run-jyotish-consultation"],
toolChoice: "auto",
});
assert.deepEqual(consultationNatalPrepareStep({ stepNumber: 1 }), {
toolChoice: "auto",
});
assert.deepEqual(consultationWindowPrepareStep({ stepNumber: 0 }), {
activeTools: ["run-jyotish-window-consultation"],
toolChoice: "auto",
});
assert.deepEqual(consultationWindowPrepareStep({ stepNumber: 1 }), {
toolChoice: "auto",
});
});
test("consultation prepareStep never sends required or named toolChoice (BUG-282 / BUG-937)", () => {
assert.doesNotMatch(tools, /toolChoice:\s*"required"/);
assert.doesNotMatch(tools, /type:\s*"tool",\s*toolName/);
});
test("consultation plans are server-owned and bounded", () => {
assert.match(plan, /consultationPlanSchema/);
assert.match(plan, /requestedDomains/);
assert.match(plan, /requiredEvidenceCategories/);
assert.match(plan, /createConsultationPlan/);
assert.match(tools, /const userIntent = ctx\.plan\?\.userIntent \?\? input\.question/);
assert.match(tools, /input\.domains === undefined && context\.plan && context\.theme/);
assert.match(tools, /return \[context\.theme\]/);
assert.match(tools, /chartCalculationProgressLabel\(index \+ 1, domains\.length\)/);
assert.match(tools, /phase: "chart-calculation"/);
assert.match(tools, /userIntent,[\s\S]*theme: domain/);
assert.match(tools, /question: userIntent,[\s\S]*theme: domain/);
assert.match(tools, /plan: domainPlan/);
assert.match(workflow, /packet_version: "consultation-evidence-packet-v2"/);
assert.match(workflow, /claim_cards/);
// 原值:/modelOutput: toModelOutput\(agentContext, domainPlan\)/
// 新值:多传执行领域 domain(parents / children 的必用层按计划领域取,引擎路由是 family)。
// 原因:TASK-consult-evidence-card-20260927 T2。
assert.match(tools, /modelOutput: toModelOutput\(agentContext, domainPlan, domain\)/);
assert.match(tools, /modelOutput: toModelOutput[\s\S]*ctx\.state\.consultationToolCompleted = true/);
assert.match(plan, /precisionBoundary/);
assert.match(plan, /consultationPlanMaxCreditCost = 1/);
const modelToolInput = tools.slice(tools.indexOf("const consultationToolInputSchema"), tools.indexOf("const MAX_RECORDED_STEPS"));
assert.doesNotMatch(modelToolInput, /birth|latitude|longitude|engine|script|credit/i);
const prepareCall = route.slice(route.indexOf("prepared = await prepareConsultationRoute"), route.indexOf("const modelSelection"));
assert.match(prepareCall, /beforeReserve: \(\{ consultationMode \}\) => createConsultationPlan\(\{/);
assert.ok(prepareCall.indexOf("beforeReserve:") < prepareCall.indexOf("reserve: () => reserveConsultationModel("));
assert.ok(routeService.indexOf("const preReserveResult = input.beforeReserve") < routeService.indexOf("const reservation = await input.reserve()"));
assert.match(route, /plan: prepared\.preReserveResult,[\s\S]*theme: consultationTheme/);
assert.match(route, /runConsultationWorkflow\(toolInput, \{[\s\S]*plan: prepared\.preReserveResult/);
assert.match(workflow, /plan_version: workflowRequest\.plan_version/);
assert.match(workflow, /strict_workflow_route: workflowRequest\.strictWorkflowRoute/);
assert.match(workflow, /required_layers: workflowRequest\.requiredLayers/);
assert.match(workflow, /claim_boundary: workflowRequest\.claimBoundary/);
assert.match(workflow, /precision_boundary: workflowRequest\.precision_boundary/);
assert.match(workflow, /required_evidence_categories: workflowRequest\.required_evidence_categories/);
assert.match(pythonPlanContract, /PLAN_VERSION = "consultation-plan-v2"/);
assert.match(pythonPlanContract, /server_allowlist_validated/);
assert.match(pythonPlanContract, /can_answer_precise_timing": False/);
});
test("the model step budget and the wall-clock budget are declared as one pair", () => {
// The pair now lives beside the domain cap it funds: the cap is derived from
// the wall clock, so a change to one that forgets the other is impossible.
assert.match(tools, /export const AGENT_MAX_STEPS = 8;\nexport const AGENT_TIMEOUT_MS = 110_000;/);
// 原值: 锁 AGENT_SLICE_MAX_STEPS = 1 与路由里的 maxSteps: AGENT_SLICE_MAX_STEPS
// 新值: 锁该常数已删除、路由不再出现单步写作流
// 原因: BUG-1053 删除 compose(它是唯一的单步流),写回答发生在 AGENT_MAX_STEPS 的主循环里
assert.doesNotMatch(tools, /AGENT_SLICE_MAX_STEPS/);
assert.match(tools, /MAX_CONSULTATION_DOMAINS = Math\.max\(\s*1,\s*Math\.floor\(CONSULTATION_DOMAIN_WALL_CLOCK_MS \/ CONSULTATION_DOMAIN_DURATION_MS\),\s*\)/);
assert.doesNotMatch(route, /const AGENT_(MAX_STEPS|TIMEOUT_MS) =/);
assert.match(route, /maxSteps: AGENT_MAX_STEPS,/);
assert.doesNotMatch(route, /AGENT_SLICE_MAX_STEPS/);
// 原值: assert.match(route, /AbortSignal\.timeout\(AGENT_TIMEOUT_MS\)/)
// 新值: 工具阶段的 AGENT_TIMEOUT_MS 交给 run clock(createConsultationRunClock 内部 AbortSignal.timeout)
// 原因: BUG-1053 主循环在拿到计算结果后改走答案时钟;工具仍在 110s 的 toolSignal 下
assert.match(route, /toolPhaseMs: AGENT_TIMEOUT_MS,/);
assert.match(tools, /const toolSignal = AbortSignal\.timeout\(toolPhaseMs\);/);
assert.match(route, /createConsultationRuntimeState\(\{ plannedSteps: AGENT_MAX_STEPS \}\)/);
assert.doesNotMatch(route, /maxSteps: \d/);
assert.doesNotMatch(route, /AbortSignal\.timeout\(\d/);
});
test("consult streams reserve an answer budget and keep provider thinking on a separate channel", () => {
const settings = readFileSync(new URL("../src/lib/agent-generation-settings.ts", import.meta.url), "utf8");
assert.match(settings, /export const AGENT_ANSWER_OUTPUT_TOKENS = 16_384;/);
assert.match(settings, /export const AGENT_THINKING_OUTPUT_TOKENS = 8_192;/);
assert.match(settings, /export const AGENT_MAX_OUTPUT_TOKENS = AGENT_ANSWER_OUTPUT_TOKENS;/);
assert.match(settings, /options\.thinking \?\? "disabled"/);
assert.match(settings, /agentOutputTokenBudget\(thinkingMode,/);
assert.match(tools, /function consultationGenerationSettings/);
assert.match(tools, /return agentGenerationSettings\(model, \{ thinking: "enabled" \}\)/);
assert.match(tools, /function consultationContinueGenerationSettings/);
assert.match(tools, /return agentGenerationSettings\(model, \{ thinking: "disabled" \}\)/);
assert.match(tools, /function consultationSliceGenerationSettings/);
assert.match(tools, /reasoningEffort: "low"/);
assert.match(tools, /AGENT_MAX_OUTPUT_TOKENS as CONSULTATION_MAX_OUTPUT_TOKENS/);
assert.match(route, /\.\.\.consultationGenerationSettings\(selectedModel\.model\)/);
assert.match(route, /consultationContinueGenerationSettings\(selectedModel\.model\)/);
assert.match(route, /consultationContinueGenerationSettings\(selectedModel\.model\)/);
// 原值: 锁 composeAnswer 接线、恰好一个 composeAnswer、续写签名 (output: string) 三处、
// 以及 compose 的 toolChoice: "none"
// 新值: 锁路由没有 composeAnswer、没有 toolChoice: "none"(主循环开 thinking,BUG-937 不得用非 auto);
// 续写仍是三处,本命与申报时段两处接收 evidence
// 原因: BUG-1053 删除 compose;续写必须拿到计算结果
assert.doesNotMatch(route, /composeAnswer/);
assert.doesNotMatch(route, /maxOutputTokens:\s*\d/);
assert.equal(route.match(/const continueAfterLength = async \(output: string(, evidence\?: unknown)?\) => \{/g)?.length, 3);
assert.equal(route.match(/const continueAfterLength = async \(output: string, evidence\?: unknown\) => \{/g)?.length, 2);
assert.doesNotMatch(route, /toolChoice: "none"/);
assert.match(route, /entrypoint: consultEntrypoint/);
assert.match(route, /entrypoint: parsed\.data\.entrypoint/);
assert.match(tools, /export function consultationNatalPrepareStep/);
assert.match(tools, /export function consultationWindowPrepareStep/);
assert.match(tools, /activeTools: \[CONSULTATION_NATAL_CALC_TOOL_ID\]/);
assert.match(tools, /activeTools: \[CONSULTATION_WINDOW_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, /prepareStep: consultationWindowPrepareStep/);
assert.match(route, /streamWithOverflowRetry\(agent, natalStreamOptions\)/);
assert.match(route, /streamWithOverflowRetry\(agent, windowStreamOptions\)/);
assert.match(
route,
/请调用 run-jyotish-window-consultation 完成计算[\s\S]*?windowStreamOptions\)/,
);
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\)/);
// 原值: 锁 compose 块用 streamOptions、带 retryHint、不用 natalStreamOptions
// 新值: Pass 4 的 retryHint 改由本命 retryForAnswer 携带;该重试保留工具(natalStreamOptions,命中同请求缓存)
// 原因: BUG-1053 删除 compose;Pass 4 整篇被拒后的重写改走带工具的 answer-retry
const natalAnswerRetry = route.slice(
route.indexOf("const retryForAnswer = async (retryHint?: string) => {", route.indexOf("if (!prepared.serverChart)")),
route.indexOf("const continueAfterLength = async", route.indexOf("if (!prepared.serverChart)")),
);
assert.match(natalAnswerRetry, /retryHint/);
assert.match(natalAnswerRetry, /\.\.\.natalStreamOptions, abortSignal: answerPhaseSignal\(\)/);
assert.doesNotMatch(stream, /section-empty-retry/);
assert.match(tools, /dailyConsultationThinkingPlan/);
assert.match(tools, /pinsConsultationDomains/);
assert.match(stream, /thinking\.section/);
assert.match(stream, /think\.plan/);
// 原值: assert.match(stream, /think\.step/);
// 新值: 咨询流不再发 think.step;计划行由第一条 answer.delta / run.* 收口(timeline completeLiveThink)
// 原因: BUG-1053 删除 interpret/compose 阶段。think.step 只由 publishFindings 在 compose 前发出,
// 而 interpretFindings 只回 id、文本恒空,事件只是把行从 running 翻到 done;事件 schema 与客户端 reducer 保留兼容
assert.doesNotMatch(stream, /publishFindings|interpretFindings/);
assert.match(stream, /continueAfterLength/);
// 原值: assert.match(stream, /composeAnswer/); assert.match(stream, /drainSpoken/);
// 新值: 流里没有 composeAnswer / drainSpoken;回答按步取(stepScopedAnswer),续写带 calculationEvidence
// 原因: BUG-1053 删除 compose 与「丢弃主循环正文」
assert.doesNotMatch(stream, /composeAnswer|drainSpoken/);
assert.match(stream, /stepScopedAnswer/);
// 原值: /continueAfterLength\(pendingAnswer\(\), calculationEvidence\)/
// 新值: 续写带 evidence = 计算结果(数据卡),模型本轮用过补取时再并上 evidence_lookup
// 原因: TASK-consult-evidence-card-20260927 T5,续写不能丢掉补取到的那一段
assert.match(stream, /continueAfterLength\(pendingAnswer\(\), evidence\)/);
assert.match(stream, /\{ \.\.\.calculationEvidence, evidence_lookup: lookupEvidence \}/);
assert.match(stream, /answer-continue/);
});
test("uses one runtime step append entry and no scattered hard-coded step cap", () => {
assert.match(tools, /export function appendConsultationRuntimeStep/);
assert.doesNotMatch(tools, /steps\.length\s*>=\s*32/);
assert.doesNotMatch(stream, /state\.steps\.push/);
assert.doesNotMatch(stream, /steps\.length\s*<\s*32/);
});
test("personal consultation lets the Agent invoke the server-bound workflow tool", () => {
const agenticStart = route.indexOf("async function runAgenticConsultation");
const agenticBranch = route.slice(
agenticStart,
// Former value: `const { history } = parsed.data;` ended the agentic function.
// History now comes from stored session messages, not the client field.
route.indexOf(" const history = storedHistory;", agenticStart),
);
assert.match(agenticBranch, /createConsultationAgentContext/);
assert.match(agenticBranch, /getJyotishAgent\(selectedModel, agentContext\)/);
assert.doesNotMatch(agenticBranch, /await runConsultationWorkflow/);
assert.doesNotMatch(agenticBranch, /JSON\.stringify\(toolInput\)/);
assert.match(tools, /\(ctx\.runWorkflow \?\? runConsultationWorkflow\)\(toolInput, \{/);
// 原值: /return \{ "run-jyotish-consultation": consultationTool \};/
// 新值: 同一个工厂再返回只读补取工具(同请求缓存,每轮一次)
// 原因: TASK-consult-evidence-card-20260927 T5(D8)
assert.match(tools, /return \{\s*"run-jyotish-consultation": consultationTool,\s*\[CONSULTATION_EVIDENCE_LOOKUP_TOOL_ID\]: lookupTool,\s*\};/);
// 原值:transformText 读 workflowReceipt.preciseTiming === "allowed" 再套恒等壳
// 新值:Pass 4 只看 consultationMode,不再读 preciseTiming 开关去挖日期
// 原因:BUG-948,恒等壳下线;日期观察按模式,不按分钟敏感开关。
assert.match(agenticBranch, /pass4Mode: consultationMode/);
assert.doesNotMatch(agenticBranch, /state\.workflowReceipt\?\.preciseTiming === "allowed"/);
assert.match(route, /createConsultationReplyMetadata/);
});
test("defers optional external evidence only for foreground chat", () => {
assert.match(workflow, /defer_optional_external_evidence: options\?\.foreground === true/);
assert.match(reportsRoute, /runWorkflow: \(input\) => runConsultationWorkflow\(input\)/);
assert.doesNotMatch(reportsRoute, /foreground:\s*true/);
});
test("personal Agent owns the Skill and context-bound calculation tool", () => {
const personalFactory = mastra.slice(
mastra.indexOf("export function getJyotishAgent"),
mastra.indexOf("export function getLegacyJyotishAgent"),
);
assert.match(personalFactory, /getJyotishAgent\(model: ResolvedLanguageModel, context: ConsultationAgentContext\)/);
assert.match(personalFactory, /\.\.\.jyotishSkillBinding\(\)/);
assert.match(personalFactory, /tools: createConsultationTools\(context\)/);
assert.doesNotMatch(personalFactory, /server-computed-jyotish-workflow/);
});
test("validates and emits non-sensitive workflow and execution receipts", () => {
assert.match(workflow, /consultationWorkflowResponseSchema/);
assert.match(workflow, /safeParse\(data\)/);
assert.match(workflow, /consultationWorkflowReceipt/);
assert.match(route, /agentExecutionReceipt/);
assert.match(route, /streamAgentResponse/);
});
test("consultationWorkflowResponseSchema still parses without western_evidence_packet", () => {
const schema = workflow.slice(
workflow.indexOf("export const consultationWorkflowResponseSchema"),
workflow.indexOf("function record("),
);
assert.match(schema, /success: z\.boolean\(\)/);
assert.match(schema, /chart: z\.record\(z\.unknown\(\)\)/);
assert.match(schema, /routing: z\.record\(z\.unknown\(\)\)/);
assert.match(schema, /consumer_context:/);
assert.match(schema, /\)\.passthrough\(\);/);
assert.doesNotMatch(schema, /western_evidence_packet/);
});
test("carries commercial technique truth into the model contract", () => {
assert.match(workflow, /technique_truth/);
assert.match(mastra, /deterministic_claims_forbidden_for/);
assert.doesNotMatch(mastra, /AYANAM_SUGGESTIONS|AYANAM_TITLE/);
assert.doesNotMatch(mastra, /2-5 short paragraphs/);
assert.match(mastra, /reference_only/);
assert.match(mastra, /Do not use a restricted technique/);
});
test("projects consultation themes through explicit strict workflow taxonomy", () => {
const projection = readFileSync(new URL("../src/lib/consultation-workflow-request.ts", import.meta.url), "utf8");
const registry = readFileSync(new URL("../src/lib/consultation-domain-registry.ts", import.meta.url), "utf8");
assert.match(projection, /consultationDomainDefinition/);
assert.match(registry, /strictWorkflowRoute/);
assert.match(registry, /claimBoundary/);
assert.match(registry, /requiredLayers/);
assert.match(registry, /negative holdout gate/);
});
test("agentic consultation is the safe default and legacy is explicit rollback", () => {
assert.match(stagingCompose, /CONSULTATION_AGENTIC_RUNTIME: enabled/);
assert.match(route, /CONSULTATION_AGENTIC_RUNTIME\?\.trim\(\)\.toLowerCase\(\) \?\? "enabled"/);
assert.match(route, /mode === "legacy"/);
assert.match(route, /mode !== "canary"/);
});
test("window calculation is precomputed on the agent context cache (BUG-957)", () => {
const windowFactory = tools.slice(tools.indexOf("export function createWindowConsultationTools"));
const natalFactory = tools.slice(
tools.indexOf("export function createConsultationTools"),
tools.indexOf("export async function precomputeWindowConsultation"),
);
assert.match(tools, /export async function precomputeWindowConsultation/);
assert.match(tools, /calculationCache/);
assert.doesNotMatch(windowFactory, /let calculation/);
assert.match(natalFactory, /let calculation/);
assert.match(mastra, /tools: createWindowConsultationTools\(context\)/);
assert.match(mastra, /If this turn already includes a server-owned window packet/);
const windowBranch = route.slice(
route.indexOf("if (shouldRunDeclaredWindowWorkflow"),
route.indexOf("if (!prepared.serverChart)"),
);
const natalBranch = route.slice(route.indexOf("if (!prepared.serverChart)"));
assert.match(windowBranch, /precomputeWindowConsultation/);
assert.match(windowBranch, /warmup:/);
assert.match(windowBranch, /windowPrecomputedPacketMessage/);
assert.match(route, /let windowPacketMessage: string \| null = null/);
assert.match(route, /if \(windowPacketMessage\) \{/);
assert.match(route, /content: windowPacketMessage/);
assert.doesNotMatch(windowBranch, /toolChoice:\s*"required"/);
assert.doesNotMatch(natalBranch, /precomputeWindowConsultation/);
assert.doesNotMatch(natalBranch, /warmup:/);
});