Merge: fix(iq) P1 trigger_reason + P2 autonomy UX 修复
Test / backend-test (push) Waiting to run Details
Test / frontend-unit (push) Waiting to run Details
Test / api-e2e (push) Waiting to run Details
Test / frontend-e2e (push) Waiting to run Details
Deploy to Production / deploy (push) Has been cancelled Details

This commit is contained in:
Chiguyong 2026-07-06 21:24:34 +08:00
commit d74b41b0aa
2 changed files with 35 additions and 2 deletions

View File

@ -589,9 +589,21 @@ class TeamOrchestrator(
f"where natural."
)
# Temporarily wrap task with hint — call _decompose_task with augmented task.
# U4/KTD5: re-decomposition is a re-planning signal — pass trigger_reason
# so retrieve_prompt_reflection can inform the new plan with historical
# reflection. "loop_detection" is the closest KTD5 trigger: the original
# plan hit a structural limit (too many independent subtasks) and needs
# re-planning, analogous to detecting a planning loop.
# ponytail: verify_retry / schema_validation triggers belong to the
# verify-failed / schema-failed retry paths (plan_exec_engine.py /
# verification_loop.py), which do not call _decompose_task today;
# wiring those is deferred until those paths are integrated with
# team re-planning.
augmented_task = f"{task}\n\n[Re-decomposition hint]: {original_hint}"
try:
new_phases = await self._decompose_task(lead, augmented_task)
new_phases = await self._decompose_task(
lead, augmented_task, trigger_reason="loop_detection"
)
except (LLMProviderError, asyncio.TimeoutError, ConnectionError, ValueError) as e:
logger.warning(f"U4 re-decompose failed: {e}, keeping original phases")
return phases

View File

@ -5,7 +5,7 @@ import { useTeamStore } from "@/stores/team";
import { useDocumentsStore } from "@/stores/documents";
import { useCalendarStore } from "@/stores/calendar";
import { useChatSocket } from "@/stores/chatSocket";
import { useChatStream } from "@/stores/chatStream";
import { useChatStream, updateLastStep } from "@/stores/chatStream";
import type { BoardState } from "@/stores/chatStream";
import { pickExpertIdentity } from "@/components/chat/helpers/expertIdentity";
import type {
@ -225,6 +225,16 @@ export const useChatStore = defineStore("chat", () => {
// 会话隔离 — 切换会话时重置三个 transient state避免跨会话数据泄漏。
// force-reload 同一会话时不重置,防止误清空。
if (prevConvId !== id) {
// U1/P2-1: if the previous conversation had an autonomy-paused execution
// waiting for resume, cancel it server-side so the engine's
// _resume_handler future does not hang for up to 30 minutes.
if (stream.autonomyPaused.value && socket.ws.value?.readyState === WebSocket.OPEN) {
try {
socket.ws.value.send(JSON.stringify({ type: "cancel" } as WsClientMessage));
} catch {
// swallow — socket teardown will cancel the future server-side
}
}
stream.boardState.value = null;
stream.debateState.value = null;
stream.collaborationState.value = null;
@ -546,6 +556,17 @@ export const useChatStore = defineStore("chat", () => {
*/
function resumeAutonomy(resumeToken: string): void {
stream.autonomyPaused.value = null;
// U1/P2-2: mark the "自主执行已暂停" milestone as completed so the
// streaming step list does not leave a perpetual "running" entry.
const cid = currentConversationId.value;
if (cid) {
updateLastStep(
stream.streamingStepsByConv,
(s) => s.type === "milestone" && s.label === "自主执行已暂停",
{ status: "success" },
cid,
);
}
if (!socket.ws.value || socket.ws.value.readyState !== WebSocket.OPEN) {
return;
}