diff --git a/.project-docs/30-worklog/tasks/20261014-work-entry-restore-91c7e6a2.md b/.project-docs/30-worklog/tasks/20261014-work-entry-restore-91c7e6a2.md index 10579e50..325d6488 100644 --- a/.project-docs/30-worklog/tasks/20261014-work-entry-restore-91c7e6a2.md +++ b/.project-docs/30-worklog/tasks/20261014-work-entry-restore-91c7e6a2.md @@ -27,7 +27,7 @@ - Implemented project work-entry persistence, deterministic Vite/static startup, content/project matching before address reuse, bounded legacy browser-receipt recovery without Pi startup, and explicit assistant opt-in with domain-specific error copy. - Preserve manual opens across slow health probes/local startup; serialize project checks and metadata writes; retain successful newly started server addresses when project selection changes; stop only unused app-owned children superseded by another server. -- Imported only already-activated product/README deltas from sidebar source commits 8cd7b597 and 14fcea389, preserving the currently effective lower-footer placement. Peer source records stay read-only. This is preservation of the latest trial baseline, not an additional sidebar redesign. +- Imported only currently activated product deltas from sidebar sources 8cd7b597/14fcea389/cca23df7 and the later active Renderer d0451e9a, preserving footer placement, WeChat status copy and default expanded module sidebar. A three-line ModuleSelection edit appeared concurrently in this owned checkout; its bytes were independently confirmed identical to the current activated d0451e9a source before inclusion, and were never reset/stashed or guessed. Peer source records stay read-only. This is preservation of the latest trial baseline, not an additional sidebar redesign. - Exact production build and isolated real Electron full-restart/stopped-service/native-render acceptance passed. Current temporary-app activation and actual Alice acceptance pending. ## Verification @@ -35,7 +35,7 @@ - 134 focused tests in 6 files passed after root integration; includes real Vite/static launch, resolved base configuration, fresh store reload, imported-folder metadata creation, content/port ownership, explicit helper selection, project switching and slow manual supersession. - Standard typecheck, changed-file ESLint, git diff --check passed. Independent review reproduced races and a Vite base issue before correction; final root tests cover those corrections. - Final exact production build from product commit 0f26b6d passed. Isolated Electron acceptance 1/1 passed: real Main quit/relaunch, persisted URL reuse, controlled original service stop, real app-owned static restart and native HTML rendering, with unchanged conversation list and no model session. Fixture guest account uses actual native presentation route rather than fabricating cloud login; actual guarded Code UI is verified on the existing trial below. -- Full normal unit suite: 297 files passed, 3333 tests passed/3 skipped; 8 failures in 3 unrelated existing setup/cloud-knowledge/design-image-preview files. Exact previous-renderer baseline comparison is running to establish inherited failures; pressure suite runs separately because the normal script stops at failed first phase. +- Full normal unit suite: 297 files passed, 3333 tests passed/3 skipped; 8 failures in 3 unrelated existing setup/cloud-knowledge/design-image-preview files. Exact previous-renderer baseline 14fcea389 reproduced all same 8 failures; none was introduced by this task. Separate pressure test passed 1/1 because the normal script stops at failed first phase. - Early isolated Electron attempts exposed imported metadata creation and fixture activity/viewport setup gaps, which were corrected. No real account, model, game source or cloud state was mutated by tests. ## Follow-ups diff --git a/src/pages/CloudAgents/ChannelAccounts.tsx b/src/pages/CloudAgents/ChannelAccounts.tsx index 70091acf..42480f58 100644 --- a/src/pages/CloudAgents/ChannelAccounts.tsx +++ b/src/pages/CloudAgents/ChannelAccounts.tsx @@ -65,9 +65,18 @@ const qrLabels: Record = { const blockerText: Record = { target_unavailable: '当前智能体暂时不能使用,可以更换一个智能体。', publish_required: '请先让智能体开始使用。', - worker_offline: '微信连接暂时不可用,请稍后刷新。', - channel_sync_degraded: '正在确认微信的使用状态。', + worker_offline: '智能体的微信服务暂时离线,暂时无法自动回复。', + channel_sync_degraded: '微信使用状态尚未同步,请刷新状态后再继续。', }; +function blockerMessage(blocker: string, account: CloudUserChannelAccount) { + const text = blockerText[blocker]; + return blocker === 'worker_offline' && + account.health === 'connected' && + account.target_agent_slug && + !account.blockers.includes('wechat_disconnect_pending') + ? text + '微信登录和智能体选择已保留,服务恢复后请刷新状态。' + : text; +} const unsettled = (account: CloudUserChannelAccount) => account.blockers.includes('wechat_disconnect_pending') || Boolean(account.sync_state && account.sync_state !== 'ready') || @@ -75,17 +84,28 @@ const unsettled = (account: CloudUserChannelAccount) => (['paused', 'disabled'].includes(account.desired_state ?? '') && account.enabled) || (account.desired_state === 'enabled' && !account.enabled); function accountState(account: CloudUserChannelAccount, uncertain: boolean, targetDisabled = false) { - if (uncertain || unsettled(account)) return { text: '正在确认', tone: 'bg-[#fff0d8] text-[#876033]' }; + if (uncertain || unsettled(account)) return { text: '状态待确认', tone: 'bg-[#fff0d8] text-[#876033]' }; if (!account.target_agent_slug) return { text: '未绑定', tone: 'bg-[#f0edf8] text-[#75618e]' }; if (['login_required', 'verification_required'].includes(account.health)) return { text: '需要重新扫码', tone: 'bg-[#fff0d8] text-[#876033]' }; if (targetDisabled) return { text: '智能体已停用', tone: 'bg-[#fff0d8] text-[#876033]' }; + if (account.health === 'connected' && account.blockers.includes('worker_offline')) + return { text: '微信服务离线', tone: 'bg-[#fff0d8] text-[#876033]' }; if (account.health !== 'connected' || unsettled(account) || usableBlockers(account).length) - return { text: '正在确认', tone: 'bg-[#fff0d8] text-[#876033]' }; + return { text: '状态待确认', tone: 'bg-[#fff0d8] text-[#876033]' }; return account.enabled ? { text: '使用中', tone: 'bg-[#e7f4ec] text-[#3f7458]' } : { text: '已暂停', tone: 'bg-[#f0edf8] text-[#75618e]' }; } +function bindingReadinessMessage(account: CloudUserChannelAccount) { + if (unsettled(account)) return '微信使用状态尚未确认,请刷新状态后再继续。'; + if (account.health !== 'connected') + return '微信登录状态尚未确认,请先完成微信登录,再检查连接。'; + const blocker = usableBlockers(account).find((item) => blockerText[item]); + return blocker + ? blockerMessage(blocker, account) + : '暂时无法完成微信绑定,请检查连接后再继续。'; +} async function loadAgents(): Promise { const items: CloudAgentDraft[] = []; let cursor: string | null = null; @@ -347,7 +367,7 @@ export function ChannelAccounts() { if (!account || account.target_agent_slug !== target) throw new Error('绑定的智能体已发生变化,请返回上一步确认。'); if (account.health !== 'connected' || unsettled(account) || usableBlockers(account).length) - throw new Error('微信还没有准备好,请稍后重试完成绑定。'); + throw new Error(bindingReadinessMessage(account)); if (ownerPending.current) throw new Error('微信退出还在确认中,请先处理原来的操作。'); if (agentsRef.current.some((agent) => agent.slug === target && agent.enabled === false)) throw new Error('当前智能体已停用,请更换智能体后再使用。'); @@ -361,7 +381,7 @@ export function ChannelAccounts() { account = await verifyFinal(id, { target, enabled: true }); } if (account.health !== 'connected' || unsettled(account) || usableBlockers(account).length) - throw new Error('微信还没有准备好,请稍后重试完成绑定。'); + throw new Error(bindingReadinessMessage(account)); intents.current.delete('enable'); return account; }); @@ -785,7 +805,7 @@ export function ChannelAccounts() { .filter((item) => blockerText[item]) .map((item) => (

- {blockerText[item]} + {blockerMessage(item, account)}

))}
@@ -990,10 +1010,16 @@ export function ChannelAccounts() { {qr?.status === 'confirmed' || selected?.health === 'connected' ? (
-

{busy ? '正在完成微信绑定…' : '微信登录已确认'}

+

+ {busy + ? '正在检查连接并完成绑定…' + : selected?.health === 'connected' + ? '微信登录已确认' + : '扫码已确认'} +

{connectionFailed - ? '还没有完成绑定,请查看上方提示。' + ? '绑定尚未完成,请按上方提示处理,再检查连接。' : '正在确认智能体和自动回复状态。'}

@@ -1052,7 +1078,7 @@ export function ChannelAccounts() { disabled={disabled || checkingIds.includes(flow.accountId)} onClick={() => void finishBinding(flow.accountId, flow.target)} > - 重试完成绑定 + 检查连接并完成绑定 )} {checkingIds.includes(flow.accountId) && ( diff --git a/src/pages/ModuleSelection/index.tsx b/src/pages/ModuleSelection/index.tsx index 1efc7209..6ac9fd46 100644 --- a/src/pages/ModuleSelection/index.tsx +++ b/src/pages/ModuleSelection/index.tsx @@ -12,6 +12,7 @@ import { aiModules, isAiModuleAllowed, type AiModuleId } from '@/lib/ai-modules' import { getAuthUserDisplayName } from '@/lib/auth-user-display'; import { cn } from '@/lib/utils'; import { useAuthStore } from '@/stores/auth'; +import { useSettingsStore } from '@/stores/settings'; import { getUserProfileDisplayName } from '@/stores/user-profile'; const moduleSelectionContent: Record = { @@ -23,6 +24,7 @@ const moduleSelectionContent: Record state.setSidebarCollapsed); const authUser = useAuthStore((state) => state.user); const moduleAccess = useAuthStore((state) => state.moduleAccess); const authenticated = useAuthStore((state) => state.isAuthenticated()); @@ -46,6 +48,7 @@ export function ModuleSelection({ authRequired = true }: { authRequired?: boolea navigate('/login'); return; } + if (useSettingsStore.getState().sidebarCollapsed) setSidebarCollapsed(false); navigate(route === '/cloud-agents' ? `${route}?view=mine` : route); };