From e87e3aa8269e8afd7ee60ed364c857a5380da382 Mon Sep 17 00:00:00 2001 From: song Date: Thu, 15 Oct 2026 18:22:49 +0800 Subject: [PATCH 1/4] feat(workspace): show a first-Goal home instead of a fresh-install health alert A fresh install opens the manager home with no registry yet. The home rendered that as a red system-health alert with protocol text ("payload.ok === false", hard-coded Chinese) above an empty "All Goals" heading, and the workspace-directory read answered 500. Chat status now passes the existing `allow_missing_registry` rule, so an absent default registry is the warning the CLI already reports for it, and the discovery view returns an empty directory instead of failing. The home read model carries a typed `no_goals` classification that holds only when the source is healthy with zero Goals and no history; the page then shows what a Goal is, one Create action reusing the existing form, and three example requests that only fill the composer. Health issues are localized owner-language sentences; the source's raw contract lines stay in a collapsed disclosure. A corrupt registry keeps its banner and gets no first-Goal invitation. Validation: new browser scenario `first-goal-home` (desktop, phone, en, create form, unhealthy negative), workspace contract test, dashboard tsc, CSS token check, and Python tests for the status and directory paths. The packaged bundle was served on an empty HOME and read back. Co-Authored-By: Claude Fable 5.1 Signed-off-by: song --- .../src/features/personal-workspace/i18n.tsx | 38 ++++++ .../personal-workspace-contract.test.mjs | 12 ++ .../personal-workspace-model.ts | 17 +++ .../personal-workspace-page.tsx | 45 ++++++- .../personal-workspace/personal-workspace.css | 21 ++++ .../dashboard/src/views/dashboard-page.tsx | 35 ++++-- docs/guides/personal-workspace-user-guide.md | 15 +++ examples/personal-workspace-browser-smoke.mjs | 2 + .../first-goal-home.mjs | 115 ++++++++++++++++++ loopx/chat_status_api.py | 22 +++- loopx/control_plane/status/collection.py | 2 + loopx/status.py | 2 + tests/test_chat_server_cors.py | 38 ++++++ tests/test_status_missing_registry.py | 35 ++++++ 14 files changed, 386 insertions(+), 13 deletions(-) create mode 100644 examples/personal-workspace-browser/first-goal-home.mjs create mode 100644 tests/test_status_missing_registry.py diff --git a/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx b/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx index eddda1027d..c64c5c9710 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx @@ -1397,6 +1397,25 @@ const en = { "brief.completedEmpty": "No completed work yet", "brief.completedTotal": "{count} completed in total", "home.allGoals": "All Goals", + "home.firstGoal.title": "No Goals yet", + "home.firstGoal.lead": "A Goal is one outcome you want; the Agent keeps working toward it until you stop it. Describe the outcome in your own words. The LoopX Manager drafts the Goal and nothing is created until you confirm.", + "home.firstGoal.greeting": "Let’s start with your first Goal.", + "home.firstGoal.create": "Create a Goal", + "home.firstGoal.examplesTitle": "Or start from an example. It only fills the message box; nothing is sent until you press send.", + "home.firstGoal.example.research": "Help me shape a goal to research Microsoft’s cash flow over the last three fiscal years from its official reports.", + "home.firstGoal.example.issue": "Help me shape a goal to turn one open issue in a repository I choose into a reviewed patch.", + "home.firstGoal.example.monitor": "Help me shape a goal to watch a public web page every weekday and report only when its content changes.", + "home.firstGoal.diagnostics": "Status source details", + "home.health.payloadNotOk": "The status source reports that it is not healthy.", + "home.health.contractFailed": "Control-plane contract checks failed: {errors} error(s), {warnings} warning(s).", + "home.health.contractFailedDetail": "Control-plane contract checks failed: {detail}", + "home.health.contractFailedUnknown": "Control-plane contract checks failed.", + "home.health.registryHigh": "Registry checks found {count} high-severity finding(s).", + "home.health.diagnostics": "Diagnostics", + "home.health.allClear": "All control-plane contract and registry checks passed.", + "home.health.checked": "Status checks completed.", + "home.health.concerns": "{count} concern(s) found.", + "home.health.freshness": "{count} decision state(s) are stale.", "state.stopped": "Stopped", "state.waiting": "Waiting", "tasks.blocked": "Blocked", @@ -2881,6 +2900,25 @@ const zhCN: Record = { "brief.completedEmpty": "还没有完成的工作", "brief.completedTotal": "累计完成 {count} 项", "home.allGoals": "全部 Goal", + "home.firstGoal.title": "还没有 Goal", + "home.firstGoal.lead": "一个 Goal 就是你想要的一个结果,Agent 会持续推进它直到你叫停。用你自己的话描述想要的结果,LoopX 管家会先整理成 Goal 草稿,你确认之前不会创建任何东西。", + "home.firstGoal.greeting": "我们从第一个 Goal 开始。", + "home.firstGoal.create": "创建 Goal", + "home.firstGoal.examplesTitle": "也可以从一个例子开始。点选只会填入输入框,按发送前不会发出任何内容。", + "home.firstGoal.example.research": "帮我整理一个目标:依据官方财报研究微软最近三个财年的现金流。", + "home.firstGoal.example.issue": "帮我整理一个目标:把我指定仓库里的一个未关闭 issue 做成一个经过评审的补丁。", + "home.firstGoal.example.monitor": "帮我整理一个目标:每个工作日查看一个公开网页,只有内容变化时才向我汇报。", + "home.firstGoal.diagnostics": "状态来源详情", + "home.health.payloadNotOk": "状态来源报告自身不健康。", + "home.health.contractFailed": "控制面契约检查未通过:{errors} 项错误,{warnings} 项警告。", + "home.health.contractFailedDetail": "控制面契约检查未通过:{detail}", + "home.health.contractFailedUnknown": "控制面契约检查未通过。", + "home.health.registryHigh": "注册表检查发现 {count} 项高危问题。", + "home.health.diagnostics": "诊断信息", + "home.health.allClear": "所有控制面契约与注册表检查均正常。", + "home.health.checked": "状态检查已完成。", + "home.health.concerns": "发现 {count} 项需要关注。", + "home.health.freshness": "{count} 项决策状态已过期。", "state.stopped": "已停止", "state.waiting": "等待条件", "tasks.blocked": "受阻", diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs index 7fc03c2055..6c39dd13be 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs @@ -585,4 +585,16 @@ assert.match( "The Goal header prefers the current projected Todo over stale recommended_action strings", ); +// First-Goal home: a healthy source with no registered Goal is a designed +// state, not empty lanes, and health issues are owner-language sentences. +assert.match(model, /kind: "no_goals"/, "The home carries a typed first-Goal classification"); +assert.match(dashboard, /const onboarding: WorkspaceHomeOnboarding \| undefined = isHealthy/, "Only a healthy source classifies as a first-Goal workspace"); +assert.doesNotMatch(dashboard, /payload\.ok === false|契约检查未通过|系统健康关注点/, "Health issues are localized owner language, not protocol fragments"); +assert.match(page, /data-testid="personal-first-goal"/, "The home renders the first-Goal state"); +assert.match(page, /onSuggestRequest\(t\(`home\.firstGoal\.example\./, "Example requests only fill the composer"); +assert.match(page, /personal-first-goal-diagnostics/, "Status source details stay in a collapsed disclosure"); +for (const key of ["home.firstGoal.title", "home.firstGoal.lead", "home.firstGoal.create", "home.health.contractFailed", "home.health.concerns"]) { + assert.equal((i18n.match(new RegExp(`"${key.replace(/\./g, "\\.")}":`, "g")) ?? []).length, 2, `${key} exists in both locales`); +} + console.log("personal workspace drawer contract smoke passed"); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-model.ts b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-model.ts index 3611611929..efd2a63df9 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-model.ts +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-model.ts @@ -434,12 +434,26 @@ export type WorkspaceGoalNotification = { }; export type WorkspaceSystemHealth = { + /** Status-source contract lines for a collapsed disclosure; never primary copy. */ + diagnostics?: string[]; freshnessWarning?: string | null; issues: string[]; ok: boolean; summary: string; }; +/** + * Typed first-Goal classification of the manager home. `no_goals` means the + * status source reports zero registered Goals and no execution history, so + * the home shows its first-Goal state instead of empty lanes. `diagnostics` + * carries the status source's own contract lines for a collapsed disclosure; + * it is never primary copy. + */ +export type WorkspaceHomeOnboarding = { + kind: "no_goals"; + diagnostics: string[]; +}; + export type WorkspaceModel = { blockingTodoCount: number; goalNotifications?: WorkspaceGoalNotification[]; @@ -449,6 +463,7 @@ export type WorkspaceModel = { error?: string | null; loading: boolean; }; + onboarding?: WorkspaceHomeOnboarding; systemHealth?: WorkspaceSystemHealth; timeline?: WorkspaceTimelineItem[]; attentionHistory?: WorkspaceAttention[]; @@ -490,6 +505,7 @@ export type PersonalHomeCompatibleModel = { goalNotifications?: WorkspaceGoalNotification[]; goals: WorkspaceGoal[]; openUserTodoCount: number; + onboarding?: WorkspaceHomeOnboarding; systemHealth?: WorkspaceSystemHealth; attentionHistory?: WorkspaceAttention[]; userTodos: WorkspaceAttention[]; @@ -596,6 +612,7 @@ export function normalizePersonalHomeModel(model: PersonalHomeCompatibleModel): goalNotifications: model.goalNotifications, goals: model.goals, openUserTodoCount: model.openUserTodoCount, + onboarding: model.onboarding, systemHealth: model.systemHealth, attentionHistory: model.attentionHistory, userTodos: model.userTodos, diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx index 85df233936..2f802b542e 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx @@ -73,6 +73,7 @@ import type { WorkspaceImageAttachment, WorkspaceModel, WorkspaceRun, + WorkspaceHomeOnboarding, WorkspaceSystemHealth, WorkspaceConversationDirectory, WorkspaceTimelineItem, @@ -120,6 +121,9 @@ function ManagerHomeBoard({ goals, onSelectGoal, onRetry, + onboarding, + onRequestGoalCreate, + onSuggestRequest, systemHealth, operations, onSelectOperation, @@ -128,6 +132,9 @@ function ManagerHomeBoard({ goals: WorkspaceGoal[]; onSelectGoal: (goalId: string) => void; onRetry?: () => void; + onboarding?: WorkspaceHomeOnboarding; + onRequestGoalCreate?: () => void; + onSuggestRequest?: (text: string) => void; systemHealth?: WorkspaceSystemHealth; operations: WorkspaceActionPreview[]; onSelectOperation: (proposal: WorkspaceActionPreview) => void; @@ -176,8 +183,41 @@ function ManagerHomeBoard({ ))} ) : null} + {systemHealth.diagnostics?.length ? ( +
+ {t("home.health.diagnostics")} +
    {systemHealth.diagnostics.map((line, idx) =>
  • {line}
  • )}
+
+ ) : null} ) : null} + {onboarding?.kind === "no_goals" && goals.length === 0 ? ( +
+

{t("home.firstGoal.title")}

+

{t("home.firstGoal.lead")}

+ {onRequestGoalCreate ? ( +
+ +
+ ) : null} + {onSuggestRequest ? ( +
    +
  • {t("home.firstGoal.examplesTitle")}
  • + {(["research", "issue", "monitor"] as const).map((example) => ( +
  • + +
  • + ))} +
+ ) : null} + {onboarding.diagnostics.length ? ( +
+ {t("home.firstGoal.diagnostics")} +
    {onboarding.diagnostics.map((line, idx) =>
  • {line}
  • )}
+
+ ) : null} +
+ ) : null} {currentGoals.some((goal) => goal.loadState) ?
{t("startup.progress", { loaded: currentGoals.filter((goal) => !goal.loadState).length, total: currentGoals.length })}
{failedCount ?
{t("startup.failedCount", { count: failedCount })} @@ -186,7 +226,7 @@ function ManagerHomeBoard({
: null} {currentGoals.some((goal) => !goal.loadState) ? : null} -

{t("home.allGoals")}

+ {onboarding?.kind === "no_goals" && goals.length === 0 ? null :

{t("home.allGoals")}

}
{activeHomeLanes.filter((lane) => active[lane.key].length > 0).map((lane) => (
@@ -2210,7 +2250,7 @@ export function PersonalWorkspacePage({ {!selectedGoal && !managerChatOpen ? (
-
{t("brief.title")} · {new Intl.DateTimeFormat(locale, { month: "long", day: "numeric", weekday: "short" }).format(new Date())}{t("home.greeting")}

{model.goals.some((goal) => goal.activationState === "active" && goal.loadState) ? t("startup.partial") : <>{t("home.waitingCount", { count: managerNeedsYouCount })} {managerBlockingCount > 0 ? t("home.blockingSummary", { count: managerBlockingCount }) : null} {!actionReadback.isError && homeOperations.length ? t("home.operationSummary", {count: homeOperations.length}) : null}}

+
{t("brief.title")} · {new Intl.DateTimeFormat(locale, { month: "long", day: "numeric", weekday: "short" }).format(new Date())}{t("home.greeting")}

{model.goals.some((goal) => goal.activationState === "active" && goal.loadState) ? t("startup.partial") : model.onboarding?.kind === "no_goals" && model.goals.length === 0 ? t("home.firstGoal.greeting") : <>{t("home.waitingCount", { count: managerNeedsYouCount })} {managerBlockingCount > 0 ? t("home.blockingSummary", { count: managerBlockingCount }) : null} {!actionReadback.isError && homeOperations.length ? t("home.operationSummary", {count: homeOperations.length}) : null}}

) : null} {selectedGoal && !selectedGoal.loadState && readOnly && routedTodoId @@ -2254,6 +2294,7 @@ export function PersonalWorkspacePage({ }} /> ) : !managerChatOpen ? ( void callbacks.onRefresh?.("missing")} onSelectGoal={selectGoal} systemHealth={model.systemHealth} + onboarding={model.onboarding} onRequestGoalCreate={readOnly ? undefined : requestGoalCreate} onSuggestRequest={readOnly ? undefined : suggestReply} operations={actionReadback.isError ? [] : homeOperations} onSelectOperation={proposal => setSelection({kind: "proposal", item: proposal})} onViewAllOperations={() => setSelectedGoalTab("chat")} /> ) : ( diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace.css b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace.css index 22181b0d3e..36a432e526 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace.css +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace.css @@ -312,6 +312,24 @@ button.personal-execution-chip:focus-visible { outline: 2px solid #0070f3; outli .personal-system-health-header { display: flex; align-items: center; gap: 8px; font-size: var(--text-body); line-height: var(--leading-body); font-weight: 600; } .personal-system-health-header small { color: #b91c1c; font-size: var(--text-caption); line-height: var(--leading-caption); font-weight: 400; } .personal-system-health-issues { margin: 6px 0 0 24px; padding: 0; font-size: var(--text-caption); line-height: var(--leading-caption); color: #b91c1c; } +.personal-first-goal { display: grid; gap: 16px; max-width: 640px; min-width: 0; padding: 8px 0 24px; border-bottom: 1px solid var(--pw-line); } +.personal-first-goal h2 { margin: 0; color: var(--pw-text); font-size: var(--text-heading); line-height: var(--leading-heading); letter-spacing: -0.01em; } +.personal-first-goal > p { margin: 0; color: var(--pw-muted); font-size: var(--text-body); line-height: var(--leading-body); } +.personal-first-goal-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; } +.personal-first-goal-create { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 16px; border: 0; border-radius: 10px; background: var(--pw-blue); color: var(--pw-card); font-size: var(--text-body); font-weight: 600; cursor: pointer; } +.personal-first-goal-create:hover { background: var(--pw-blue-ink); } +.personal-first-goal-create:focus-visible { outline: 2px solid var(--pw-blue); outline-offset: 2px; } +.personal-first-goal-examples { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; } +.personal-first-goal-examples > li:first-child { color: var(--pw-muted); font-size: var(--text-caption); line-height: var(--leading-caption); } +.personal-first-goal-examples button { display: block; width: 100%; min-height: 44px; padding: 10px 0; border: 0; border-top: 1px solid var(--pw-line); background: transparent; color: var(--pw-text); font-size: var(--text-body); line-height: var(--leading-body); text-align: left; cursor: pointer; } +.personal-first-goal-examples button:hover { color: var(--pw-blue-ink); } +.personal-first-goal-examples button:focus-visible { outline: 2px solid var(--pw-blue); outline-offset: -2px; } +.personal-first-goal-diagnostics { color: var(--pw-muted); font-size: var(--text-caption); line-height: var(--leading-caption); } +.personal-first-goal-diagnostics summary { cursor: pointer; } +.personal-first-goal-diagnostics ul { margin: 6px 0 0 16px; padding: 0; } +.personal-system-health-diagnostics { margin-top: 6px; font-size: var(--text-caption); line-height: var(--leading-caption); } +.personal-system-health-diagnostics summary { cursor: pointer; } +.personal-system-health-diagnostics ul { margin: 4px 0 0 16px; padding: 0; } .personal-home-lane { display: flex; flex-direction: column; min-width: 0; min-height: 100%; border: 1px solid var(--pw-line); border-radius: 16px; background: #fff; box-shadow: 0 1px 4px rgb(30 28 20 / 4%); } .personal-manager-greeting > span { display: grid; flex: none; place-items: center; width: 44px; height: 44px; border-radius: 12px; background: var(--pw-blue-soft); color: var(--pw-blue); } .personal-manager-greeting div { display: grid; gap: 4px; min-width: 0; } @@ -1857,6 +1875,9 @@ button.personal-execution-chip:focus-visible { outline: 2px solid #0070f3; outli .personal-workspace-shell[data-pw-theme="brutal"] .personal-manager-conversation-messages article.is-user { background: #8fdcff; } .personal-workspace-shell[data-pw-theme="brutal"] .personal-quick-prompts button { border: 2px solid #141414; background: #fff; color: #141414; box-shadow: 2px 2px 0 #141414; } .personal-workspace-shell[data-pw-theme="brutal"] .personal-quick-prompts button:hover { background: #ffe23f; border-color: #141414; color: #141414; } +.personal-workspace-shell[data-pw-theme="brutal"] .personal-first-goal-create { border: 2px solid #141414; border-radius: 4px; background: #ffe23f; color: #141414; box-shadow: 3px 3px 0 #141414; } +.personal-workspace-shell[data-pw-theme="brutal"] .personal-first-goal-create:hover { background: #ffd91a; } +.personal-workspace-shell[data-pw-theme="brutal"] .personal-first-goal-examples button { border-top: 1.5px solid #141414; } .personal-workspace-shell[data-pw-theme="brutal"] .personal-channel-composer { border: 2px solid #141414; border-radius: 6px; box-shadow: 3px 3px 0 #141414; } .personal-workspace-shell[data-pw-theme="brutal"] .personal-channel-composer:focus-within { border-color: #141414; box-shadow: 5px 5px 0 #141414; } .personal-workspace-shell[data-pw-theme="brutal"] .personal-channel-composer textarea { border-left: 2px solid #141414; } diff --git a/apps/presentation/dashboard/src/views/dashboard-page.tsx b/apps/presentation/dashboard/src/views/dashboard-page.tsx index 832b6a55a9..e4d3c91baa 100644 --- a/apps/presentation/dashboard/src/views/dashboard-page.tsx +++ b/apps/presentation/dashboard/src/views/dashboard-page.tsx @@ -108,6 +108,7 @@ import { type WorkspaceGoalTab, type WorkspaceGoalUsage, type WorkspaceImageAttachment, + type WorkspaceHomeOnboarding, type WorkspaceSystemHealth, type WorkspaceTimelineItem, type WorkspaceWorker, @@ -504,6 +505,7 @@ type PersonalHomeModel = { goalNotifications?: WorkspaceGoalNotification[]; goals: PersonalGoalItem[]; openUserTodoCount: number; + onboarding?: WorkspaceHomeOnboarding; systemHealth?: WorkspaceSystemHealth; attentionHistory?: PersonalNeedsYouItem[]; userTodos: PersonalNeedsYouItem[]; @@ -1105,20 +1107,23 @@ function buildPersonalHomeModel( }]; }); + // Owner language first: each issue is a localized sentence. The status + // source's own contract lines stay available to a collapsed disclosure. const systemHealthIssues: string[] = []; if (!payload.ok) { - systemHealthIssues.push("状态载荷未标记为正常 (payload.ok === false)"); + systemHealthIssues.push(t("home.health.payloadNotOk")); } if (payload.contract && !payload.contract.ok) { const summary = payload.contract.summary; - const detail = summary - ? `${summary.errors} 项错误 / ${summary.warnings} 项警告` - : (payload.contract.errors?.[0] || "请检查控制面契约"); - systemHealthIssues.push(`契约检查未通过: ${detail}`); + systemHealthIssues.push(summary + ? t("home.health.contractFailed", { errors: summary.errors, warnings: summary.warnings }) + : payload.contract.errors?.[0] + ? t("home.health.contractFailedDetail", { detail: payload.contract.errors[0] }) + : t("home.health.contractFailedUnknown")); } if (payload.global_registry) { if (!payload.global_registry.ok) { - systemHealthIssues.push(`注册表状态异常: ${payload.global_registry.summary.high} 项高危`); + systemHealthIssues.push(t("home.health.registryHigh", { count: payload.global_registry.summary.high })); } for (const finding of payload.global_registry.findings || []) { if (finding.severity === "high") { @@ -1127,17 +1132,24 @@ function buildPersonalHomeModel( } } const freshnessWarning = payload.decision_freshness_summary?.summary?.stale_count - ? `${payload.decision_freshness_summary.summary.stale_count} 项决策状态已过期` + ? t("home.health.freshness", { count: payload.decision_freshness_summary.summary.stale_count }) : null; const isHealthy = systemHealthIssues.length === 0 && !freshnessWarning; const systemHealth: WorkspaceSystemHealth = { ok: isHealthy, summary: isHealthy - ? "所有控制面契约与注册表检查均正常" - : `发现 ${systemHealthIssues.length + (freshnessWarning ? 1 : 0)} 项系统健康关注点`, + ? t("home.health.allClear") + : t("home.health.concerns", { count: systemHealthIssues.length + (freshnessWarning ? 1 : 0) }), issues: systemHealthIssues, freshnessWarning, + diagnostics: [...(payload.contract?.errors ?? []), ...(payload.contract?.warnings ?? [])], }; + // A healthy source with no registered Goal and no execution history is a + // first-Goal workspace, not a failure. Any health issue keeps its banner. + const onboarding: WorkspaceHomeOnboarding | undefined = isHealthy + && payload.goal_count === 0 && goals.length === 0 && (payload.run_history?.goals ?? []).length === 0 + ? { kind: "no_goals", diagnostics: [...(payload.contract?.warnings ?? []), ...(payload.contract?.checks ?? [])] } + : undefined; return { blockingTodoCount: allUserTodos.filter((todo) => todo.blocking).length, @@ -1159,6 +1171,7 @@ function buildPersonalHomeModel( })), goals, openUserTodoCount: allUserTodos.length, + onboarding, systemHealth, attentionHistory: [...attentionHistory, ...pendingOperatorGates], userTodos: allUserTodos, @@ -1256,10 +1269,12 @@ function PersonalGoalHome({ openUserTodoCount: userTodos.length, blockingTodoCount: userTodos.filter((todo) => todo.blocking).length, workers: [...new Map(models.flatMap((item) => item.workers ?? []).map((worker) => [worker.agentId, worker])).values()], goalNotifications: models.flatMap((item) => item.goalNotifications ?? []), + onboarding: incomplete ? undefined : base.onboarding, systemHealth: incomplete || models.length === 0 ? undefined : { ok: models.every((item) => item.systemHealth?.ok), issues, - summary: issues.length ? `发现 ${issues.length} 项系统健康关注点` : "状态检查已完成", + summary: issues.length ? t("home.health.concerns", { count: issues.length }) : t("home.health.checked"), freshnessWarning: models.map((item) => item.systemHealth?.freshnessWarning).filter(Boolean).join(";") || null, + diagnostics: [...new Set(models.flatMap((item) => item.systemHealth?.diagnostics ?? []))], }, }; }, [payload, rows, progress, goalSubagentConfigurationEnabled, t]); diff --git a/docs/guides/personal-workspace-user-guide.md b/docs/guides/personal-workspace-user-guide.md index 0fefa252cd..80ae06e3b5 100644 --- a/docs/guides/personal-workspace-user-guide.md +++ b/docs/guides/personal-workspace-user-guide.md @@ -69,6 +69,21 @@ graph TD ![LoopX 管家总览与 4 泳道流](../assets/personal-workspace/guide_manager_overview.png) +### 首次打开 / First open + +还没有任何 Goal 时,总览不显示空泳道或健康告警,而是一个「还没有 Goal」状态:一句话说明 +Goal 是什么、一个 **创建 Goal** 按钮(打开与侧边栏 `+` 相同的创建表单),以及三条示例 +请求。点选示例只会填入输入框,按发送前不会发出任何内容。状态来源自身的检查行(例如 +本机尚未建立注册表)折叠在「状态来源详情」里。只有状态来源真的不健康(例如注册表损坏) +时才显示健康告警,并以可读句子描述问题,原始检查行同样折叠在「诊断信息」里。 + +When no Goal exists yet, the overview shows a first-Goal state instead of empty lanes or a +health banner: what a Goal is, a **Create a Goal** button (the same form as the sidebar `+`), +and three example requests that only fill the composer. The status source's own check +lines (such as "no registry yet on this machine") stay collapsed under "Status source +details". A health banner appears only when the source is actually unhealthy, in plain +sentences, with the raw check lines collapsed under "Diagnostics". + ### 核心功能区 1. **「你不在的时候」离线概览**: - 聚合展示你离开期间所有 Agent 的运行结果:`已完成` 数量、`异常/失败` 数量以及当前 **`等你确认`** 的阻塞项。 diff --git a/examples/personal-workspace-browser-smoke.mjs b/examples/personal-workspace-browser-smoke.mjs index 14348a3e96..4c5efde221 100644 --- a/examples/personal-workspace-browser-smoke.mjs +++ b/examples/personal-workspace-browser-smoke.mjs @@ -5,6 +5,7 @@ import {goalWorkspaceCreateScenario} from "./personal-workspace-browser/goal-wor import {agentModelSelectionScenario} from "./personal-workspace-browser/agent-model-selection.mjs"; import {goalWorkspaceM2Scenario} from "./personal-workspace-browser/goal-workspace-m2.mjs"; import {goalWorkspaceSelectionScenario} from "./personal-workspace-browser/goal-workspace-selection.mjs"; +import {firstGoalHomeScenario} from "./personal-workspace-browser/first-goal-home.mjs"; import { replanCadenceScenario } from "./personal-workspace-browser/replan-cadence.mjs"; import {nativeChildActivityScenario} from "./personal-workspace-browser/native-child-activity.mjs"; import {privateStewardScopeScenario} from "./personal-workspace-browser/private-steward-scope.mjs"; @@ -109,6 +110,7 @@ scenarioCatalog.push(zcodeGoalScenario); scenarioCatalog.push(goalWorkspaceM2Scenario); scenarioCatalog.push(goalWorkspaceCreateScenario); scenarioCatalog.push(goalWorkspaceSelectionScenario); +scenarioCatalog.push(firstGoalHomeScenario); const requestedScenario = process.env.LOOPX_PERSONAL_WORKSPACE_SCENARIO; scenarioCatalog.push(agentModelSelectionScenario); const scenarios = requestedScenario diff --git a/examples/personal-workspace-browser/first-goal-home.mjs b/examples/personal-workspace-browser/first-goal-home.mjs new file mode 100644 index 0000000000..a15f23fdb7 --- /dev/null +++ b/examples/personal-workspace-browser/first-goal-home.mjs @@ -0,0 +1,115 @@ +import assert from "node:assert/strict"; +import { resolve } from "node:path"; +import { outputDir } from "./fixture.mjs"; +import { openWorkspacePage } from "./scenario-context.mjs"; + +// A fresh install opens to the manager home with zero registered Goals. The +// status source is healthy (a missing default registry is a warning, not an +// error), so the home must show its first-Goal state instead of empty lanes or +// a health banner, and example requests may only fill the composer. A source +// that is actually unhealthy keeps its banner in owner language and does not +// invite the first Goal. + +function emptyWorkspace(fixture, { healthy = true } = {}) { + fixture.ok = healthy; + fixture.goal_count = 0; + fixture.run_count = 0; + fixture.run_history.goals = []; + fixture.run_history.recent_runs = []; + fixture.run_history.goal_count = 0; + fixture.run_history.run_count = 0; + fixture.attention_queue.items = []; + fixture.attention_queue.item_count = 0; + fixture.attention_queue.needs_user_or_controller = 0; + fixture.attention_queue.needs_codex = 0; + fixture.attention_queue.watching_external_evidence = 0; + if (fixture.usage_summary) fixture.usage_summary.goals = []; + fixture.contract = healthy + ? { ok: true, summary: { errors: 0, warnings: 1, checks: 3 }, errors: [], + warnings: ["registry file does not exist: [local-path]"], + checks: ["runtime root resolved: [local-path]", "run-history goals=0 runs=0", "public boundary scan deferred for status hot path"] } + : { ok: false, summary: { errors: 1, warnings: 0, checks: 2 }, errors: ["registry file is not valid JSON"], warnings: [], + checks: ["runtime root resolved: [local-path]", "run-history goals=0 runs=0"] }; +} + +export const firstGoalHomeScenario = { + id: "first-goal-home", + async run({ browser, collectCoverage, url }) { + const coverageEntries = []; + const session = await openWorkspacePage(browser, url, { collectCoverage, viewport: { width: 1440, height: 900 }, + beforeGoto: (api) => { api.transformStatus = (fixture) => emptyWorkspace(fixture); } }); + const { page } = session; + const turnRequests = []; + page.on("request", (request) => { + if (request.method() === "POST" && /\/api\/chat\/sessions/.test(request.url())) turnRequests.push(request.url()); + }); + try { + const firstGoal = page.getByTestId("personal-first-goal"); + await firstGoal.waitFor({ state: "visible", timeout: 15_000 }); + assert.equal(await page.locator(".personal-system-health-banner").count(), 0, "A missing default registry is not a health concern"); + assert.equal(await page.locator(".personal-home-section-title").count(), 0, "No empty 'All Goals' heading in the first-Goal state"); + assert.match(await firstGoal.locator("h2").innerText(), /还没有 Goal/); + assert.match(await page.locator(".personal-manager-greeting p").innerText(), /我们从第一个 Goal 开始/); + // Status source details stay collapsed and carry the source's own warning. + const details = firstGoal.locator("details.personal-first-goal-diagnostics"); + assert.equal(await details.getAttribute("open"), null, "Diagnostics are collapsed by default"); + assert.match((await details.locator("li").first().textContent()) ?? "", /registry file does not exist/); + await page.screenshot({ path: resolve(outputDir, "first-goal-home-desktop.png"), animations: "disabled" }); + + // An example request only fills the composer; nothing is sent. + const composer = page.locator(".personal-channel-composer textarea"); + const example = firstGoal.locator(".personal-first-goal-examples button").first(); + const exampleText = (await example.innerText()).trim(); + await example.click(); + assert.equal((await composer.inputValue()).trim(), exampleText, "The example fills the composer verbatim"); + assert.equal(turnRequests.length, 0, "Selecting an example sends nothing"); + await page.keyboard.press("Escape"); + + // Create a Goal opens the existing typed Goal form in place. + await firstGoal.locator(".personal-first-goal-create").click(); + const form = page.locator("dialog.personal-action-form"); + await form.waitFor({ state: "visible", timeout: 10_000 }); + await page.screenshot({ path: resolve(outputDir, "first-goal-home-create.png"), animations: "disabled" }); + await page.keyboard.press("Escape"); + await form.waitFor({ state: "hidden", timeout: 10_000 }); + + // English copy and the phone composition. + await page.evaluate(() => localStorage.setItem("loopx-pw-locale", "en")); + await page.reload({ waitUntil: "networkidle" }); + await firstGoal.waitFor({ state: "visible", timeout: 15_000 }); + assert.match(await firstGoal.locator("h2").innerText(), /No Goals yet/); + await page.screenshot({ path: resolve(outputDir, "first-goal-home-en.png"), animations: "disabled" }); + await page.evaluate(() => localStorage.removeItem("loopx-pw-locale")); + await page.setViewportSize({ width: 390, height: 844 }); + await page.reload({ waitUntil: "networkidle" }); + await firstGoal.waitFor({ state: "visible", timeout: 15_000 }); + assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth), true, "No horizontal overflow on a phone"); + await page.screenshot({ path: resolve(outputDir, "first-goal-home-phone.png"), animations: "disabled" }); + coverageEntries.push(...await session.close()); + } catch (error) { + await session.close().catch(() => {}); + throw error; + } + + // Negative: an unhealthy source with zero Goals keeps its banner and does + // not present the first-Goal invitation. + const unhealthy = await openWorkspacePage(browser, url, { collectCoverage, viewport: { width: 1440, height: 900 }, + beforeGoto: (api) => { api.transformStatus = (fixture) => emptyWorkspace(fixture, { healthy: false }); } }); + try { + const banner = unhealthy.page.locator(".personal-system-health-banner"); + await banner.waitFor({ state: "visible", timeout: 15_000 }); + const bannerText = await banner.innerText(); + assert.match(bannerText, /控制面契约检查未通过:1 项错误,0 项警告/, "Health issues read as owner language"); + assert.doesNotMatch(bannerText, /payload\.ok/, "No protocol fragment in primary copy"); + assert.equal(await banner.locator("details.personal-system-health-diagnostics").getAttribute("open"), null, "Source lines stay collapsed"); + assert.match((await banner.locator("details li").first().textContent()) ?? "", /registry file is not valid JSON/); + assert.equal(await unhealthy.page.getByTestId("personal-first-goal").count(), 0, "An unhealthy source does not invite a first Goal"); + await unhealthy.page.screenshot({ path: resolve(outputDir, "first-goal-home-unhealthy.png"), animations: "disabled" }); + coverageEntries.push(...await unhealthy.close()); + } catch (error) { + await unhealthy.close().catch(() => {}); + throw error; + } + return { coverageEntries }; + }, +}; diff --git a/loopx/chat_status_api.py b/loopx/chat_status_api.py index 861eec2d0c..9f7dbb3276 100644 --- a/loopx/chat_status_api.py +++ b/loopx/chat_status_api.py @@ -5,6 +5,7 @@ import errno import json from datetime import datetime, timezone +from pathlib import Path from typing import Any from urllib.parse import parse_qs, urlparse @@ -73,6 +74,21 @@ def _goal_task_map(server: Any, goal_id: str) -> dict[str, Any] | None: ) +def _discovery_registry(registry_path: Path) -> dict[str, Any]: + """Read the registry for the workspace directory; a missing file is an empty workspace. + + A fresh install has no registry until the first Goal is created. Discovery + reports that as zero registered Goals rather than a projection failure, so + the home page can show its first-Goal state. Unreadable or incompatible + registries still raise. + """ + + try: + return load_project_registry(registry_path) + except FileNotFoundError: + return {} + + def _status_access_denied(error: BaseException) -> bool: """Recognize permission causes without reclassifying unrelated I/O failures.""" current: BaseException | None = error @@ -158,7 +174,7 @@ def _status(self, *, delivery_review: bool = False) -> None: if views is not None or requested_goals is not None or delivery_review: # Discovery reads only metadata. Full status/runtime reads keep # their existing lifecycle-only profile refusal. - registry = (load_project_registry(self.server.registry_path) if views == ["workspace-directory"] + registry = (_discovery_registry(self.server.registry_path) if views == ["workspace-directory"] else load_registry(self.server.registry_path)) if goal_id and goal_id not in { str(g.get("id")) for g in registry_goals(registry) @@ -173,6 +189,10 @@ def _status(self, *, delivery_review: bool = False) -> None: return projection = collect_status( registry_path=self.server.registry_path, + # The Chat service already starts on an absent registry as an + # empty workspace (serve_chat); its status reports the same + # fact as a warning, so a fresh install is not a failed source. + allow_missing_registry=True, runtime_root_override=self.server.runtime_root_override, scan_roots=self.server.scan_roots, limit=self.server.limit, diff --git a/loopx/control_plane/status/collection.py b/loopx/control_plane/status/collection.py index e745db37ca..89df438f2d 100644 --- a/loopx/control_plane/status/collection.py +++ b/loopx/control_plane/status/collection.py @@ -85,6 +85,7 @@ def collect_status( include_zcode_goal_eligibility: bool = False, activation_state_filter: GoalActivationState | str | None = None, agent_lane_id: str | None = None, + allow_missing_registry: bool = False, ) -> dict[str, Any]: display_limit = max(0, limit) control_plane_limit = max( @@ -134,6 +135,7 @@ def collect_status( runtime_root_override=str(runtime_root), scan_roots=scan_roots, limit=limit, + allow_missing_registry=allow_missing_registry, goal_id_filter=goal_filter, include_public_boundary_scan=include_public_boundary_scan, activation_state_filter=activation_filter, diff --git a/loopx/status.py b/loopx/status.py index d5642c0e49..c505bbc574 100644 --- a/loopx/status.py +++ b/loopx/status.py @@ -1362,10 +1362,12 @@ def collect_status( include_zcode_goal_eligibility: bool = False, activation_state_filter: str | None = None, agent_lane_id: str | None = None, + allow_missing_registry: bool = False, ) -> dict[str, Any]: with effect_runtime_request_scope(): return _collect_status_read_model( registry_path=registry_path, + allow_missing_registry=allow_missing_registry, runtime_root_override=runtime_root_override, scan_roots=scan_roots, limit=limit, diff --git a/tests/test_chat_server_cors.py b/tests/test_chat_server_cors.py index 970c8a4ff8..9a13d17088 100644 --- a/tests/test_chat_server_cors.py +++ b/tests/test_chat_server_cors.py @@ -487,6 +487,43 @@ def test_chat_status_rejects_invalid_goal_activation_scope(monkeypatch) -> None: server.server_close() +def test_workspace_directory_reports_a_missing_registry_as_an_empty_workspace(tmp_path) -> None: + # A fresh install has no registry until the first Goal is created. The + # discovery read must answer with zero registered Goals, not a 500, so the + # home page can render its first-Goal state instead of a service failure. + server, thread = _start_server() + server.registry_path = tmp_path / ".loopx" / "registry.json" + try: + response = _request(server.server_address[1], method="GET", origin=None, path="/status.json?view=workspace-directory") + payload = json.loads(response.read().decode("utf-8")) + assert response.status == 200 + assert payload["ok"] is True + assert payload["schema_version"] == "loopx_workspace_directory_v1" + assert payload["goals"] == [] + finally: + server.shutdown() + thread.join(timeout=5) + server.server_close() + + +def test_workspace_directory_still_fails_on_an_unreadable_registry(tmp_path) -> None: + # Only absence is an empty workspace; a corrupt registry stays a failure. + server, thread = _start_server() + registry_path = tmp_path / ".loopx" / "registry.json" + registry_path.parent.mkdir(parents=True) + registry_path.write_text("{not json", encoding="utf-8") + server.registry_path = registry_path + try: + response = _request(server.server_address[1], method="GET", origin=None, path="/status.json?view=workspace-directory") + payload = json.loads(response.read().decode("utf-8")) + assert response.status == 500 + assert payload["ok"] is False + finally: + server.shutdown() + thread.join(timeout=5) + server.server_close() + + def test_workspace_directory_skips_expensive_projection_and_respects_scope(monkeypatch) -> None: registry = {"goals": [{"id": "alpha", "display_name": "Alpha"}, {"id": "beta"}]} monkeypatch.setattr("loopx.chat_status_api.load_project_registry", lambda _: registry) @@ -631,6 +668,7 @@ def fail(*_args, **_kwargs): raise PermissionError("synthetic confidential diagnostic") monkeypatch.setattr("loopx.chat_status_api.load_registry", fail) + monkeypatch.setattr("loopx.chat_status_api.load_project_registry", fail) monkeypatch.setattr("loopx.chat_status_api.collect_status", fail) server, thread = _start_server() try: diff --git a/tests/test_status_missing_registry.py b/tests/test_status_missing_registry.py new file mode 100644 index 0000000000..937bb9a1d0 --- /dev/null +++ b/tests/test_status_missing_registry.py @@ -0,0 +1,35 @@ +"""A missing registry is an empty workspace only where the caller says so.""" + +from __future__ import annotations + +from pathlib import Path + +from loopx.status import collect_status + + +def _collect(tmp_path: Path, **overrides): + return collect_status( + registry_path=tmp_path / ".loopx" / "registry.json", + runtime_root_override=str(tmp_path / "runtime"), + scan_roots=[], + limit=5, + include_public_boundary_scan=False, + **overrides, + ) + + +def test_missing_registry_is_a_contract_error_by_default(tmp_path: Path) -> None: + payload = _collect(tmp_path) + assert payload["ok"] is False + assert payload["goal_count"] == 0 + assert "registry file does not exist" in payload["contract"]["errors"] + + +def test_missing_registry_is_a_warning_when_the_caller_allows_it(tmp_path: Path) -> None: + payload = _collect(tmp_path, allow_missing_registry=True) + assert payload["ok"] is True, payload["contract"] + assert payload["contract"]["ok"] is True + assert payload["contract"]["errors"] == [] + assert any(line.startswith("registry file does not exist") for line in payload["contract"]["warnings"]) + assert payload["goal_count"] == 0 + assert payload["run_history"]["goals"] == [] From 27ec33051b0b609a47f6e3ec225671fd978acc76 Mon Sep 17 00:00:00 2001 From: song Date: Thu, 15 Oct 2026 21:10:21 +0800 Subject: [PATCH 2/4] docs(workspace): illustrate the first-Goal home Add the synthetic-fixture screenshot the first-goal-home browser scenario produces, so the user guide's first-open section shows the composition. Co-Authored-By: Claude Fable 5.1 Signed-off-by: song --- .../first-goal-home-desktop.png | Bin 0 -> 111794 bytes docs/guides/personal-workspace-user-guide.md | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 docs/assets/personal-workspace/first-goal-home-desktop.png diff --git a/docs/assets/personal-workspace/first-goal-home-desktop.png b/docs/assets/personal-workspace/first-goal-home-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..8a159bffc9bc32acd436af6c543a8b7190ade93b GIT binary patch literal 111794 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 z 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 L*SlY#Y8UZ8{~97l literal 0 HcmV?d00001 diff --git a/docs/guides/personal-workspace-user-guide.md b/docs/guides/personal-workspace-user-guide.md index 80ae06e3b5..f0f95c9fbf 100644 --- a/docs/guides/personal-workspace-user-guide.md +++ b/docs/guides/personal-workspace-user-guide.md @@ -71,6 +71,8 @@ graph TD ### 首次打开 / First open +![还没有 Goal 时的管家总览](../assets/personal-workspace/first-goal-home-desktop.png) + 还没有任何 Goal 时,总览不显示空泳道或健康告警,而是一个「还没有 Goal」状态:一句话说明 Goal 是什么、一个 **创建 Goal** 按钮(打开与侧边栏 `+` 相同的创建表单),以及三条示例 请求。点选示例只会填入输入框,按发送前不会发出任何内容。状态来源自身的检查行(例如 From f22b3f4370a5cdfd6c7dc8e6d14fea57d483c6e8 Mon Sep 17 00:00:00 2001 From: song Date: Thu, 15 Oct 2026 21:43:11 +0800 Subject: [PATCH 3/4] test(workspace): count only Turn creation as sending from the first-Goal home CI shard 3 saw one POST to the sessions route after the example click and read it as a send. Session admission on composer focus is not a send; the assertion now counts only Turn creation and steering, and names any request it did see. Co-Authored-By: Claude Fable 5.1 Signed-off-by: song --- examples/personal-workspace-browser/first-goal-home.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/personal-workspace-browser/first-goal-home.mjs b/examples/personal-workspace-browser/first-goal-home.mjs index a15f23fdb7..b6ddfb2954 100644 --- a/examples/personal-workspace-browser/first-goal-home.mjs +++ b/examples/personal-workspace-browser/first-goal-home.mjs @@ -39,9 +39,11 @@ export const firstGoalHomeScenario = { const session = await openWorkspacePage(browser, url, { collectCoverage, viewport: { width: 1440, height: 900 }, beforeGoto: (api) => { api.transformStatus = (fixture) => emptyWorkspace(fixture); } }); const { page } = session; + // "Sends nothing" means no Turn is created. Session admission on focus is + // not a send, so only turn creation and steering count. const turnRequests = []; page.on("request", (request) => { - if (request.method() === "POST" && /\/api\/chat\/sessions/.test(request.url())) turnRequests.push(request.url()); + if (request.method() === "POST" && /\/api\/chat\/sessions\/[^/]+\/turns/.test(request.url())) turnRequests.push(request.url()); }); try { const firstGoal = page.getByTestId("personal-first-goal"); @@ -62,7 +64,7 @@ export const firstGoalHomeScenario = { const exampleText = (await example.innerText()).trim(); await example.click(); assert.equal((await composer.inputValue()).trim(), exampleText, "The example fills the composer verbatim"); - assert.equal(turnRequests.length, 0, "Selecting an example sends nothing"); + assert.equal(turnRequests.length, 0, `Selecting an example sends nothing: ${turnRequests.join(", ")}`); await page.keyboard.press("Escape"); // Create a Goal opens the existing typed Goal form in place. From df7a8876e33a7894e51c784ebcb21e50290737da Mon Sep 17 00:00:00 2001 From: song Date: Thu, 15 Oct 2026 22:10:29 +0800 Subject: [PATCH 4/4] fix(workspace): withdraw the first-Goal state when the source's latest read fails Review on the first-Goal home found that a warm refresh returning HTTP 500 for both status reads kept the retained empty payload on screen: the page still invited the owner to create a Goal and the header briefly reported "Updated just now". A payload from the last successful read is not a current fact once the source cannot be read. The refresh callback now reports its outcome instead of swallowing the error: the dashboard reads with `rejectOnError` and returns `{ ok, error }`, the page shows "Refresh failed" on a failed result and never advances to "Updated", and the retained load error reaches the home read model as `sourceError`. With it set, the home withdraws the first-Goal state and shows a retryable failure in owner language ("the status source could not be read; what is shown is the last successful read") with the raw diagnostics still collapsed and a Retry that re-reads the source. The browser scenario now covers the path the review asked for: a successful empty read, then both status routes answering HTTP 500 on refresh (invitation withdrawn, failure and retry visible, header reports the failure and no update), then the source restored and Retry bringing the first-Goal state back with "Updated just now". Co-Authored-By: Claude Fable 5.1 Signed-off-by: song --- .../src/features/personal-workspace/i18n.tsx | 6 +++ .../personal-workspace-contract.test.mjs | 3 ++ .../personal-workspace-model.ts | 7 +++- .../personal-workspace-page.tsx | 18 +++++++-- .../personal-workspace/personal-workspace.css | 2 + .../dashboard/src/views/dashboard-page.tsx | 38 +++++++++++++++++-- .../first-goal-home.mjs | 26 ++++++++++++- .../personal-workspace-browser/fixture.mjs | 6 +++ 8 files changed, 96 insertions(+), 10 deletions(-) diff --git a/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx b/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx index c64c5c9710..e3f6022e76 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx @@ -1412,6 +1412,9 @@ const en = { "home.health.contractFailedUnknown": "Control-plane contract checks failed.", "home.health.registryHigh": "Registry checks found {count} high-severity finding(s).", "home.health.diagnostics": "Diagnostics", + "home.health.sourceUnreadable": "The status source could not be read. What is shown is the last successful read.", + "home.health.sourceUnreadableDetail": "Latest read failed: {error}", + "home.health.retry": "Retry", "home.health.allClear": "All control-plane contract and registry checks passed.", "home.health.checked": "Status checks completed.", "home.health.concerns": "{count} concern(s) found.", @@ -2915,6 +2918,9 @@ const zhCN: Record = { "home.health.contractFailedUnknown": "控制面契约检查未通过。", "home.health.registryHigh": "注册表检查发现 {count} 项高危问题。", "home.health.diagnostics": "诊断信息", + "home.health.sourceUnreadable": "状态来源读取失败,当前显示的是上一次成功读取的内容。", + "home.health.sourceUnreadableDetail": "最近一次读取失败:{error}", + "home.health.retry": "重试", "home.health.allClear": "所有控制面契约与注册表检查均正常。", "home.health.checked": "状态检查已完成。", "home.health.concerns": "发现 {count} 项需要关注。", diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs index 6c39dd13be..2efa09d6c0 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs @@ -589,6 +589,9 @@ assert.match( // state, not empty lanes, and health issues are owner-language sentences. assert.match(model, /kind: "no_goals"/, "The home carries a typed first-Goal classification"); assert.match(dashboard, /const onboarding: WorkspaceHomeOnboarding \| undefined = isHealthy/, "Only a healthy source classifies as a first-Goal workspace"); +assert.match(dashboard, /if \(!sourceError\) return loadedModel;[\s\S]*?onboarding: undefined,[\s\S]*?retryable: true,/, "A failed current read withdraws the first-Goal state and shows a retryable failure"); +assert.match(dashboard, /onRefresh=\{async \(readScope = "all"\) => \{[\s\S]*?rejectOnError: true[\s\S]*?return \{ ok: false, error: formatStatusError\(error\) \}/, "A refresh reports its failure to the page instead of swallowing it"); +assert.match(page, /if \(result && result\.ok === false\) \{\n\s*setRefreshState\("error"\);/, "The header reports a failed refresh and never an update"); assert.doesNotMatch(dashboard, /payload\.ok === false|契约检查未通过|系统健康关注点/, "Health issues are localized owner language, not protocol fragments"); assert.match(page, /data-testid="personal-first-goal"/, "The home renders the first-Goal state"); assert.match(page, /onSuggestRequest\(t\(`home\.firstGoal\.example\./, "Example requests only fill the composer"); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-model.ts b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-model.ts index efd2a63df9..5ceca40824 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-model.ts +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-model.ts @@ -439,9 +439,14 @@ export type WorkspaceSystemHealth = { freshnessWarning?: string | null; issues: string[]; ok: boolean; + /** The current read of the source failed; a retry can clear it. */ + retryable?: boolean; summary: string; }; +/** Outcome of a workspace refresh; a failed read never rejects, it reports. */ +export type WorkspaceRefreshResult = { ok: true } | { ok: false; error: string }; + /** * Typed first-Goal classification of the manager home. `no_goals` means the * status source reports zero registered Goals and no execution history, so @@ -561,7 +566,7 @@ export type PersonalWorkspaceCallbacks = { * names the Goals the action touched, so a peer's snapshot is not re-read for it. */ onReconcileStatus?: (options?: { invalidateGoalIds?: string[] }) => void | Promise; /** Full refresh by default; error recovery can explicitly read missing Goals only. */ - onRefresh?: (scope?: WorkspaceReadScope) => void | Promise; + onRefresh?: (scope?: WorkspaceReadScope) => void | WorkspaceRefreshResult | Promise; onRetryGoalArchive?: () => void | Promise; onPreviewAction?: (request: WorkspaceActionPreviewRequest) => WorkspaceActionPreview | Promise; onRequestGoalCreate?: () => WorkspaceActionPreview | Promise | void; diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx index 2f802b542e..dd6b7d4dcd 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx @@ -121,6 +121,7 @@ function ManagerHomeBoard({ goals, onSelectGoal, onRetry, + onRetrySource, onboarding, onRequestGoalCreate, onSuggestRequest, @@ -132,6 +133,8 @@ function ManagerHomeBoard({ goals: WorkspaceGoal[]; onSelectGoal: (goalId: string) => void; onRetry?: () => void; + /** Re-read the whole source after its latest read failed. */ + onRetrySource?: () => void; onboarding?: WorkspaceHomeOnboarding; onRequestGoalCreate?: () => void; onSuggestRequest?: (text: string) => void; @@ -189,6 +192,9 @@ function ManagerHomeBoard({
    {systemHealth.diagnostics.map((line, idx) =>
  • {line}
  • )}
) : null} + {systemHealth.retryable && onRetrySource ? ( + + ) : null}
) : null} {onboarding?.kind === "no_goals" && goals.length === 0 ? ( @@ -2050,9 +2056,13 @@ export function PersonalWorkspacePage({ } setRefreshState("loading"); try { - await callbacks.onRefresh(); - setHistoryRefreshRevision((revision) => revision + 1); - setRefreshState("done"); + const result = await callbacks.onRefresh(); + if (result && result.ok === false) { + setRefreshState("error"); + } else { + setHistoryRefreshRevision((revision) => revision + 1); + setRefreshState("done"); + } } catch { setRefreshState("error"); } @@ -2293,7 +2303,7 @@ export function PersonalWorkspacePage({ ), }} /> ) : !managerChatOpen ? ( - void callbacks.onRefresh?.("missing")} onSelectGoal={selectGoal} systemHealth={model.systemHealth} + void callbacks.onRefresh?.("missing")} onRetrySource={() => void refreshWorkspace()} onSelectGoal={selectGoal} systemHealth={model.systemHealth} onboarding={model.onboarding} onRequestGoalCreate={readOnly ? undefined : requestGoalCreate} onSuggestRequest={readOnly ? undefined : suggestReply} operations={actionReadback.isError ? [] : homeOperations} onSelectOperation={proposal => setSelection({kind: "proposal", item: proposal})} onViewAllOperations={() => setSelectedGoalTab("chat")} /> diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace.css b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace.css index 36a432e526..d4172394a9 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace.css +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace.css @@ -327,6 +327,8 @@ button.personal-execution-chip:focus-visible { outline: 2px solid #0070f3; outli .personal-first-goal-diagnostics { color: var(--pw-muted); font-size: var(--text-caption); line-height: var(--leading-caption); } .personal-first-goal-diagnostics summary { cursor: pointer; } .personal-first-goal-diagnostics ul { margin: 6px 0 0 16px; padding: 0; } +.personal-system-health-retry { margin-top: 10px; min-height: 36px; padding: 0 14px; border: 1px solid var(--pw-red); border-radius: 8px; background: var(--pw-card); color: var(--pw-red); font-size: var(--text-caption); font-weight: 600; cursor: pointer; } +.personal-system-health-retry:focus-visible { outline: 2px solid var(--pw-red); outline-offset: 2px; } .personal-system-health-diagnostics { margin-top: 6px; font-size: var(--text-caption); line-height: var(--leading-caption); } .personal-system-health-diagnostics summary { cursor: pointer; } .personal-system-health-diagnostics ul { margin: 4px 0 0 16px; padding: 0; } diff --git a/apps/presentation/dashboard/src/views/dashboard-page.tsx b/apps/presentation/dashboard/src/views/dashboard-page.tsx index e4d3c91baa..7142d99c58 100644 --- a/apps/presentation/dashboard/src/views/dashboard-page.tsx +++ b/apps/presentation/dashboard/src/views/dashboard-page.tsx @@ -109,6 +109,7 @@ import { type WorkspaceGoalUsage, type WorkspaceImageAttachment, type WorkspaceHomeOnboarding, + type WorkspaceRefreshResult, type WorkspaceSystemHealth, type WorkspaceTimelineItem, type WorkspaceWorker, @@ -1187,6 +1188,7 @@ function buildPersonalHomeModel( } function PersonalGoalHome({ goalArchiveLoadState, + sourceError, selectedView, selectedTodoId, onSelectTodo, @@ -1207,6 +1209,8 @@ function PersonalGoalHome({ theme, }: { goalArchiveLoadState: WorkspaceGoalArchiveLoadState; + /** The current source's latest read failed; the retained payload is stale. */ + sourceError: string | null; selectedView?: WorkspaceGoalTab; selectedTodoId?: string | null; onSelectTodo: (todoId: string | null) => void; @@ -1217,7 +1221,7 @@ function PersonalGoalHome({ onSelectWorkspace: (projectRef: string | null) => void; selectedWorkspaceRef: string | null; onReconcileStatus: (options?: { invalidateGoalIds?: string[] }) => void | Promise; - onRefresh: (scope?: WorkspaceReadScope) => void | Promise; + onRefresh: (scope?: WorkspaceReadScope) => Promise; onRetryGoalArchive: () => void | Promise; payload: StatusPayload; progress: WorkspaceProgress | null; @@ -1248,7 +1252,7 @@ function PersonalGoalHome({ const [managerRuntime, setManagerRuntime] = useState(null); const [managerChannelBinding, setManagerChannelBinding] = useState(null); const [capabilityRevision, setCapabilityRevision] = useState(0); - const model = useMemo(() => { + const loadedModel = useMemo(() => { const base = buildPersonalHomeModel(payload, rows, t, goalSubagentConfigurationEnabled); if (!progress) return base; const models = Object.values(progress.snapshots).map((snapshot) => buildPersonalHomeModel( @@ -1278,6 +1282,23 @@ function PersonalGoalHome({ }, }; }, [payload, rows, progress, goalSubagentConfigurationEnabled, t]); + // A retained payload is not a current fact once the source's latest read + // failed: the first-Goal invitation is withdrawn and the failure is shown + // with a retry, instead of an empty workspace that claims to be updated. + const model = useMemo(() => { + if (!sourceError) return loadedModel; + return { + ...loadedModel, + onboarding: undefined, + systemHealth: { + ok: false, retryable: true, + summary: t("home.health.sourceUnreadable"), + issues: [t("home.health.sourceUnreadableDetail", { error: sourceError })], + freshnessWarning: null, + diagnostics: loadedModel.systemHealth?.diagnostics ?? [], + }, + }; + }, [sourceError, loadedModel, t]); // A workspace conversation has no Goal, so no Goal projection may apply to it. const selectedGoal = selectedWorkspaceRef ? null : model.goals.find((goal) => goal.goalId === selectedGoalId) ?? null; const workspaceProjects = useChatProjects(readOnly); @@ -2945,8 +2966,9 @@ function PersonalGoalHome({ URL.revokeObjectURL(url); }, onRefresh: async (scope) => { - await onRefresh(scope); + const result = await onRefresh(scope); setCapabilityRevision((revision) => revision + 1); + return result; }, onRetryResumeRun: retryManagerSession, onSelectAgent: chooseAgent, @@ -3463,6 +3485,7 @@ export function DashboardPage() { return ( { const goalId = search.goalId, statusUrl = search.statusUrl; @@ -3494,7 +3517,14 @@ export function DashboardPage() { { rejectOnError: true, background: true, invalidateGoalIds: options?.invalidateGoalIds, readScope: "missing" }, )} onRetryGoalArchive={retryGoalArchive} - onRefresh={(readScope = "all") => loadFromUrl(source.kind === "url" ? source.label : (statusUrl || defaultGlobalStatusUrl), { readScope })} + onRefresh={async (readScope = "all") => { + try { + await loadFromUrl(source.kind === "url" ? source.label : (statusUrl || defaultGlobalStatusUrl), { readScope, rejectOnError: true }); + return { ok: true } as const; + } catch (error) { + return { ok: false, error: formatStatusError(error) } as const; + } + }} payload={payload} progress={progress} rows={goalRows} diff --git a/examples/personal-workspace-browser/first-goal-home.mjs b/examples/personal-workspace-browser/first-goal-home.mjs index b6ddfb2954..4d621688c6 100644 --- a/examples/personal-workspace-browser/first-goal-home.mjs +++ b/examples/personal-workspace-browser/first-goal-home.mjs @@ -38,7 +38,7 @@ export const firstGoalHomeScenario = { const coverageEntries = []; const session = await openWorkspacePage(browser, url, { collectCoverage, viewport: { width: 1440, height: 900 }, beforeGoto: (api) => { api.transformStatus = (fixture) => emptyWorkspace(fixture); } }); - const { page } = session; + const { api, page } = session; // "Sends nothing" means no Turn is created. Session admission on focus is // not a send, so only turn creation and steering count. const turnRequests = []; @@ -75,6 +75,30 @@ export const firstGoalHomeScenario = { await page.keyboard.press("Escape"); await form.waitFor({ state: "hidden", timeout: 10_000 }); + // A warm refresh that fails is not an empty workspace: both status reads + // answer HTTP 500, the invitation is withdrawn, the failure is shown with + // a retry, and the header never reports an update. Repairing the source + // and retrying restores the first-Goal state. + api.statusFailure = 500; + const refresh = page.getByRole("button", { name: "刷新状态", exact: true }); + await refresh.click(); + const sourceRetry = page.getByTestId("personal-source-retry"); + await sourceRetry.waitFor({ state: "visible", timeout: 15_000 }); + assert.equal(await firstGoal.count(), 0, "A failed read withdraws the first-Goal invitation"); + const failedBanner = page.locator(".personal-system-health-banner"); + assert.match(await failedBanner.innerText(), /状态来源读取失败/, "The failure is shown in owner language"); + assert.match(await failedBanner.innerText(), /最近一次读取失败:/); + const headerStatus = page.locator(".personal-channel-title, header").first(); + await page.locator("small", { hasText: "刷新失败" }).first().waitFor({ state: "visible", timeout: 10_000 }); + assert.equal(await page.locator("small", { hasText: "刚刚更新" }).count(), 0, "A failed refresh never reports an update"); + void headerStatus; + await page.screenshot({ path: resolve(outputDir, "first-goal-home-source-failed.png"), animations: "disabled" }); + api.statusFailure = null; + await sourceRetry.click(); + await firstGoal.waitFor({ state: "visible", timeout: 15_000 }); + assert.equal(await page.locator(".personal-system-health-banner").count(), 0, "A repaired source clears the failure"); + await page.locator("small", { hasText: "刚刚更新" }).first().waitFor({ state: "visible", timeout: 10_000 }); + // English copy and the phone composition. await page.evaluate(() => localStorage.setItem("loopx-pw-locale", "en")); await page.reload({ waitUntil: "networkidle" }); diff --git a/examples/personal-workspace-browser/fixture.mjs b/examples/personal-workspace-browser/fixture.mjs index 4968b5416e..117aa6a8b8 100644 --- a/examples/personal-workspace-browser/fixture.mjs +++ b/examples/personal-workspace-browser/fixture.mjs @@ -446,6 +446,8 @@ export async function installApi(page, { fixturePort = port, goalSubagentConfigu failNextLifecyclePreview: false, failNextActionPreview: false, failNextStatusRequest: false, + /** When set, every status read answers this HTTP status until cleared: a source that cannot be projected. */ + statusFailure: null, freezeGoalSubagentStatusProjection: false, goalSubagentConfigurationEnabled, goalActivationStates: new Map([ @@ -503,6 +505,10 @@ export async function installApi(page, { fixturePort = port, goalSubagentConfigu }; await page.route(`http://127.0.0.1:${fixturePort}/status.json*`, async (route) => { state.statusRequestCount += 1; + if (state.statusFailure) { + await route.fulfill({ contentType: "application/json", json: { ok: false, error: "LoopX status could not be projected for the workspace." }, status: state.statusFailure }); + return; + } const requestUrl = new URL(route.request().url()); const requestedGoalId = requestUrl.searchParams.get("goal_id"); if (progressiveWorkspace && requestUrl.searchParams.get("view") === "workspace-directory") {