(fixtureParams.get('goalEmpty') === '1' ? null : {
+ threadId: fixtureThreadId.value,
+ objective: '持续完善 7420 的稳定性与细节体验,并以可复现回归作为完成标准。',
+ status: 'active',
+ tokenBudget: 120_000,
+ tokensUsed: 36_800,
+ timeUsedSeconds: 4_260,
+ createdAt: Date.now() - 4_260_000,
+ updatedAt: Date.now(),
+})
const { isMobile } = useMobile()
const sendWithEnter = computed(() => resolveSendWithEnterPreference(null, isMobile.value))
const originalFetch = window.fetch
@@ -154,6 +195,40 @@ function noop(): void {
// Fixture route only needs rendered output for browser assertions.
}
+function updateFixtureGoal(objective: string): void {
+ fixtureGoal.value = fixtureGoal.value
+ ? { ...fixtureGoal.value, objective, updatedAt: Date.now() }
+ : {
+ threadId: fixtureThreadId.value,
+ objective,
+ status: 'active',
+ tokenBudget: null,
+ tokensUsed: 0,
+ timeUsedSeconds: 0,
+ createdAt: Date.now(),
+ updatedAt: Date.now(),
+ }
+}
+
+function updateFixtureGoalStatus(status: 'active' | 'paused'): void {
+ if (!fixtureGoal.value) return
+ fixtureGoal.value = { ...fixtureGoal.value, status, updatedAt: Date.now() }
+}
+
+function switchFixtureGoalThread(): void {
+ fixtureThreadId.value = fixtureThreadId.value === 'fixture-thread-a' ? 'fixture-thread-b' : 'fixture-thread-a'
+ fixtureGoal.value = {
+ threadId: fixtureThreadId.value,
+ objective: `持续目标:${fixtureThreadId.value}`,
+ status: 'active',
+ tokenBudget: 120_000,
+ tokensUsed: 36_800,
+ timeUsedSeconds: 4_260,
+ createdAt: Date.now() - 4_260_000,
+ updatedAt: Date.now(),
+ }
+}
+
function onSubmit(_payload: SubmitPayload): void {
submitCount.value += 1
}
diff --git a/src/components/content/ConversationRegressionFixture.vue b/src/components/content/ConversationRegressionFixture.vue
index bba1462..68194b8 100644
--- a/src/components/content/ConversationRegressionFixture.vue
+++ b/src/components/content/ConversationRegressionFixture.vue
@@ -53,7 +53,7 @@
:pending-requests="pendingRequests"
:live-overlay="liveOverlay"
:is-loading="false"
- :is-turn-in-progress="!isLoadFailureFixture && !isScrollSwitchRaceFixture"
+ :is-turn-in-progress="!isLoadFailureFixture && !isScrollSwitchRaceFixture && !isPlanFixture"
:load-error="isLoadFailureFixture ? '连接不到桌面端,会话内容暂时未加载。页面会自动重试,也可以检查或修改连接地址。' : ''"
:show-connection-settings-action="isLoadFailureFixture"
compact-runtime-chrome
@@ -61,6 +61,8 @@
cwd="E:/javaword/CXCodex/codexui"
:scroll-state="activeScrollState"
:favorite-message-ids="favoriteMessageIds"
+ :implementing-plan-id="fixtureImplementingPlanId"
+ :implemented-plan-ids="fixtureImplementedPlanIds"
@update-scroll-state="onUpdateScrollState"
@respond-server-request="noop"
@rollback="noop"
@@ -71,6 +73,7 @@
@return-to-new-thread="noop"
@dismiss-empty-thread="noop"
@retry-failed-message="noop"
+ @implement-plan="implementFixturePlan"
@copy-status="copyStatus = $event"
/>
(isPlanSubmittedFixture ? ['plan:fixture-plan-turn'] : [])
const activeThreadId = ref(isScrollSwitchRaceFixture ? 'regression-scroll-a' : 'regression-conversation-blocks')
const scrollStateByThreadId = ref>({})
const activeScrollState = computed(() => scrollStateByThreadId.value[activeThreadId.value] ?? null)
@@ -393,6 +402,54 @@ const markdownImageMessages: UiMessage[] = [
turnIndex: 9,
},
]
+const planMessages: UiMessage[] = [
+ {
+ id: 'plan:fixture-older-plan-turn',
+ role: 'system',
+ text: '',
+ messageType: 'plan',
+ plan: {
+ turnId: 'fixture-older-plan-turn',
+ explanation: '这是较早的计划,默认应收起以降低长会话噪声。',
+ steps: [
+ { step: '读取现有实现', status: 'completed' },
+ { step: '列出体验问题', status: 'completed' },
+ ],
+ rawText: '',
+ isStreaming: false,
+ },
+ turnIndex: 9,
+ },
+ {
+ id: 'fixture-plan-request',
+ role: 'user',
+ text: '请先规划如何稳定实现持续目标,不要修改文件。',
+ turnIndex: 10,
+ },
+ {
+ id: 'plan:fixture-plan-turn',
+ role: 'system',
+ text: '',
+ messageType: 'plan',
+ plan: {
+ turnId: 'fixture-plan-turn',
+ explanation: '先确认桌面端协议,再以最小改动补齐状态、交互和验证闭环。',
+ steps: [
+ { step: '核对 thread/goal 与 turn/plan 事件结构', status: 'completed' },
+ { step: '接入线程级目标生命周期和持续计划模式', status: 'completed' },
+ { step: '补充计划增量合并,避免频繁重绘', status: 'completed' },
+ { step: '为目标读取增加请求去重与旧响应保护', status: 'completed' },
+ { step: '优化移动端目标操作区', status: 'inProgress' },
+ { step: '验证清除确认和错误重试', status: 'pending' },
+ { step: '完成浏览器回归并记录兼容边界', status: 'pending' },
+ { step: '整理发布前验收结论', status: 'pending' },
+ ],
+ rawText: '',
+ isStreaming: false,
+ },
+ turnIndex: 10,
+ },
+]
const scrollRaceMessagesByThreadId = ref>(Object.fromEntries(
['regression-scroll-a', 'regression-scroll-b'].map((threadId) => [
threadId,
@@ -411,6 +468,16 @@ const fixtureMessages = computed(() => {
}
if (isImagePreviewFixture) return [...messages, imagePreviewMessage]
if (isMarkdownImageFixture) return [...messages, ...markdownImageMessages]
+ if (isPlanFixture) {
+ return isPlanHistoryImplementedFixture
+ ? [...planMessages, {
+ id: 'fixture-plan-implementation-confirmation',
+ role: 'user' as const,
+ text: PLAN_IMPLEMENTATION_CONFIRMATION,
+ turnIndex: 11,
+ }]
+ : planMessages
+ }
return messages
})
const pendingRequests: UiServerRequest[] = isTailStatusFixture || isLoadFailureFixture ? [] : allPendingRequests
@@ -428,7 +495,7 @@ const liveOverlay = ref({
errorText: '',
})
-if (isLoadFailureFixture || isScrollSwitchRaceFixture) {
+if (isLoadFailureFixture || isScrollSwitchRaceFixture || isPlanFixture) {
liveOverlay.value = null
}
@@ -498,6 +565,15 @@ function noop(): void {
// Fixture route only needs rendered output for browser assertions.
}
+function implementFixturePlan(message: UiMessage): void {
+ if (fixtureImplementingPlanId.value || fixtureImplementedPlanIds.value.includes(message.id)) return
+ fixtureImplementingPlanId.value = message.id
+ window.setTimeout(() => {
+ fixtureImplementedPlanIds.value = [...fixtureImplementedPlanIds.value, message.id]
+ fixtureImplementingPlanId.value = ''
+ }, 400)
+}
+
function appendResumeOutput(): void {
const threadId = activeThreadId.value
const currentMessages = scrollRaceMessagesByThreadId.value[threadId] ?? []
diff --git a/src/components/content/ThreadComposer.vue b/src/components/content/ThreadComposer.vue
index 373a580..15c8cd6 100644
--- a/src/components/content/ThreadComposer.vue
+++ b/src/components/content/ThreadComposer.vue
@@ -221,7 +221,10 @@
-
+
×
+
+
+ 计划模式 · 持续开启
+
+
@@ -409,9 +428,9 @@
>
✓
- 仅生成计划
+ 计划模式
- {{ selectedCollaborationMode === 'plan' ? '本次只生成计划,发送后回到执行' : '仅规划一次,不执行文件和命令' }}
+ {{ selectedCollaborationMode === 'plan' ? '持续开启,仅规划,不执行修改' : '开启后持续使用计划模式' }}
@@ -425,7 +444,7 @@
>
◎
- 本轮要求
+ 本轮要求(一次性)
{{ activeGoalLabel }}
@@ -434,7 +453,7 @@
+
+
+
+
+
+
+ {{ entry.message.plan.explanation }}
+
+
+
+ -
+ 已隐藏 {{ stepEntry.hiddenBefore }} 步
+
+ -
+ {{ planStepMarker(stepEntry.step.status) }}
+ {{ stepEntry.step.step }}
+ {{ planStepStatusLabel(stepEntry.step.status) }}
+
+
+
+
+
{{ entry.message.plan.rawText }}
+
正在整理计划…
+
+ {{ isPlanImplemented(entry.message) ? '计划已交给执行模式' : '实施此计划?' }}
+
+
+
+
+
+
+