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
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ try {
assert.equal((await next()).recovered, true);
requests.length = 0;
const restored = await fetchChatHistory(options, partial);
assert.deepEqual(requests, ["/api/chat/sessions/old"], "Recovery reads only the missing session");
assert.deepEqual(requests.map(request => new URL(request, origin).pathname),
["/api/chat/sessions/old"], "Recovery reads only the missing session");
assert.deepEqual(restored.unavailableSessionIds, []);
assert.deepEqual(restored.messages.map(row => row.text), ["Earlier public report", "Current public report"]);
assert.deepEqual(restored.messages.map(row => row.session_id), ["old", "current"], "Colliding IDs preserve both sessions");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ try {
assert.ok((await (await recoveredRead).text()).includes("Revised checked result"), "A non-current conversation recovers before navigation back");
await page.locator(".personal-manager-link").click();
await chat();
assert.equal(await composer.inputValue(), "Keep this draft while original history recovers.",
"Goal navigation and background recovery must preserve the steward draft");
await page.getByText("Revised checked result", { exact: true }).waitFor({ state: "visible" });
assert.equal(await page.getByText("First checked result", { exact: true }).count(), 1);
assert.equal(await page.getByText("Revised checked result", { exact: true }).count(), 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,36 @@ export function useAppStewardTarget(enabled: boolean) {
unavailable: ready && read.unavailable, retry};
}

export function AppStewardEntry({children, onOpen}: {children: ReactNode; onOpen: (target: BoundConversationTarget) => void}) {
export function AppStewardEntry({children, enabled, intercept, onOpen}: {
children: (entry: ReturnType<typeof useAppStewardTarget>) => ReactNode;
enabled: boolean;
intercept: boolean;
onOpen: (target: BoundConversationTarget) => void;
}) {
const {locale} = useWorkspaceI18n();
const zh = locale === "zh-CN";
const entry = useAppStewardTarget(true);
const entry = useAppStewardTarget(enabled);
const open = useRef(onOpen);
open.current = onOpen;
useEffect(() => {if (entry.target) open.current(entry.target);}, [entry.target]);
if (!entry.loading && !entry.unavailable && !entry.target) return children;
useEffect(() => {if (intercept && entry.target) open.current(entry.target);}, [intercept, entry.target]);
const blocked = intercept && (entry.loading || entry.unavailable || Boolean(entry.target));
const settings = new URL(window.location.href);
settings.searchParams.set("view", "overview");
settings.searchParams.set("settings_scope", "machine");
settings.searchParams.set("settings_tab", "lark");
return <main className="mx-auto flex min-h-dvh max-w-xl flex-col justify-center gap-5 p-6 text-slate-900 dark:text-zinc-100">
<h1 className="text-xl font-semibold">{zh ? "LoopX 管家" : "LoopX steward"}</h1>
{!entry.unavailable ? <p role="status">{zh ? "正在打开管家对话…" : "Opening the steward conversation…"}</p> : <>
<p role="alert">{zh ? "原管家会话暂不可用。请检查所选连接后重试。" : "The original steward conversation is unavailable. Check the selected connection and retry."}</p>
<div className="flex flex-wrap gap-3">
<Button onClick={entry.retry}>{zh ? "重试" : "Retry"}</Button>
<a className="inline-flex min-h-9 items-center underline underline-offset-4" href={settings.href}>{zh ? "打开会话设置" : "Open conversation settings"}</a>
</div>
</>}
</main>;
return <>
{/* Keep the workspace and its readers mounted across entry/navigation.
The same readback gates its queue while discovery is unresolved. */}
<div style={{display: blocked ? "none" : "contents"}}>{children(entry)}</div>
{blocked ? <main className="mx-auto flex min-h-dvh max-w-xl flex-col justify-center gap-5 p-6 text-slate-900 dark:text-zinc-100">
<h1 className="text-xl font-semibold">{zh ? "LoopX 管家" : "LoopX steward"}</h1>
{!entry.unavailable ? <p role="status">{zh ? "正在打开管家对话…" : "Opening the steward conversation…"}</p> : <>
<p role="alert">{zh ? "原管家会话暂不可用。请检查所选连接后重试。" : "The original steward conversation is unavailable. Check the selected connection and retry."}</p>
<div className="flex flex-wrap gap-3">
<Button onClick={entry.retry}>{zh ? "重试" : "Retry"}</Button>
<a className="inline-flex min-h-9 items-center underline underline-offset-4" href={settings.href}>{zh ? "打开会话设置" : "Open conversation settings"}</a>
</div>
</>}
</main> : null}
</>;
}
22 changes: 11 additions & 11 deletions apps/presentation/dashboard/src/views/dashboard-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,7 @@ function buildPersonalHomeModel(
};
}
function PersonalGoalHome({
appEntry,
goalArchiveLoadState,
sourceError,
selectedView,
Expand Down Expand Up @@ -1259,6 +1260,7 @@ function PersonalGoalHome({
statusSourceControl,
theme,
}: {
appEntry: ReturnType<typeof useAppStewardTarget>;
goalArchiveLoadState: WorkspaceGoalArchiveLoadState;
/** The current source's latest read failed; the retained payload is stale. */
sourceError: string | null;
Expand Down Expand Up @@ -1379,10 +1381,6 @@ function PersonalGoalHome({
},
};
}, [sourceError, scopedModel, t]);
const appEntryEnabled = !readOnly && statusSourceControl.activeSource.kind === "local"
&& !requestedGoalId && !rootGoalId && !requestedWorkspaceRef && !requestedBoundConversation
&& selectedWorkAgentId == null && !selectedWorkSessionId && selectedView !== "chat";
const appEntry = useAppStewardTarget(appEntryEnabled);
const appEntryBlocked = appEntry.loading || appEntry.unavailable;
const boundConversation = requestedBoundConversation ?? appEntry.target;
const selectedGoalId = rootGoalId && !model.goals.some(goal => goal.goalId === requestedGoalId)
Expand Down Expand Up @@ -3787,8 +3785,9 @@ export function DashboardPage() {
);
}

const home = (
const home = (appEntry: ReturnType<typeof useAppStewardTarget>) => (
<PersonalGoalHome
appEntry={appEntry}
boundConversation={search.conversationBinding !== undefined || search.conversationSession !== undefined
? { bindingId: search.conversationBinding ?? "", sessionId: search.conversationSession ?? "" } : null}
onOpenOriginalConversation={target => void navigate({search: current => ({...current, goalId: "", workspace: undefined,
Expand Down Expand Up @@ -3859,15 +3858,16 @@ export function DashboardPage() {
theme={theme}
/>
);
const defaultStewardEntry = search.view === "conversation" && !search.goalId && !search.rootGoalId
&& search.workspace === undefined && search.workAgent === undefined && search.workSession === undefined
const stewardEntryEnabled = !search.goalId && !search.rootGoalId
&& !search.workspace && search.workAgent === undefined && !search.workSession
&& search.conversationBinding === undefined && search.conversationSession === undefined
&& activeStatusSource.kind === "local" && !activeStatusSource.readOnly;
return defaultStewardEntry ? <AppStewardEntry onOpen={target => void navigate({replace: true,
return <AppStewardEntry enabled={stewardEntryEnabled} intercept={stewardEntryEnabled && search.view === "conversation"}
onOpen={target => void navigate({replace: true,
search: current => "goalId" in current && current.statusUrl === search.statusUrl && current.view === "conversation"
&& !current.goalId && !current.rootGoalId && current.workspace === undefined && current.workAgent === undefined
&& current.workSession === undefined && current.conversationBinding === undefined && current.conversationSession === undefined
&& !current.goalId && !current.rootGoalId && !current.workspace && current.workAgent === undefined
&& !current.workSession && current.conversationBinding === undefined && current.conversationSession === undefined
? {...current, conversationBinding: target.bindingId, conversationSession: target.sessionId} : current})}>
{home}
</AppStewardEntry> : home;
</AppStewardEntry>;
}
10 changes: 7 additions & 3 deletions docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -1315,9 +1315,13 @@ state or grant. Response row count is bounded; server-side journal reads and
prefix hashing still scale with full history and no latency improvement is
claimed. HTTP/store and source/packaged browser checks qualify this bounded
history path; installed native input, reply threading and live App/Lark media
remain separate acceptance. The scoped packaged `--pagination-only` journey
does not certify the default return scenario's background-read wait, which also
times out with the unchanged baseline production code; that gap remains open.
remain separate acceptance. Earlier baseline and `--pagination-only` runs did
not qualify the default return scenario's background wait. That historical
timeout is retained; the default return journey now reads background revisions
across steward/Goal navigation. The App entry keeps one workspace and its
readers mounted, sharing its source readback. Pending or failed discovery still
gates the ordinary queue. Disposable HTTP/store checks qualify this continuation
path, not installed native or live-provider acceptance.

Following the newest reply also survives a resized viewport or timeline, including
image layout and expanded replies, without waiting for another message. Scrolling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -977,8 +977,11 @@ TS 历史 owner 校验并读完全部页面,随后复核
读取提示,不增加 store、consumer、执行状态或 grant。响应记录数有界,但服务端
journal 读取和前缀摘要仍随完整历史增长,不宣称延迟改善。HTTP/store 与源码、
打包浏览器检查验收该历史路径;实际原生输入、回复线程和真实 App/飞书媒体仍需
分别验收。打包 `--pagination-only` 有界旅程不代表默认回传场景的后台读取等待已通过;
该等待在未修改的基线生产代码上同样超时,缺口继续保留。
分别验收。此前基线及 `--pagination-only` 运行未验收默认回传场景的后台读取等待,
该历史超时保留;当前默认回传旅程已覆盖管家/Goal 切换期间的后台修订读取。
App 入口保持同一个工作区及其读取器挂载,共享来源读回;来源发现尚未完成或失败时
仍阻止普通队列准入。隔离 HTTP/store 检查只验收该接续路径,不扩大实际原生或
真实 provider 的验收范围。

同一对话界面在用户停留底部时跟随输出,上翻阅读后保持位置,并提供回到最新消息的入口。
窗口或时间线尺寸变化(包括图片布局与展开回复)后仍应跟随最新回复,不必等待下一条消息。
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading