diff --git a/src/agentkit/experts/orchestrator.py b/src/agentkit/experts/orchestrator.py index 7f15a6f..c3d894d 100644 --- a/src/agentkit/experts/orchestrator.py +++ b/src/agentkit/experts/orchestrator.py @@ -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 diff --git a/src/agentkit/server/frontend/src/stores/chatStore.ts b/src/agentkit/server/frontend/src/stores/chatStore.ts index 4fe56b0..0dfdf33 100644 --- a/src/agentkit/server/frontend/src/stores/chatStore.ts +++ b/src/agentkit/server/frontend/src/stores/chatStore.ts @@ -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; }