Window consultation sends its two chart requests together (issue #128, BUG-1473)
Independent Staging Quality Gate / publish (push) Canceled after 0s
Independent Staging Quality Gate / validate (push) Canceled after 9m48s

executeWindowConsultation awaited /api/declared_window_chart before sending
/api/rectification/v5/range_reading, which does not depend on it. Both now go
out at once; a failed range reading still only drops the minute-sensitive
themes. Two 150 ms stubs: ~150 ms total instead of ~300 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jesse_Chen
2026-10-28 11:16:11 +08:00
co-authored by Claude Opus 5.5
parent ad801a5db7
commit ea7dd95757
4 changed files with 73 additions and 13 deletions
+5
View File
@@ -1,5 +1,10 @@
# 印度占星 Skill 更新日志
## 2026-10-28 — 只知道出生时段时,咨询出字更快
- 只填了出生时段(没有精确到分钟)的人来咨询时,后台两次计算改成同时进行,每轮约快 0.3~1 秒。回答内容不变。
- Skill 版本不变(BUG-1473,issue #128)。
## 2026-10-28 — 回答出字时页面不再整页重画
- 回答逐字出来时,只有回答那一块在更新;顶栏、输入框和右侧本命盘不再每一帧跟着重画(以前最多每秒 60 次)。长回答时电脑更省力,滚动和点按更跟手。看到的内容不变。
+15
View File
@@ -20096,3 +20096,18 @@
- 关联:BUG-1329(侧栏每帧重注册)、BUG-1130。
- 复发自:无
- 修复版本:分支 `codex/home-stream-rerender-20261028`。
## BUG-1473 | 按出生时段咨询时,窗口排盘和时段读数排队串行
- 状态:resolved(代码 + 回归测试;待部署)
- 首次发现 / 最近更新:2026-10-28 / 2026-10-28(issue #118 代码体检第 11 条 → #128)
- 影响面:只知道出生时段(没有精确出生分钟)的咨询;`frontend/src/mastra/consultation-tools.ts` `executeWindowConsultation`。
- 现象:这类咨询每轮在出字前多等一段:先等 `/api/declared_window_chart` 回来,再发 `/api/rectification/v5/range_reading`。估每轮 0.3~1 秒。
- 触发条件:任何按出生时段(declared birth window)走的咨询轮。
- 根因:两个请求互不依赖,代码却先 `await` 第一个再发第二个。
- 修复:时段读数先发出、窗口排盘随后发出,两者同时进行,再一起等结果。时段读数失败仍只是没有「分钟敏感主题」,与改前相同;窗口排盘失败时整轮照旧失败,只是此时时段读数已经发出(结果丢弃)。
- 验证:`frontend/tests/consultation-agentic-runtime.test.ts` 新增两条:两个桩各延时 150 ms,改后总耗时 <260 ms(改前约 300 ms,改前代码上该测试失败),且窗口排盘返回前时段读数已开始;`minuteSensitiveThemes` 与改前一致。时段读数抛错时照常完成、不带分钟敏感主题。同文件既有窗口咨询测试(BUG-957 三条)全过。tsc 0、lint 0。
- 防复发:上述测试。互不依赖的后端请求同时发。
- 关联:BUG-957(窗口咨询预计算与缓存)。
- 复发自:无
- 修复版本:分支 `codex/window-consult-parallel-20261028`。
+10 -10
View File
@@ -1620,12 +1620,9 @@ async function executeWindowConsultation(
label: "正在比较声明出生窗口内的稳定层",
},
});
const packet = await (ctx.fetchWindowChart ?? fetchDeclaredWindowChart)({
window: ctx.declaredWindow,
signal: context.abortSignal ?? ctx.abortSignal,
});
let minuteSensitiveThemes: string[] = [];
try {
// The range reading does not depend on the window chart, so both go out at once
// (issue #128). A failed reading still only drops the minute-sensitive themes.
const sensitiveThemes = (async () => {
const reading = await (ctx.runRangeReading ?? runV9RangeReading)({
baselineBirthSnapshot: {
birth_date: ctx.declaredWindow.truth.birthDate,
@@ -1640,10 +1637,13 @@ async function executeWindowConsultation(
},
birthTimeAccuracy: "approximate",
});
minuteSensitiveThemes = [...(reading?.sensitiveThemes ?? [])];
} catch {
minuteSensitiveThemes = [];
}
return [...(reading?.sensitiveThemes ?? [])];
})().catch((): string[] => []);
const packet = await (ctx.fetchWindowChart ?? fetchDeclaredWindowChart)({
window: ctx.declaredWindow,
signal: context.abortSignal ?? ctx.abortSignal,
});
const minuteSensitiveThemes = await sensitiveThemes;
const varyingLagna = Array.isArray(packet.varying_layers.ascendant_signs)
&& packet.varying_layers.ascendant_signs.length > 1;
ctx.state.workflowReceipt = {
@@ -1636,7 +1636,12 @@ function windowPacket() {
};
}
function makeWindowCtx(options: { fetchWindowChart?: () => Promise<ReturnType<typeof windowPacket>> } = {}) {
function makeWindowCtx(options: {
fetchWindowChart?: () => Promise<ReturnType<typeof windowPacket>>;
runRangeReading?: () => Promise<{
stableThemes: string[]; sensitiveThemes: string[]; claimBoundary: null; themeSensitivity: Record<string, unknown>; window: null;
}>;
} = {}) {
const state = createConsultationRuntimeState();
const ctx = createWindowConsultationAgentContext({
userId: "u",
@@ -1646,9 +1651,9 @@ function makeWindowCtx(options: { fetchWindowChart?: () => Promise<ReturnType<ty
declaredWindow: declaredWindowFixture,
state,
fetchWindowChart: options.fetchWindowChart ?? (async () => windowPacket()),
runRangeReading: async () => {
runRangeReading: options.runRangeReading ?? (async () => {
throw new Error("range-reading-unused-in-this-test");
},
}),
});
return { ctx, state, tools: createWindowConsultationTools(ctx) };
}
@@ -1730,6 +1735,41 @@ test("window precompute and a later tool call share one cache (BUG-957)", async
assert.deepEqual(first, second);
});
test("the window chart and the range reading go out together (issue #128)", async () => {
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
let rangeStarted = false;
let rangeStartedBeforeChartReturned = false;
const { ctx, state } = makeWindowCtx({
fetchWindowChart: async () => {
await delay(150);
rangeStartedBeforeChartReturned = rangeStarted;
return windowPacket();
},
runRangeReading: async () => {
rangeStarted = true;
await delay(150);
return { stableThemes: [], sensitiveThemes: ["career"], claimBoundary: null, themeSensitivity: {}, window: null };
},
});
const startedAt = Date.now();
await precomputeWindowConsultation(ctx, { question: "事业如何" });
const elapsed = Date.now() - startedAt;
assert.equal(rangeStartedBeforeChartReturned, true);
// Serial was 300 ms; together it is about the longer of the two.
assert.ok(elapsed < 260, `took ${elapsed} ms`);
assert.deepEqual(state.workflowReceipt?.minuteSensitiveThemes, ["career"]);
assert.equal(state.consultationToolCompleted, true);
});
test("a failed range reading still only drops the minute-sensitive themes (issue #128)", async () => {
const { ctx, state } = makeWindowCtx({
runRangeReading: async () => { throw new Error("range_unavailable"); },
});
await precomputeWindowConsultation(ctx, { question: "事业如何" });
assert.equal(state.consultationToolCompleted, true);
assert.equal(state.workflowReceipt?.minuteSensitiveThemes, undefined);
});
test("window precompute failure still degrades when the model writes without a tool (BUG-957)", async () => {
const { ctx, state } = makeWindowCtx({
fetchWindowChart: async () => {