Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions apps/presentation/dashboard/src/views/dashboard-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1819,13 +1819,16 @@ function PersonalGoalHome({
});
}, [conversationHistory.history, contextId, selectedAgent.label, runtimeAgents]);

// Switching steward views keeps the same conversation. A Goal's overview
// still cannot initiate recovery for a conversation that was not opened.
const mayRecoverConversation = !selectedGoal || selectedView === "chat";
useEffect(() => {
if (readOnly || appEntryBlocked) return;
if (!selectedAgent.available) return;
if (!conversationHistory.connectionKey || !conversationHistory.history) return;
// Browsing a Goal (including palette navigation) must not create/resume
// a session. Explicit Chat entry or sending through the composer owns that.
if (selectedGoal && selectedView !== "chat") return;
if (!mayRecoverConversation) return;
const readHistory = conversationHistory.history;
const targetContextId = contextId;
const sessionKey = `${targetContextId}:${selectedAgent.agentId}`;
Expand Down Expand Up @@ -2098,7 +2101,7 @@ function PersonalGoalHome({
retireRecoveryObservation?.();
window.clearTimeout(handoffRetryTimer);
};
}, [conversationHistory.connectionKey, contextId, model.goals[0]?.goalId, readOnly, appEntryBlocked, selectedView, selectedGoal?.goalId, selectedAgent.agentId, selectedAgent.available, selectedAgent.label, selectedAgents, turnRecoveryRequest]);
}, [conversationHistory.connectionKey, contextId, model.goals[0]?.goalId, readOnly, appEntryBlocked, mayRecoverConversation, selectedGoal?.goalId, selectedAgent.agentId, selectedAgent.available, selectedAgent.label, selectedAgents, turnRecoveryRequest]);

// The projection deliberately outlives the mounted view. Its caller may be
// running for a Goal the owner has already left, and the recovery stream's
Expand Down
38 changes: 28 additions & 10 deletions examples/personal-workspace-browser/conversation-activity.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -213,18 +213,36 @@ export const conversationActivityScenario = {
await makeStorageUnavailable("storage unavailable");
await assertStorageUnavailable("storage unavailable");
await composer.fill("先检查最新证据。");
await page.getByRole("navigation", { name: "管家视图" }).getByRole("button", { name: "总览", exact: true }).click();
await page.getByRole("navigation", { name: "管家视图" }).getByRole("button", { name: /^(Chat|对话)$/ }).click();
assert.equal(await composer.inputValue(), "先检查最新证据。", "Blocked storage preserves the page draft across manager views");
// A slow resume cannot turn a correction into a fresh message while
// the same manager conversation still owns an active Turn.
let releaseResume;
const resumeGate = new Promise(resolve => { releaseResume = resolve; });
const delayResume = async route => {
const request = route.request();
const body = request.method() === "POST" ? request.postDataJSON() : null;
if (body?.context_kind === "manager" && body.mode === "resume_latest") await resumeGate;
return route.fallback();
};
await page.route("**/api/chat/sessions", delayResume);
const streamsBeforeViewChange = observations.length;
const dispatchedBeforeStorageFailure = adjustments.length;
const turnsBeforeStorageFailure = api.turnRequests.length;
await composer.focus();
await page.keyboard.press("Meta+Enter");
await waitForFeedback("调整尚未发送", "Storage failure did not report a retained steering draft");
assert.equal(adjustments.length, dispatchedBeforeStorageFailure, "Storage failure cannot dispatch steering");
assert.equal(api.turnRequests.length, turnsBeforeStorageFailure, "Storage failure cannot queue a new conversation Turn");
assert.equal(await composer.inputValue(), "先检查最新证据。");
await page.screenshot({path: resolve(outputDir, "conversation-steering-storage-unavailable.png"), animations: "disabled"});
try {
await page.getByRole("navigation", { name: "管家视图" }).getByRole("button", { name: "总览", exact: true }).click();
await page.getByRole("navigation", { name: "管家视图" }).getByRole("button", { name: /^(Chat|对话)$/ }).click();
assert.equal(await composer.inputValue(), "先检查最新证据。", "Blocked storage preserves the page draft across manager views");
await composer.focus();
await page.keyboard.press("Meta+Enter");
await waitForFeedback("调整尚未发送", "Storage failure did not report a retained steering draft");
assert.equal(adjustments.length, dispatchedBeforeStorageFailure, "Storage failure cannot dispatch steering");
assert.equal(api.turnRequests.length, turnsBeforeStorageFailure, "Storage failure cannot queue a new conversation Turn");
assert.equal(await composer.inputValue(), "先检查最新证据。");
await page.screenshot({path: resolve(outputDir, "conversation-steering-storage-unavailable.png"), animations: "disabled"});
assert.equal(observations.length, streamsBeforeViewChange, "Switching manager views keeps the original observer");
} finally {
releaseResume();
await page.unroute("**/api/chat/sessions", delayResume);
}
await restoreStorage();
await composer.fill("先检查最新证据,再继续。");
await composer.focus();
Expand Down
2 changes: 1 addition & 1 deletion tests/test_lark_private_conversations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2813,7 +2813,7 @@ def test_app_messages_and_answers_reach_the_original_chat_once_without_new_execu
runtime.close()


def test_app_projection_discovers_a_restored_transcript_with_the_same_size_and_mtime(ordinary):
def test_app_projection_discovers_a_restored_transcript_with_the_same_size_and_mtime(ordinary): # noqa: F811
import os

store, runtime, provider, transport, original, turns, before = _app_projection_journey(ordinary)
Expand Down
Loading