fix: avoid stale oem routing metadata

This commit is contained in:
coso
2026-05-27 11:36:49 +08:00
parent cc3e0bb82d
commit fc0bf62532
7 changed files with 307 additions and 38 deletions
+3 -2
View File
@@ -112,10 +112,11 @@ function printSummary(changedFiles, tasks) {
if (tasks.docsOnly) {
console.log("[local-ci] 当前仅检测到文档改动,跳过本地代码校验。");
return;
}
console.log("[local-ci] 计划执行:");
if (!tasks.docsOnly) {
console.log("[local-ci] 计划执行:");
}
if (tasks.integrity) {
console.log("[local-ci] - 一致性校验");
}
+143 -1
View File
@@ -78,6 +78,22 @@ const I18N_PATCH_RETIREMENT_RECOMMENDED_COMMANDS = [
"npm run i18n:patch-retirement-gate -- --check",
];
const I18N_RELEASE_DOCS_WORKFLOW_RECOMMENDED_COMMANDS = [
"npm run i18n:release-docs-report:json -- --output docs/roadmap/i18n/evidence/release-docs-workflow-inventory.json",
];
const I18N_APP_METADATA_WORKFLOW_RECOMMENDED_COMMANDS = [
"npm run i18n:app-metadata-report:json -- --output docs/roadmap/i18n/evidence/app-metadata-workflow-inventory.json",
];
const I18N_RTL_READINESS_RECOMMENDED_COMMANDS = [
"npm run i18n:rtl-readiness-report:json -- --output docs/roadmap/i18n/evidence/rtl-readiness-inventory.json",
];
const I18N_RTL_SMOKE_RECOMMENDED_COMMANDS = [
"npm run i18n:rtl-smoke",
];
const I18N_PATCH_RETIREMENT_FILES = new Set([
"scripts/i18n-patch-metrics-report.mjs",
"scripts/i18n-patch-retirement-gate.mjs",
@@ -87,6 +103,91 @@ const I18N_PATCH_RETIREMENT_FILES = new Set([
"scripts/verify-gui-smoke.mjs",
]);
const I18N_RELEASE_DOCS_WORKFLOW_FILES = new Set([
"README.md",
"README.en.md",
"RELEASE_NOTES.md",
"RELEASE_NOTES.en.md",
"docs/README.md",
"docs/nuxt.config.ts",
"docs/package.json",
"docs/roadmap/i18n/evidence/release-docs-workflow-inventory.json",
"docs/roadmap/i18n/release-docs-workflow-evaluation.md",
"scripts/i18n-release-docs-workflow-report.test.ts",
"scripts/i18n-release-docs-workflow-report.ts",
]);
const I18N_RELEASE_DOCS_WORKFLOW_PREFIXES = [
"docs/bussniss/",
"docs/content/",
"docs/develop/",
"docs/oem/",
];
const I18N_APP_METADATA_WORKFLOW_FILES = new Set([
"docs/roadmap/i18n/app-metadata-workflow-evaluation.md",
"docs/roadmap/i18n/evidence/app-metadata-workflow-inventory.json",
"package.json",
"scripts/i18n-app-metadata-workflow-report.test.ts",
"scripts/i18n-app-metadata-workflow-report.ts",
"src-tauri/Cargo.toml",
"src-tauri/capabilities/agent-app-shell.json",
"src-tauri/tauri.conf.headless.json",
"src-tauri/tauri.conf.json",
]);
const I18N_RTL_READINESS_FILES = new Set([
"docs/roadmap/i18n/evidence/rtl-readiness-inventory.json",
"docs/roadmap/i18n/rtl-readiness-evaluation.md",
"scripts/i18n-rtl-readiness-report.test.ts",
"scripts/i18n-rtl-readiness-report.ts",
"src/i18n/createI18n.ts",
"src/i18n/locales.ts",
]);
const I18N_RTL_SMOKE_FILES = new Set([
"scripts/i18n-rtl-playwright-smoke.mjs",
]);
const I18N_RTL_SURFACE_FILES = new Set([
"src/App.tsx",
"src/components/AppSidebar.tsx",
"src/components/Modal.tsx",
"src/components/agent/chat/components/team-workspace-board/TeamWorkspaceBoardHeader.tsx",
"src/components/agent/chat/components/team-workspace-board/TeamWorkspaceBoardShell.tsx",
"src/components/agent/chat/components/team-workspace-board/TeamWorkspaceCanvasLaneCard.tsx",
"src/components/agent/chat/components/team-workspace-board/TeamWorkspaceCanvasStage.tsx",
"src/components/agent/chat/components/team-workspace-board/TeamWorkspaceCanvasToolbar.tsx",
"src/components/agent/chat/workspace/WorkspaceConversationScene.tsx",
"src/components/agent/chat/workspace/WorkspaceGeneralWorkbenchSidebar.tsx",
"src/components/agent/chat/workspace/WorkspaceHarnessDialogs.tsx",
"src/components/agent/chat/workspace/WorkspaceMainArea.tsx",
"src/components/agent/chat/workspace/WorkspaceShellScene.tsx",
"src/components/api-key-provider/ImportExportDialog.tsx",
"src/components/app-sidebar/AppSidebarConversationShelf.tsx",
"src/components/channels/ImConfigPage.tsx",
"src/components/connect/ConnectConfirmDialog.tsx",
"src/components/settings-v2/_layout/SettingsSidebar.tsx",
"src/components/settings-v2/_layout/index.tsx",
"src/components/settings-v2/agent/providers/index.tsx",
"src/components/settings-v2/general/appearance/index.tsx",
"src/components/settings-v2/general/hotkeys/index.tsx",
"src/components/settings-v2/general/memory/index.tsx",
"src/components/settings-v2/system/about/index.tsx",
"src/components/settings-v2/system/automation/index.tsx",
"src/components/settings-v2/system/channels/ChannelLogTailPanel.tsx",
"src/components/settings-v2/system/channels/ChannelsDebugWorkbench.tsx",
"src/components/settings-v2/system/developer/index.tsx",
"src/components/settings-v2/system/environment/index.tsx",
"src/components/settings-v2/system/experimental/index.tsx",
"src/components/settings-v2/system/web-search/index.tsx",
"src/components/skills/SkillScaffoldDialog.tsx",
"src/components/workspace/canvas/shared/CanvasBreadcrumbHeader.tsx",
"src/components/workspace/document/DocumentToolbar.tsx",
"src/components/workspace/video/VideoSidebar.tsx",
"src/features/knowledge/KnowledgePage.tsx",
]);
const I18N_HARDCODED_SCAN_PREFIXES = [
"src/components/",
"src/features/",
@@ -358,6 +459,31 @@ function isI18nPatchRetirementChange(file) {
return I18N_PATCH_RETIREMENT_FILES.has(file);
}
function isI18nReleaseDocsWorkflowChange(file) {
return (
I18N_RELEASE_DOCS_WORKFLOW_FILES.has(file) ||
I18N_RELEASE_DOCS_WORKFLOW_PREFIXES.some((prefix) =>
file.startsWith(prefix),
)
);
}
function isI18nAppMetadataWorkflowChange(file) {
return I18N_APP_METADATA_WORKFLOW_FILES.has(file);
}
function isI18nRtlReadinessChange(file) {
return (
I18N_RTL_READINESS_FILES.has(file) ||
I18N_RTL_SMOKE_FILES.has(file) ||
I18N_RTL_SURFACE_FILES.has(file)
);
}
function isI18nRtlSmokeChange(file) {
return I18N_RTL_SMOKE_FILES.has(file) || I18N_RTL_SURFACE_FILES.has(file);
}
function isI18nHardcodedScanChange(file) {
return (
file === "src/App.tsx" ||
@@ -456,6 +582,22 @@ function collectRecommendedCommands(changedFiles, { docsOnly = false } = {}) {
commands.push(...I18N_BUNDLE_STRATEGY_RECOMMENDED_COMMANDS);
}
if (changedFiles.some(isI18nReleaseDocsWorkflowChange)) {
commands.push(...I18N_RELEASE_DOCS_WORKFLOW_RECOMMENDED_COMMANDS);
}
if (changedFiles.some(isI18nAppMetadataWorkflowChange)) {
commands.push(...I18N_APP_METADATA_WORKFLOW_RECOMMENDED_COMMANDS);
}
if (changedFiles.some(isI18nRtlReadinessChange)) {
commands.push(...I18N_RTL_READINESS_RECOMMENDED_COMMANDS);
}
if (changedFiles.some(isI18nRtlSmokeChange)) {
commands.push(...I18N_RTL_SMOKE_RECOMMENDED_COMMANDS);
}
return Array.from(new Set(commands));
}
@@ -545,7 +687,7 @@ function detectTasks(changedFiles, { full = false } = {}) {
docs: true,
docsOnly: true,
fallback: false,
recommendedCommands: [],
recommendedCommands,
workflow: false,
};
}
@@ -3834,6 +3834,7 @@ export function AgentChatWorkspace({
sessionId,
executionStrategy,
accessMode,
providerType,
preferredTeamPresetId,
selectedTeam,
selectedTeamLabel,
@@ -207,6 +207,38 @@ describe("CodeReviewSummaryPanel", () => {
expect(container.textContent).toContain("1 条输出需要处理");
});
it("只有输出时主按钮应打开输出区块", () => {
const { container, onOpenSection, onOpenFileCheckpoints } = renderPanel({
harnessState: {
...createEmptyHarnessState(),
outputSignals: [
{
id: "signal-only",
toolCallId: "tool-test",
toolName: "bash",
title: "类型检查",
summary: "typecheck passed",
preview: "tsc --noEmit",
exitCode: 0,
},
],
hasSignals: true,
},
fileCheckpointSummary: null,
});
const primaryAction = container.querySelector(
'[data-testid="code-review-summary-primary-action"]',
) as HTMLButtonElement | null;
act(() => {
primaryAction?.click();
});
expect(onOpenSection).toHaveBeenCalledWith("outputs");
expect(onOpenFileCheckpoints).not.toHaveBeenCalled();
});
it("没有快照时只展示空态入口", () => {
const { container } = renderPanel({
harnessState: {
@@ -1062,6 +1062,65 @@ Extract it into the Agent Skills directory.`,
}
});
it("切到非 OEM Provider 时不应继续透传 OEM 低额度路由", async () => {
mockResolveOemCloudRuntimeContext.mockReturnValue({
baseUrl: "https://lime.example.com",
controlPlaneBaseUrl: "https://lime.example.com",
sceneBaseUrl: "https://lime.example.com",
gatewayBaseUrl: "https://gateway.example.com",
tenantId: "tenant-1",
sessionToken: "session-token",
hubProviderName: "lime-hub",
loginPath: "/login",
desktopClientId: "desktop-client",
desktopOauthRedirectUrl: "lime://oauth/callback",
desktopOauthNextPath: "/welcome",
});
mockGetOemCloudBootstrapSnapshot.mockReturnValue({
providerPreference: {
tenantId: "tenant-1",
userId: "user-1",
providerSource: "oem_cloud",
providerKey: "lime-hub",
defaultModel: "gpt-5.5",
needsValidation: false,
updatedAt: "2026-04-24T00:00:00.000Z",
},
providerOffersSummary: [
{
providerKey: "lime-hub",
source: "oem_cloud",
state: "available_quota_low",
defaultModel: "gpt-5.5",
configMode: "managed",
quotaStatus: "low",
fallbackToLocalAllowed: false,
canInvoke: true,
},
],
});
const harness = mountHook({ providerType: "deepseek" });
try {
await act(async () => {
const started = await harness
.getValue()
.handleSend([], false, false, "继续处理当前话题", "react");
expect(started).toBe(true);
});
expect(mockSendMessage).toHaveBeenCalledTimes(1);
const args = mockSendMessage.mock.calls[0] as Parameters<
HookProps["sendMessage"]
>;
expect(args?.[8]?.requestMetadata?.harness).not.toHaveProperty(
"oem_routing",
);
} finally {
harness.unmount();
}
});
it("OEM bootstrap features 应透传为 tenant_feature_flags 策略输入", async () => {
mockResolveOemCloudRuntimeContext.mockReturnValue({
baseUrl: "https://lime.example.com",
@@ -2554,6 +2554,7 @@ interface UseWorkspaceSendActionsParams {
sessionId?: string | null;
executionStrategy: ExecutionStrategy;
accessMode?: AgentAccessMode;
providerType?: string | null;
preferredTeamPresetId?: string | null;
selectedTeam?: TeamDefinition | null;
selectedTeamLabel?: string;
@@ -2712,6 +2713,7 @@ export function useWorkspaceSendActions({
sessionId,
executionStrategy,
accessMode: _accessMode,
providerType,
preferredTeamPresetId,
selectedTeam,
selectedTeamLabel,
@@ -5216,6 +5218,7 @@ export function useWorkspaceSendActions({
...(sendOptions || {}),
toolPreferencesOverride: effectiveToolPreferences,
},
currentProviderType: providerType,
effectiveToolPreferences,
mappedTheme,
isThemeWorkbench,
@@ -5384,6 +5387,7 @@ export function useWorkspaceSendActions({
messagesCount,
mentionedCharacters,
preferredTeamPresetId,
providerType,
rollbackAfterSendFailure,
selectedTeam,
selectedTeamLabel,
@@ -497,6 +497,25 @@ function buildOemRoutingRequestMetadata():
};
}
function normalizeProviderSelection(value?: string | null): string {
return value?.trim().toLowerCase() || "";
}
function providerMatchesOemRouting(
providerType: string | null | undefined,
oemRouting: HarnessOemRoutingRequestMetadata | undefined,
): boolean {
const normalizedProvider = normalizeProviderSelection(providerType);
const normalizedOemProvider = normalizeProviderSelection(
oemRouting?.provider_key,
);
if (!normalizedProvider || !normalizedOemProvider) {
return true;
}
return normalizedProvider === normalizedOemProvider;
}
function buildTenantFeatureFlagsRequestMetadata():
| HarnessTenantFeatureFlagsRequestMetadata
| undefined {
@@ -607,6 +626,7 @@ interface PrimeBrowserAssistBeforeSendOptions {
interface BuildWorkspaceRequestMetadataOptions {
workspaceRequestMetadataBase?: Record<string, unknown>;
sendOptions?: HandleSendOptions;
currentProviderType?: string | null;
effectiveToolPreferences: ChatToolPreferences;
mappedTheme: ThemeType;
isThemeWorkbench: boolean;
@@ -941,6 +961,7 @@ export function buildWorkspaceRequestMetadata(
const {
workspaceRequestMetadataBase,
sendOptions,
currentProviderType,
effectiveToolPreferences,
mappedTheme,
isThemeWorkbench,
@@ -1004,45 +1025,54 @@ export function buildWorkspaceRequestMetadata(
const resolvedTenantFeatureFlags =
buildTenantFeatureFlagsRequestMetadata() ||
readExistingTenantFeatureFlags(existingHarnessMetadata);
const oemRoutingMatchesCurrentProvider = providerMatchesOemRouting(
currentProviderType,
resolvedOemRouting,
);
const harnessMetadata = buildHarnessRequestMetadata({
base: existingHarnessMetadata,
theme: mappedTheme,
turnPurpose: sendOptions?.purpose,
preferences: {
webSearch: effectiveToolPreferences.webSearch,
thinking: effectiveToolPreferences.thinking,
task: effectiveToolPreferences.task,
subagent: effectiveToolPreferences.subagent,
},
sessionMode: isThemeWorkbench ? "general_workbench" : "default",
gateKey: isThemeWorkbench ? currentGateKey : undefined,
runTitle: themeWorkbenchActiveQueueTitle?.trim() || undefined,
contentId: contentId || undefined,
browserRequirement: resolvedBrowserRequirement,
browserRequirementReason: resolvedBrowserRequirementReason,
browserLaunchUrl: resolvedBrowserLaunchUrl,
browserAssistProfileKey:
mappedTheme === "general"
? browserAssistProfileKey || GENERAL_BROWSER_ASSIST_PROFILE_KEY
: undefined,
browserAssistPreferredBackend,
browserAssistAutoLaunch,
preferredTeamPresetId: resolvedPreferredTeamPresetId,
selectedTeamId: resolvedSelectedTeamId,
selectedTeamSource: resolvedSelectedTeamSource,
selectedTeamLabel: resolvedSelectedTeamLabel,
selectedTeamDescription: resolvedSelectedTeamDescription,
selectedTeamSummary: resolvedSelectedTeamSummary,
selectedTeamRoles: resolvedSelectedTeamRoles,
teamMemoryShadow: resolvedTeamMemoryShadow,
agentResponseLanguage,
oemRouting: oemRoutingMatchesCurrentProvider ? resolvedOemRouting : undefined,
tenantFeatureFlags: resolvedTenantFeatureFlags,
});
if (!oemRoutingMatchesCurrentProvider) {
delete harnessMetadata.oem_routing;
delete harnessMetadata.oemRouting;
}
return {
...(workspaceRequestMetadataBase || {}),
...(sendOptions?.requestMetadata || {}),
harness: buildHarnessRequestMetadata({
base: existingHarnessMetadata,
theme: mappedTheme,
turnPurpose: sendOptions?.purpose,
preferences: {
webSearch: effectiveToolPreferences.webSearch,
thinking: effectiveToolPreferences.thinking,
task: effectiveToolPreferences.task,
subagent: effectiveToolPreferences.subagent,
},
sessionMode: isThemeWorkbench ? "general_workbench" : "default",
gateKey: isThemeWorkbench ? currentGateKey : undefined,
runTitle: themeWorkbenchActiveQueueTitle?.trim() || undefined,
contentId: contentId || undefined,
browserRequirement: resolvedBrowserRequirement,
browserRequirementReason: resolvedBrowserRequirementReason,
browserLaunchUrl: resolvedBrowserLaunchUrl,
browserAssistProfileKey:
mappedTheme === "general"
? browserAssistProfileKey || GENERAL_BROWSER_ASSIST_PROFILE_KEY
: undefined,
browserAssistPreferredBackend,
browserAssistAutoLaunch,
preferredTeamPresetId: resolvedPreferredTeamPresetId,
selectedTeamId: resolvedSelectedTeamId,
selectedTeamSource: resolvedSelectedTeamSource,
selectedTeamLabel: resolvedSelectedTeamLabel,
selectedTeamDescription: resolvedSelectedTeamDescription,
selectedTeamSummary: resolvedSelectedTeamSummary,
selectedTeamRoles: resolvedSelectedTeamRoles,
teamMemoryShadow: resolvedTeamMemoryShadow,
agentResponseLanguage,
oemRouting: resolvedOemRouting,
tenantFeatureFlags: resolvedTenantFeatureFlags,
}),
harness: harnessMetadata,
};
}