From aba973e53fec24bdbe5d89c6fbcb90bd6cd374ba Mon Sep 17 00:00:00 2001 From: "CatEqdvbcxw-GPT-5.6-sol" Date: Wed, 12 Aug 2026 19:43:00 +0800 Subject: [PATCH 1/2] fix(web): simplify Context Window settings hierarchy (#1343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: Keep normal member configuration focused on Context Window and Session Strategy choices without exposing runtime diagnostics. [砚砚/gpt-5.6-sol🐾] Thread-Context: threadId=thread_msq07g67kqo1d1nn catId=cat-eqdvbcxw --- .../components/HubSessionStrategyEditor.tsx | 125 ++++------- .../__tests__/hub-cat-editor.test.tsx | 194 +++++++++++++++++- .../components/hub-cat-editor-advanced.tsx | 56 ++--- 3 files changed, 241 insertions(+), 134 deletions(-) diff --git a/packages/web/src/components/HubSessionStrategyEditor.tsx b/packages/web/src/components/HubSessionStrategyEditor.tsx index 02a415bd29..1b8c1c220e 100644 --- a/packages/web/src/components/HubSessionStrategyEditor.tsx +++ b/packages/web/src/components/HubSessionStrategyEditor.tsx @@ -2,23 +2,6 @@ import { SESSION_STRATEGY_OPTIONS, type StrategyFormState } from './hub-cat-editor.model'; import { RangeField, SelectField, TextField } from './hub-cat-editor-fields'; -import { SOURCE_LABELS } from './hub-strategy-types'; - -const REASON_LABELS: Record = { - managed_invocation_boundary: '需要受管 invocation 边界', - effective_input_ceiling: '缺少有效输入上限', - carrier_binding: '缺少当前 carrier 绑定', - authoritative_usage: '缺少权威 usage', - session_rotation: '缺少 Session 轮换能力', - continuity_bootstrap: '缺少连续性重建能力', - compression_signal: '缺少压缩事件信号', -}; - -function statusLabel(status: StrategyFormState['executionStatus']['status']): string { - if (status === 'active') return 'Active'; - if (status === 'degraded') return 'Degraded'; - return 'Unavailable'; -} export function HubSessionStrategyEditor({ strategyForm, @@ -31,10 +14,9 @@ export function HubSessionStrategyEditor({ error: string | null; onChange: (patch: Partial) => void; }) { - const statusMatchesDraft = strategyForm?.strategy === strategyForm?.statusStrategy; - return ( -
+
+

Session State / Chain

{loading ?

Session 策略加载中...

: null} {error ? (

@@ -42,74 +24,47 @@ export function HubSessionStrategyEditor({

) : null} {strategyForm ? ( -
-
-

Session State / Chain 始终记录并可见。

-

策略只表达你的意图;能力不足会显示执行状态,不会把策略静默改成另一种。

-

保存后的策略从下一次 invocation 起作用于当前 active session;正在运行的 invocation 保持原快照。

-
- -
- - {statusMatchesDraft ? '能力预检' : `已保存 ${strategyForm.statusStrategy} 策略的能力预检`}: - {statusLabel(strategyForm.executionStatus.status)} - - 来源:{SOURCE_LABELS[strategyForm.source] ?? strategyForm.source} -
- {!statusMatchesDraft ? ( -

- 保存后会重新预检 {strategyForm.strategy};当前状态不会被套用到未保存的策略。 -

- ) : strategyForm.executionStatus.missingCapabilities.length > 0 ? ( -

- {strategyForm.executionStatus.missingCapabilities - .map((reason) => REASON_LABELS[reason] ?? reason) - .join(';')} -

- ) : null} - -
- onChange({ strategy: value as StrategyFormState['strategy'] })} +
+ onChange({ strategy: value as StrategyFormState['strategy'] })} + tone="success" + /> + onChange({ warnThreshold: value })} + hint={ + strategyForm.strategy === 'compress' + ? '仅用于观测;compress 不触发 handoff' + : 'context 填充到此比例时发出警告' + } + /> + onChange({ actionThreshold: value })} + hint={ + strategyForm.strategy === 'compress' + ? '仅用于观测;Session 在 client 压缩后继续存活' + : '能力状态为 Active 时,达到此比例才执行当前策略动作' + } + /> + {strategyForm.strategy === 'hybrid' ? ( + onChange({ maxCompressions: value })} + inputMode="numeric" tone="success" /> - onChange({ warnThreshold: value })} - hint={ - strategyForm.strategy === 'compress' - ? '仅用于观测;compress 不触发 handoff' - : 'context 填充到此比例时发出警告' - } - /> - onChange({ actionThreshold: value })} - hint={ - strategyForm.strategy === 'compress' - ? '仅用于观测;Session 在 client 压缩后继续存活' - : '能力状态为 Active 时,达到此比例才执行当前策略动作' - } - /> - {strategyForm.strategy === 'hybrid' ? ( - onChange({ maxCompressions: value })} - inputMode="numeric" - tone="success" - /> - ) : null} -
+ ) : null}
) : null} -
+
); } diff --git a/packages/web/src/components/__tests__/hub-cat-editor.test.tsx b/packages/web/src/components/__tests__/hub-cat-editor.test.tsx index 22cad3ff5e..32443c6ce8 100644 --- a/packages/web/src/components/__tests__/hub-cat-editor.test.tsx +++ b/packages/web/src/components/__tests__/hub-cat-editor.test.tsx @@ -159,6 +159,10 @@ describe('HubCatEditor', () => { defaultModel = 'test-model', patch: Partial = {}, speed: { visible?: boolean; fastSupported?: boolean } = {}, + runtime: { + cat?: CatData | null; + strategyForm?: Parameters[0]['strategyForm']; + } = {}, ) { const form: HubCatEditorFormState = { catId: `runtime-${clientId}`, @@ -195,9 +199,9 @@ describe('HubCatEditor', () => { await act(async () => { root.render( React.createElement(AdvancedRuntimeSection, { - cat: null, + cat: runtime.cat ?? null, form, - strategyForm: null, + strategyForm: runtime.strategyForm ?? null, loadingStrategy: false, strategyError: null, codexSettings: null, @@ -216,6 +220,176 @@ describe('HubCatEditor', () => { return onChange; } + it('keeps Context Window focused on the Auto or Manual choice', async () => { + await renderAdvancedRuntimeSection( + 'openai', + 'gpt-5.6-sol', + {}, + {}, + { + cat: { + id: 'runtime-openai', + displayName: 'Runtime openai', + clientId: 'openai', + defaultModel: 'gpt-5.6-sol', + color: { primary: '#16a34a', secondary: '#bbf7d0' }, + mentionPatterns: ['@runtime-openai'], + avatar: '/avatars/default.png', + roleDescription: 'runtime config', + personality: 'test', + resolvedContext: { + ...actionableContextProjection, + source: 'reported', + provenance: 'internal carrier provenance', + }, + }, + }, + ); + + const input = queryField(container, 'input[aria-label="Context Window"]'); + expect(input.placeholder).toBe('留空或 0 = Auto;正整数 = Manual'); + expect(document.body.textContent).toContain( + '填写正整数 = Manual 模式,作为该成员的上下文窗口大小。留空或填 0 = Auto,由运行时自动探测。', + ); + expect(document.body.textContent).not.toContain('解析值'); + expect(document.body.textContent).not.toContain('模型目录'); + expect(document.body.textContent).not.toContain('internal carrier provenance'); + }); + + it('shows a concise action only when the selected client cannot resolve or apply Context Window', async () => { + const baseCat: CatData = { + id: 'runtime-antigravity', + displayName: 'Runtime antigravity', + clientId: 'antigravity', + defaultModel: 'test-model', + color: { primary: '#16a34a', secondary: '#bbf7d0' }, + mentionPatterns: ['@runtime-antigravity'], + avatar: '/avatars/default.png', + roleDescription: 'runtime config', + personality: 'test', + }; + + await renderAdvancedRuntimeSection( + 'antigravity', + 'test-model', + {}, + {}, + { + cat: { + ...baseCat, + resolvedContext: { + reportsRuntimeWindow: false, + nativeWindowControl: false, + authoritativeUsage: false, + usageTelemetry: 'unavailable', + nativeCompressionControl: false, + observesCompression: false, + capabilityReason: 'Concrete carrier capability unavailable', + }, + }, + }, + ); + + expect(document.body.textContent).toContain('当前 Client 无法自动探测上下文窗口;请填写正整数使用 Manual 模式。'); + expect(document.body.textContent).not.toContain('Concrete carrier capability unavailable'); + expect(document.body.textContent).not.toContain('未能解析 Context Window'); + + await renderAdvancedRuntimeSection( + 'antigravity', + 'test-model', + {}, + {}, + { + cat: { + ...baseCat, + resolvedContext: { + reportsRuntimeWindow: true, + nativeWindowControl: false, + authoritativeUsage: false, + usageTelemetry: 'conditional', + nativeCompressionControl: false, + observesCompression: false, + }, + }, + }, + ); + + expect(document.body.textContent).not.toContain('当前 Client 无法自动探测上下文窗口'); + expect(document.body.textContent).not.toContain('未能解析 Context Window'); + + await renderAdvancedRuntimeSection( + 'antigravity', + 'test-model', + {}, + {}, + { + cat: { + ...baseCat, + resolvedContext: { + source: 'catalog', + windowTokens: 128_000, + reportsRuntimeWindow: false, + nativeWindowControl: false, + authoritativeUsage: false, + usageTelemetry: 'unavailable', + nativeCompressionControl: false, + observesCompression: false, + }, + }, + }, + ); + + expect(document.body.textContent).not.toContain('当前 Client 无法自动探测上下文窗口'); + }); + + it.each([ + 'handoff', + 'compress', + 'hybrid', + ] as const)('renders Session State / Chain as a heading directly above the %s strategy controls', async (strategy) => { + await renderAdvancedRuntimeSection( + 'openai', + 'gpt-5.6-sol', + {}, + {}, + { + cat: { + id: 'runtime-openai', + displayName: 'Runtime openai', + clientId: 'openai', + defaultModel: 'gpt-5.6-sol', + color: { primary: '#16a34a', secondary: '#bbf7d0' }, + mentionPatterns: ['@runtime-openai'], + avatar: '/avatars/default.png', + roleDescription: 'runtime config', + personality: 'test', + resolvedContext: actionableContextProjection, + }, + strategyForm: { + strategy, + statusStrategy: strategy, + warnThreshold: '0.75', + actionThreshold: '0.85', + maxCompressions: '2', + source: 'runtime_override', + revision: 'test', + changedAt: 0, + executionStatus: { status: 'unavailable', missingCapabilities: ['authoritative_usage'] }, + }, + }, + ); + + const heading = document.body.querySelector('h3'); + const strategySelect = queryField(container, 'select[aria-label="Session Strategy"]'); + expect(heading?.textContent).toBe('Session State / Chain'); + expect(heading?.nextElementSibling?.contains(strategySelect)).toBe(true); + expect(strategySelect.value).toBe(strategy); + expect(Array.from(strategySelect.options, (option) => option.value)).toEqual(['handoff', 'compress', 'hybrid']); + expect(document.body.textContent).not.toContain('能力预检'); + expect(document.body.textContent).not.toContain('来源:'); + expect(document.body.textContent).not.toContain('缺少权威 usage'); + }); + it('shows extra CLI args editor for CLI clients and hides it for API-only clients', async () => { for (const clientId of ['anthropic', 'openai', 'google', 'kimi', 'opencode'] as const) { await renderAdvancedRuntimeSection(clientId); @@ -3481,7 +3655,11 @@ describe('HubCatEditor', () => { expect(document.body.textContent).toContain('展开后可配置 TTS clone 参考音频和文本。'); expect(document.body.textContent).toContain('别名与 @ 路由'); expect(document.body.textContent).toContain('认证与模型'); - expect(document.body.textContent).toContain('Session State / Chain 始终记录并可见'); + expect( + Array.from(document.body.querySelectorAll('h3')).some( + (heading) => heading.textContent === 'Session State / Chain', + ), + ).toBe(true); expect(document.body.textContent).toContain('── Codex 专属 (仅 Client=Codex 时显示) ──'); expect(document.body.textContent).toContain('Codex Sandbox (Codex)'); expect(document.body.textContent).toContain('Codex Approval (Codex)'); @@ -3504,8 +3682,8 @@ describe('HubCatEditor', () => { await changeField(queryField(container, 'input[aria-label="Team Strengths"]'), '代码审查、找 bug、深度思考'); await changeField(queryField(container, 'input[aria-label="Strengths"]'), 'security, testing, debugging'); await changeField(queryField(container, 'select[aria-label="Session Strategy"]'), 'handoff', 'change'); - expect(document.body.textContent).toContain('已保存 compress 策略的能力预检'); - expect(document.body.textContent).toContain('保存后会重新预检 handoff'); + expect(document.body.textContent).not.toContain('能力预检'); + expect(document.body.textContent).not.toContain('保存后会重新预检 handoff'); await changeField(queryField(container, 'input[aria-label="Session Warn Threshold"]'), '0.55', 'change'); await changeField(queryField(container, 'select[aria-label^="Codex Sandbox"]'), 'danger-full-access', 'change'); await changeField(queryField(container, 'select[aria-label^="Codex Approval"]'), 'never', 'change'); @@ -3731,7 +3909,11 @@ describe('HubCatEditor', () => { }); await flushEffects(); - expect(document.body.textContent).toContain('Session State / Chain 始终记录并可见'); + expect( + Array.from(document.body.querySelectorAll('h3')).some( + (heading) => heading.textContent === 'Session State / Chain', + ), + ).toBe(true); expect(document.body.textContent).not.toContain('Session Chain 未开启'); expect(queryField(container, 'select[aria-label="Session Strategy"]').value).toBe('compress'); expect(document.body.querySelector('input[aria-label="Session Observe Threshold"]')).toBeTruthy(); diff --git a/packages/web/src/components/hub-cat-editor-advanced.tsx b/packages/web/src/components/hub-cat-editor-advanced.tsx index bb7212c36f..437cd1e200 100644 --- a/packages/web/src/components/hub-cat-editor-advanced.tsx +++ b/packages/web/src/components/hub-cat-editor-advanced.tsx @@ -71,12 +71,12 @@ export function AdvancedRuntimeSection({ onChange={(value) => onChange({ contextWindow: value })} inputMode="numeric" tone="success" - placeholder="留空或 0 = Auto(由 CLI / 模型目录自动探测)" + placeholder="留空或 0 = Auto;正整数 = Manual" />

填写正整数 = Manual 模式,作为该成员的上下文窗口大小。留空或填 0 = Auto,由运行时自动探测。

- + {cliExtensionsAvailable && cliEffortOptions ? ( <> = { - reported: 'CLI 实时上报', - manual: '手动设置', - catalog: '模型目录', -}; - -const SOURCE_BADGES: Record = { - reported: '✓ 运行时', - manual: '✓ 手动', - catalog: '≈ 目录', -}; - -/** Show resolved context window info below the Context Window field. */ -function ResolvedContextInfo({ cat }: { cat?: CatData | null }) { - if (!cat) return null; - - const rc = cat.resolvedContext; - if (!rc || !rc.source) { - return ( -
-

⚠ 未能解析 Context Window — 无已知的模型目录条目或 CLI 上报值。

- {rc?.capabilityReason ?

原因: {rc.capabilityReason}

: null} -
- ); - } - - const badge = SOURCE_BADGES[rc.source] ?? rc.source; - const sourceLabel = SOURCE_LABELS[rc.source] ?? rc.source; - const windowK = rc.windowTokens ? Math.round(rc.windowTokens / 1000) : 0; + const manualWindow = Number(contextWindow.trim()); + const isManual = Number.isInteger(manualWindow) && manualWindow > 0; + if (!isManual && capability.source) return null; return ( -
-

- {badge} - {' · '} - 解析值: {windowK}K tokens - {' · '} - 来源: {sourceLabel} - {rc.actionable ? '' : '(仅供参考,不触发自动 Session 操作)'} -

-

{rc.provenance}

-
+

+ {isManual + ? '当前 Client 无法校验或调整自身的上下文窗口;请确认 Manual 值不高于 Client 的实际上限。' + : '当前 Client 无法自动探测上下文窗口;请填写正整数使用 Manual 模式。'} +

); } From c77ee06e2d0ce80d310e023a740649226e9a1475 Mon Sep 17 00:00:00 2001 From: "CatEqdvbcxw-GPT-5.6-sol" Date: Wed, 12 Aug 2026 20:24:52 +0800 Subject: [PATCH 2/2] fix(web): finish settings hierarchy cleanup (#1343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: runtime session cards still exposed applied policy internals after the member editor was simplified, and the manual incompatibility copy lacked direct regression coverage. Remove those runtime projections while retaining meaningful session facts, and cover the remaining user-visible branch. [砚砚/gpt-5.6-sol🐾] --- .../web/src/components/SessionChainPanel.tsx | 24 --------- .../__tests__/hub-cat-editor.test.tsx | 25 ++++++++++ .../__tests__/session-chain-panel.test.ts | 49 ++++++++++++++++--- 3 files changed, 67 insertions(+), 31 deletions(-) diff --git a/packages/web/src/components/SessionChainPanel.tsx b/packages/web/src/components/SessionChainPanel.tsx index c44714bd23..d8096fb299 100644 --- a/packages/web/src/components/SessionChainPanel.tsx +++ b/packages/web/src/components/SessionChainPanel.tsx @@ -36,15 +36,6 @@ interface SessionSummary { cacheReadTokens?: number; costUsd?: number; }; - appliedPolicy?: { - config: { strategy: 'handoff' | 'compress' | 'hybrid' }; - source: string; - revision: string; - execution: { - status: 'active' | 'degraded' | 'unavailable'; - missingCapabilities: string[]; - }; - }; runtimeSession?: RuntimeSessionSummary; } @@ -127,9 +118,6 @@ function sealedSessionDetails(session: SessionSummary): string | undefined { ? `${session.compressionCount} compress` : '0 compress observed', session.sealReason ? sealReasonLabel(session.sealReason) : null, - session.appliedPolicy - ? `${session.appliedPolicy.config.strategy} · ${session.appliedPolicy.execution.status}` - : null, ].filter(Boolean); return details.length > 0 ? details.join(' · ') : undefined; } @@ -364,18 +352,6 @@ export function SessionChainPanel({ threadId, catInvocations, onViewSession }: S )} {session.compressionCount === 0 && · 0 compress observed}
- {session.appliedPolicy && ( -
- policy {session.appliedPolicy.config.strategy} - · {session.appliedPolicy.execution.status} - {session.appliedPolicy.execution.missingCapabilities.length > 0 && ( - · missing {session.appliedPolicy.execution.missingCapabilities.join(', ')} - )} -
- )} {session.runtimeSession && (
{ expect(document.body.textContent).not.toContain('Concrete carrier capability unavailable'); expect(document.body.textContent).not.toContain('未能解析 Context Window'); + await renderAdvancedRuntimeSection( + 'antigravity', + 'test-model', + { contextWindow: '128000' }, + {}, + { + cat: { + ...baseCat, + resolvedContext: { + reportsRuntimeWindow: false, + nativeWindowControl: false, + authoritativeUsage: false, + usageTelemetry: 'unavailable', + nativeCompressionControl: false, + observesCompression: false, + }, + }, + }, + ); + + expect(document.body.textContent).toContain( + '当前 Client 无法校验或调整自身的上下文窗口;请确认 Manual 值不高于 Client 的实际上限。', + ); + expect(document.body.textContent).not.toContain('当前 Client 无法自动探测上下文窗口'); + await renderAdvancedRuntimeSection( 'antigravity', 'test-model', diff --git a/packages/web/src/components/__tests__/session-chain-panel.test.ts b/packages/web/src/components/__tests__/session-chain-panel.test.ts index 6974254b9e..33ccdb2ced 100644 --- a/packages/web/src/components/__tests__/session-chain-panel.test.ts +++ b/packages/web/src/components/__tests__/session-chain-panel.test.ts @@ -169,10 +169,10 @@ describe('F24: SessionChainPanel', () => { expect(container.textContent).toContain('0 compress observed'); }); - it('shows the applied policy and exact execution status on the active session node', async () => { + it('keeps applied policy details out of active cards and sealed summaries', async () => { mockSessionsResponse([ { - id: 'policy-node', + id: 'active-policy-node', catId: 'opus', seq: 0, status: 'active', @@ -190,16 +190,51 @@ describe('F24: SessionChainPanel', () => { }, }, }, + { + id: 'sealed-policy-node', + catId: 'codex', + seq: 1, + status: 'sealed', + messageCount: 12, + compressionCount: 2, + contextHealth: { + usedTokens: 94_720, + windowTokens: 128_000, + fillRatio: 0.74, + source: 'exact', + }, + sealReason: 'threshold', + createdAt: Date.now() - 60_000, + sealedAt: Date.now() - 30_000, + appliedPolicy: { + config: { strategy: 'handoff', thresholds: { warn: 0.8, action: 0.9 } }, + source: 'runtime_override', + revision: 'runtime:handoff:1', + changedAt: 1, + execution: { + status: 'unavailable', + missingCapabilities: ['authoritative_usage'], + }, + }, + }, ]); renderPanel('thread-policy-state'); await flushFetch(); - const policyState = container.querySelector('[data-testid="session-policy-state"]'); - expect(policyState?.textContent).toContain('hybrid'); - expect(policyState?.textContent).toContain('degraded'); - expect(policyState?.textContent).toContain('compression_signal'); - expect(policyState?.textContent).toContain('session_rotation'); + expect(container.querySelector('[data-testid="session-policy-state"]')).toBeNull(); + expect(container.textContent).not.toContain('compression_signal'); + expect(container.textContent).not.toContain('session_rotation'); + + expandSealed(); + const sealedSummary = container.querySelector('[data-testid="sealed-session-summary"]'); + await act(async () => sealedSummary?.querySelector('button[aria-expanded="false"]')?.click()); + expect(sealedSummary?.textContent).toContain('74%'); + expect(sealedSummary?.textContent).toContain('2 compress'); + expect(sealedSummary?.textContent).toContain('threshold'); + expect(sealedSummary?.textContent).not.toContain('handoff'); + expect(sealedSummary?.textContent).not.toContain('unavailable'); + expect(sealedSummary?.textContent).not.toContain('authoritative_usage'); }); it('collapses repeated 0-msg tool_conflict retry corpses into one summary (F201-churn)', async () => {