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 @@ -209,9 +209,9 @@ export function GoalTasksView({
<section aria-label={t("tasks.chatRecent")} className="personal-task-chat-receipt">
<span className="personal-task-chat-icon"><MessageSquareText size={18} /></span>
<div>
<header><strong>{replyPending ? t("tasks.chatPending") : latestReply ? t("tasks.chatAgentReplied") : t("tasks.chatRecent")}</strong><small>{goal.agentLabel ?? goal.agentId}</small></header>
<header><strong>{replyPending ? t("tasks.chatPending") : latestReply ? t("tasks.chatAgentReplied") : t("tasks.chatRecent")}</strong><small>{latestReply?.returnDelivery ? t("tasks.chatReturn") : latestReply?.agentLabel}</small></header>
<p className="is-user"><b>{t("common.you")}</b>{latestUserMessage.text}</p>
{latestReply && !latestReply.pending ? <p className="is-assistant"><b>{t("common.agent")}</b>{latestReply.text}</p> : null}
{latestReply && !latestReply.pending ? <p className="is-assistant"><b>{latestReply.returnDelivery ? t("tasks.chatReturn") : latestReply.agentLabel ?? t("common.agent")}</b>{latestReply.text}</p> : null}
<small>{replyPending
? t("tasks.chatPendingDescription")
: t("tasks.chatUnchangedDescription")}</small>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const en = {
"composer.globalTasks": "Ask about global priorities",
"composer.globalTasksPrompt": "Which Goals need me, and what should I handle first?",
"composer.goalMessageHint": "Your message is delivered to {agent} in this Goal session.",
"composer.goalRunningHint": "{agent} is running {count} tasks · your message enters this session as guidance without interrupting it",
"composer.goalRunningHint": "{count} conversations are running in this Goal · your message goes to the current {agent} conversation",
"composer.goalPlaceholder": "Ask or guide {goal}…",
"composer.imageAnalysisPrompt": "Analyze these images in the context of the current Goal and tell me the next step.",
"composer.imageCountError": "You can add up to {count} images.",
Expand Down Expand Up @@ -1017,8 +1017,9 @@ const en = {
"tasks.agentLaneDescription": "Filter this Goal's projected work lanes",
"tasks.agentLaneFilter": "Filter by work Agent",
"tasks.allAgentLanes": "All Agents ({count})",
"tasks.chatAgentReplied": "Agent replied",
"tasks.chatPending": "Sent to Agent",
"tasks.chatAgentReplied": "New conversation reply",
"tasks.chatPending": "Conversation in progress",
"tasks.chatReturn": "Collaboration receipt",
"tasks.chatPendingDescription": "Processing. Tasks update only after a task operation is confirmed.",
"tasks.chatRecent": "Recent conversation",
"tasks.chatUnchangedDescription": "This conversation did not directly change Tasks. Convert the reply to a Task draft and confirm it when execution is needed.",
Expand Down Expand Up @@ -1142,7 +1143,7 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"composer.globalTasks": "询问全局待办",
"composer.globalTasksPrompt": "有哪些 Goal 正在等我?我现在该优先处理什么?",
"composer.goalMessageHint": "你的消息由 {agent} 在本 Goal 的会话中接收",
"composer.goalRunningHint": "{agent} 正在执行 {count} 个任务 · 你的消息作为纠偏进入本会话,不会打断执行",
"composer.goalRunningHint": "本项目有 {count} 段会话正在运行 · 消息发往当前 {agent} 对话",
"composer.goalPlaceholder": "询问或纠偏 {goal}…",
"composer.imageAnalysisPrompt": "请结合这些图片分析当前 Goal,并告诉我下一步。",
"composer.imageCountError": "最多添加 {count} 张图片。",
Expand Down Expand Up @@ -2076,8 +2077,9 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"tasks.agentLaneDescription": "筛选这个 Goal 下的工作泳道",
"tasks.agentLaneFilter": "按工作 Agent 筛选",
"tasks.allAgentLanes": "全部 Agent({count})",
"tasks.chatAgentReplied": "Agent 已回复",
"tasks.chatPending": "已发送给 Agent",
"tasks.chatAgentReplied": "对话有新回复",
"tasks.chatPending": "对话处理中",
"tasks.chatReturn": "协作回执",
"tasks.chatPendingDescription": "正在处理;只有经过确认的任务操作才会更新 Tasks。",
"tasks.chatRecent": "最近对话",
"tasks.chatUnchangedDescription": "本次对话没有直接修改 Tasks。需要执行时,可先转成 Task 草稿并确认。",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@
assert.match(tasks, /t\("tasks\.chatRecent"\)/, "Tasks surface the latest Goal conversation without forcing a tab switch");
assert.match(tasks, /t\("tasks\.chatUnchangedDescription"\)/, "Tasks explain that ordinary Chat does not silently mutate Todo state");
assert.doesNotMatch(tasks, /personal-task-capability-callout|tab: "capabilities"/, "Goal Tasks does not spend a full-width row on capability settings");
assert.match(header, /personal-goal-tools-trigger/, "Goal details and capability settings share one compact header entry");
assert.match(header, /onOpenGoalDetail[\s\S]*onOpenGoalCapabilities/, "The unified Goal entry preserves both existing details and capability settings");
assert.match(header, /aria-label=\{t\("header\.goalSettings"\)\}/, "The Goal settings entry has an accessible label");
assert.match(header, /onClick=\{onOpenGoalCapabilities\}/, "The Goal settings entry opens the existing capability owner directly");
assert.match(page, /onOpenGoalCapabilities=.*tab: "capabilities"/, "The unified Goal entry opens the selected Goal capability settings directly");
assert.match(goalCapabilitySettings, /fetchGoalConfiguration\(goalId\)/, "Goal capability settings inspect the selected Goal through the path-free API");
assert.match(capabilityWorkbench, /personal-capability-editor-status/, "Shared capability details distinguish editable contracts from read-only capabilities");
Expand Down Expand Up @@ -171,7 +171,8 @@
assert.match(page, /setComposerDraft\(`manager:\$\{selectedAgentId\}`,\s*t\("composer\.createGoalTemplate"\)\)/, "Create Goal writes the localized template to the manager draft even when invoked from a Goal");
assert.match(page, /personal-action-feedback/, "Typed actions surface a persistent visible receipt");
assert.match(page, /visibleTimelineItems[\s\S]*item\.run\.runId === activeSessionRun\.runId/, "Session record mode filters unrelated Goal activity");
assert.match(page, /if \(tab === "chat"\) setActiveSessionRun\(null\)/, "The top Chat view exits the nested Session record filter");
assert.match(page, /function openGoalConversation\(\)[\s\S]*setActiveSessionRun\(null\)[\s\S]*scrollIntoView\(\{ block: "start" \}\)/, "Opening a Goal reply exits a nested Session and reveals the latest answer");
assert.match(page, /onOpenChat=\{openGoalConversation\}/, "The task reply button uses the same conversation navigation as other Goal entries");
assert.match(header, /header\.refreshing[\s\S]*header\.refreshDone[\s\S]*header\.refreshFailed/, "Refresh exposes localized loading, success, and failure feedback");
assert.match(drawer, /t\("drawer\.proposalExplainer"\)/, "Preview explains what confirmation will do");
assert.match(drawer, /t\("drawer\.proposalApplyFailed"\)/, "Failed preview communicates its no-write result clearly");
Expand All @@ -190,15 +191,15 @@
assert.match(page, /field\.key === "cadence"[\s\S]*field\.key === "stop_condition"[\s\S]*field\.key === "timezone"/, "Applied Heartbeat readback consumes stable semantic keys");
assert.doesNotMatch(page, /field\.label === "cadence"|field\.label === "stop condition"/, "Schedule semantics never depend on localized display labels");
assert.match(page, /defaultTimeline\(model, managerProjectionId, t\)/, "Default schedule projection uses the active locale authority");
assert.match(page, /onOpenGoal: \(goalId\)[\s\S]*selectGoal\(goalId\);[\s\S]*Promise\.resolve\(\)\.then\(\(\) => reconcile\?\.\(\)\)/, "Applied-action Goal navigation is immediate and reconciles state in the background");
assert.match(page, /onOpenGoal: \(goalId\) => \{\s*selectGoal\(goalId\);\s*void reconcileStatus\(\[goalId\]\)/, "Applied-action Goal navigation is immediate and reconciles that Goal in the background");
assert.match(drawer, /onClose\(\); void callbacks\.onOpenGoal\?\.\(goalId\)/, "Applied-action Goal navigation closes its result drawer before asynchronous reconciliation");
assert.match(tasks, /aria-busy=\{quickCompletingTodoIds\?\.has\(todo\.todoId\)/, "Quick Todo completion exposes accessible pending state while its typed preview is prepared");
assert.match(tasks, /disabled=\{quickCompletingTodoIds\?\.has\(todo\.todoId\)\}/, "Quick Todo completion rejects duplicate clicks while preview creation is pending");
assert.match(page, /callbacks\.onGoalActivationStateChange\?\.\(lifecycleChange\.goalId, lifecycleChange\.next\)/, "Goal lifecycle apply projects the requested state before the server responds");
assert.match(page, /model\.goals\.find\(\(goal\) => goal\.goalId === proposal\.goalId\)\?\.activationState/, "Goal lifecycle rollback captures the rendered state instead of assuming the operation inverse");
assert.match(page, /callbacks\.onGoalActivationStateChange\?\.\(lifecycleChange\.goalId, lifecycleChange\.previous\)/, "Rejected Goal lifecycle apply rolls back the optimistic projection");
assert.match(page, /Promise\.resolve\(\)\.then\(\(\) => reconcile\?\.\(\)\)/, "Successful Goal lifecycle apply reconciles the full status payload without blocking the sidebar");
assert.match(dashboard, /onReconcileStatus=\{\(\) => loadFromUrl\([\s\S]*\{ background: true \}/, "Lifecycle reconciliation uses the non-fatal background status path");
assert.match(page, /if \(applied\.actionKind === "goal\.lifecycle"\) \{\s*void reconcileStatus\(applied\.goalId \? \[applied\.goalId\] : undefined\)/, "Successful Goal lifecycle apply reconciles the affected Goal without blocking the sidebar");
assert.match(dashboard, /onReconcileStatus=\{\(options\) => loadFromUrl\([\s\S]*\{ background: true, invalidateGoalIds: options\?\.invalidateGoalIds, reuseSnapshots: true \}/, "Lifecycle reconciliation uses the non-fatal background status path");
assert.match(dashboard, /statusRequestCanCommit\(statusRequestFenceRef\.current, request\)/, "A stale background response cannot overwrite a newer optimistic transition");
assert.match(sidebar, /Trash2/, "Stopped Goals expose a delete icon");
assert.match(sidebar, /onRequestGoalLifecycle\(goal, "delete"\)/, "Goal deletion stays behind the lifecycle request boundary");
Expand Down Expand Up @@ -283,8 +284,8 @@
assert.doesNotMatch(page, /personal-worker-strip/, "Manager home omits the redundant Agent worker strip");
assert.doesNotMatch(header, /切换到野兽主题|切换到默认主题/, "Workspace header does not expose theme switching");
assert.match(workspaceTheme, /workspaceThemeStorageKey = "loopx-pw-theme"/, "Theme preference persists across reloads");
assert.match(dashboard, /function isManagerProjectionQuestion[\s\S]*我现在该做什么[\s\S]*哪些 Goal 在等我[\s\S]*Agent 在做什么/, "Manager projection questions use stable intent phrases instead of exact button copy");
assert.match(dashboard, /targetContextId === "manager" && isManagerProjectionQuestion\(question\)/, "Manager projection questions remain on the cross-Goal manager route when the user adds a read-only boundary");
assert.doesNotMatch(dashboard, /isManagerProjectionQuestion/, "Ordinary manager questions do not silently bypass the selected model by matching phrases");
assert.match(dashboard, /if \(selectedRoute\.agentId === "status-only" \|\| \(!targetGoal && targetContextId !== "manager"\)\)/, "Projection answers require the explicit status-only route or a missing Goal fallback");
assert.match(dashboard, /const asksForNextAction[\s\S]*if \(asksForNextAction\)[\s\S]*personalManagerMatches\(question, \["状态"/, "A next-step question outranks a read-only boundary that mentions state");
assert.match(dashboard, /先处理「\$\{personalGoalTitle\(nextTodo\.goalId\)\}」:\$\{nextTodo\.text\}/, "The compact manager answer names the Goal and concrete blocking action");
assert.match(drawer, /t\("drawer\.decisionReview"\)/, "Blocked items preview their decision boundary before any write");
Expand All @@ -303,7 +304,7 @@
assert.match(page, /managerChatOpen/, "Manager full conversation uses a dedicated Chat view instead of stretching the home tray");
assert.match(page, /managerChatItems/, "Manager Chat only renders conversation and confirmation items");
assert.match(page, /sessionProposalIds\.includes\(item\.proposal\.previewId\)/, "Manager Chat only shows proposals created in the current UI session");
assert.match(page, /\["ready", "gated", "deferred", "applying"\]\.includes\(proposal\.status\)/, "Restored proposal history excludes stale and failed write cards from the active Chat");
assert.match(page, /\["preview_ready", "gated", "deferred", "applying"\]\.includes\(proposal\.status\)[\s\S]*compileActionReviewPlan\(proposal\)\.retryOriginal === true/, "Restored proposal history uses wire states and preserves retryable original operations");
assert.doesNotMatch(page, /proposal\.title, proposal\.status/, "Proposal dedupe does not split one action into duplicate cards by lifecycle status");
assert.match(header, /header\.managerView/, "Manager Chat exposes explicit overview and Chat navigation");
assert.match(header, /header\.managerOverview/, "Manager Chat can return to the cross-Goal overview");
Expand Down Expand Up @@ -351,7 +352,7 @@
assert.match(dashboard, /personalGoalHasPendingOperatorGate\(row\)/, "Pending operator gates project into the needs-you state");
assert.match(dashboard, /explicitUserWait/, "Explicit user-approval language repairs incomplete gate projections");
assert.match(page, /proposal\.status === "applied"/, "Unconfirmed Heartbeat previews never project as active schedules");
assert.match(drawer, /actionKind === "goal\.create" \? t\("drawer\.proposalEnterGoal"\) : t\("drawer\.proposalViewGoal"\)/, "Applied actions offer scoped refreshed navigation labels");
assert.match(drawer, /actionKind === "goal\.create" \? t\("drawer\.proposalEnterGoal"\) : t\(selection\.item\.actionKind === "team\.plan" \? "proposal\.teamPlan\.openGoal" : "drawer\.proposalViewGoal"\)/, "Applied actions offer scoped refreshed navigation labels");
assert.doesNotMatch(sidebar, /Agent 设置/, "The sidebar omits the read-only Agent settings dead end");
assert.doesNotMatch(sidebar, /野兽主题|默认主题/, "The sidebar keeps one owner-reviewed visual theme");
for (const key of ["composer.createGoalTemplate", "composer.monitorTemplate", "composer.heartbeatTemplate", "proposal.primary.goalCreate", "proposal.impact.goalCreate"]) {
Expand All @@ -370,7 +371,7 @@
"Initial real-status loading must not display bundled example tasks; explicit example mode remains available",
);

assert.match(page, /if \(settingsOpen\)[\s\S]*<WorkspaceSettingsPage/, "Settings replace the whole workspace shell");
assert.match(page, /const settingsPage = settingsOpen \?[\s\S]*<WorkspaceSettingsPage[\s\S]*<div hidden=\{settingsOpen\}>[\s\S]*<WorkspaceShell/, "Settings hide the workspace while preserving its mounted conversation state");
assert.match(sidebar, /t\("settings\.open"\)/, "The sidebar exposes one localized Settings entry");
assert.doesNotMatch(sidebar, /个人工作区/, "The sidebar footer no longer renders a static personal workspace row");
assert.doesNotMatch(workspaceSettings, /NotificationSettingsPanel/, "Settings do not render the old per-Goal notification binding panel");
Expand Down Expand Up @@ -442,7 +443,7 @@
assert.equal(matches.length, 2, `${capabilityId} has English and Simplified Chinese metadata`);
}
for (const fieldKey of ["allowed_domains", "coordinator_agent_id", "enabled", "executor_endpoint", "executor_model", "executor_reasoning_effort", "max_children", "profile", "profile_preset", "review_priority", "route_ref", "safe_fix", "strict_receipt", "timezone"]) {
const matches = capabilityLocalization.match(new RegExp(`${fieldKey}:`, "g")) ?? [];
const matches = capabilityLocalization.match(new RegExp(`^\\s+${fieldKey}:`, "gm")) ?? [];

Check warning on line 446 in apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

`String.raw` should be used to avoid escaping `\`.

See more on https://sonarcloud.io/project/issues?id=huangruiteng_loopx&issues=AaC0qzalNW4LWF9PxlUu&open=AaC0qzalNW4LWF9PxlUu&pullRequest=4696
assert.equal(matches.length, 2, `${fieldKey} has English and Simplified Chinese field copy`);
}
assert.doesNotMatch(machineSettings, /password|secret|credential/i, "Machine settings do not collect credentials");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1503,6 +1503,16 @@ export function PersonalWorkspacePage({
}
}

function openGoalConversation() {
setSelectedGoalTab("chat");
setActiveSessionRun(null);
setGoalConversationReceiptVisible(false);
window.requestAnimationFrame(() => {
const replies = channelScrollRef.current?.querySelectorAll<HTMLElement>(".personal-message.is-assistant");
replies?.item(replies.length - 1)?.scrollIntoView({ block: "start" });
});
}

const drawerCallbacks: PersonalWorkspaceCallbacks = {
...callbacks,
onOpenRunSession: async (run) => {
Expand All @@ -1517,8 +1527,8 @@ export function PersonalWorkspacePage({
void reconcileStatus([goalId]);
},
onOpenGoalView: (tab) => {
setSelectedGoalTab(tab);
if (tab === "chat") setActiveSessionRun(null);
if (tab === "chat") openGoalConversation();
else setSelectedGoalTab(tab);
setSelection(null);
},
onOpenOutput: (output) => {
Expand Down Expand Up @@ -1906,11 +1916,8 @@ export function PersonalWorkspacePage({
setManagerChatOpen(true);
}}
onSelectGoalTab={(tab) => {
setSelectedGoalTab(tab);
if (tab === "chat") {
setActiveSessionRun(null);
setGoalConversationReceiptVisible(false);
}
if (tab === "chat") openGoalConversation();
else setSelectedGoalTab(tab);
}}
onSelectAgent={selectAgent}
onReturnManagerHome={() => {
Expand Down Expand Up @@ -1962,7 +1969,7 @@ export function PersonalWorkspacePage({
setActionFeedback(t("feedback.taskDraftCreated"));
window.requestAnimationFrame(() => composerRef.current?.focus());
}}
onOpenChat={() => setSelectedGoalTab("chat")}
onOpenChat={openGoalConversation}
onQuickComplete={readOnly ? undefined : requestQuickTodoCompletion}
onSelect={setSelection}
quickCompletingTodoIds={quickCompletingTodoIds}
Expand Down Expand Up @@ -2015,10 +2022,7 @@ export function PersonalWorkspacePage({
setActionFeedback(t("feedback.taskDraftCreated"));
window.requestAnimationFrame(() => composerRef.current?.focus());
} : undefined}
onOpenConversation={() => {
setGoalConversationReceiptVisible(false);
setSelectedGoalTab("chat");
}}
onOpenConversation={openGoalConversation}
title={`${selectedGoal.title} · ${selectedAgentLabel}`}
/>
) : null}
Expand Down
Loading
Loading