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
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.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function ChannelHeader({
selectedAgentReasoningEffort,
selectedGoal,
selectedGoalTab,
scopeUnavailable = false,
workspaceGrantLabel,
}: {
agents: WorkspaceAgentOption[];
Expand Down Expand Up @@ -81,6 +82,8 @@ export function ChannelHeader({
selectedAgentReasoningEffort?: string;
selectedGoal: WorkspaceGoal | null;
selectedGoalTab: WorkspaceGoalTab;
/** A requested Goal scope failed; do not present the steward as its fallback. */
scopeUnavailable?: boolean;
}) {
const { locale, t } = useWorkspaceI18n();
const statusOnly = selectedAgentId === "status-only";
Expand Down Expand Up @@ -154,7 +157,7 @@ export function ChannelHeader({
: "header.managerAllocationConfigured")
: null;

const runtimeControl = agentWorkActive ? null : readOnlySourceLabel ? (
const runtimeControl = scopeUnavailable || agentWorkActive ? null : readOnlySourceLabel ? (
<span className="personal-read-only-source" title={t("header.readOnlySourceDescription", { source: readOnlySourceLabel })}><Eye size={15} />{readOnlySourceLabel}<small>{t("common.readOnly")}</small></span>
) : <>
<WorkspaceSelect
Expand Down Expand Up @@ -197,8 +200,8 @@ export function ChannelHeader({

// The steward channel binding describes the steward executor; a workspace
// conversation runs on the selected runtime instead.
const stewardBindingVisible = !selectedGoal && !workspaceGrantLabel;
const scopeControl = !selectedGoal && managerChatOpen && conversationScope && !readOnlySourceLabel ? (
const stewardBindingVisible = !scopeUnavailable && !selectedGoal && !workspaceGrantLabel;
const scopeControl = !scopeUnavailable && !selectedGoal && managerChatOpen && conversationScope && !readOnlySourceLabel ? (
<WorkspaceSelect
ariaLabel={t("header.conversationScope")}
className="personal-agent-select personal-scope-select"
Expand All @@ -214,7 +217,7 @@ export function ChannelHeader({
<header className="personal-channel-header" data-goal-selected={Boolean(selectedGoal)} data-workspace-scope={workspaceGrantLabel ? "true" : undefined}>
<button aria-expanded={mobileNavigationOpen ?? false} aria-label={t("header.openGoalNavigation")} className="personal-icon-button personal-mobile-menu" onClick={onOpenNavigation} type="button"><Menu size={18} /></button>
<div className="personal-channel-title">
{selectedGoal ? <GoalMastheadIdentity goal={selectedGoal} onOpenDetails={onOpenGoalDetails} onOpenWorkspace={onOpenWorkspace ?? (() => undefined)} /> : <h1>{t("header.manager")}</h1>}
{selectedGoal ? <GoalMastheadIdentity goal={selectedGoal} onOpenDetails={onOpenGoalDetails} onOpenWorkspace={onOpenWorkspace ?? (() => undefined)} /> : <h1>{t(scopeUnavailable ? "header.goalView" : "header.manager")}</h1>}
{workspaceGrantLabel ? <p className="personal-channel-activity">{t("workspace.conversation", { grant: workspaceGrantLabel })}</p> : null}
<div className="personal-manager-context">
{stewardBindingVisible && managerChannelBinding ? (
Expand Down Expand Up @@ -269,12 +272,12 @@ export function ChannelHeader({
</nav>
{runtimeControl}
</div>
) : (
) : !scopeUnavailable ? (
<nav aria-label={t("header.managerView")} className="personal-goal-tabs">
<button aria-current={!managerChatOpen ? "page" : undefined} onClick={onReturnManagerHome} type="button">{t("header.managerOverview")}</button>
<button aria-current={managerChatOpen ? "page" : undefined} onClick={onOpenManagerChat} type="button">{t("header.chat")}</button>
</nav>
)}
) : null}
<div className="personal-channel-actions">
{selectedGoal && onOpenAllWork ? <button type="button" className="personal-icon-button goal-all-work-trigger" aria-label={locale === "zh-CN" ? "打开全部工作" : "Open all work"} aria-expanded={allWorkOpen} aria-haspopup="dialog" onClick={onOpenAllWork}><List size={17} aria-hidden/></button> : null}
{selectedGoal && !selectedGoal.loadState && onRequestGoalLifecycle ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2674,6 +2674,7 @@ export function PersonalWorkspacePage({
<div>
<ChannelHeader
agents={agents}
scopeUnavailable={rootUnavailable}
agentWorkActive={agentWorkActive}
conversationScope={workspaceConversations && (workspaceConversations.projects?.length || workspaceConversations.readFailed || selectedWorkspaceRef) ? {
onChange: (value) => {
Expand Down
5 changes: 5 additions & 0 deletions examples/personal-workspace-browser/goal-hierarchy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ export const goalHierarchyScenario = {
assert.equal(await page.getByTestId("personal-first-goal").count(), 0);
assert.equal(await page.locator(".personal-manager-greeting, .personal-composer-wrap").count(), 0,
"An unavailable research root must not open an unrelated steward conversation");
assert.equal(await page.getByRole("navigation", {name: "管家视图", exact: true}).count(), 0,
"An unavailable research root cannot offer unrelated steward navigation");
assert.notEqual(await page.locator(".personal-channel-title h1").textContent(), "LoopX 管家");
unavailable.searchParams.set("rootGoalId", "../missing");
await page.goto(unavailable.href, {waitUntil: "networkidle"});
await page.getByRole("alert").filter({hasText: "当前来源中此 Goal 范围不可用"}).waitFor();
Expand All @@ -128,6 +131,8 @@ export const goalHierarchyScenario = {
await page.getByRole("alert").filter({hasText: "当前来源中此 Goal 范围不可用"}).waitFor();
assert.equal(await page.locator(".personal-goal-row").count(), 0, `${invalid}: decoded values cannot widen the scope`);
assert.equal(await page.locator(".personal-manager-greeting, .personal-composer-wrap").count(), 0);
assert.equal(await page.getByRole("navigation", {name: "管家视图", exact: true}).count(), 0);
assert.notEqual(await page.locator(".personal-channel-title h1").textContent(), "LoopX 管家");
await page.getByRole("button", {name: "全部 Goals", exact: true}).click();
await row("daily").waitFor();
await page.goBack({waitUntil: "networkidle"});
Expand Down
Loading