Commit 9f8a4c31 authored by AI-甘富林's avatar AI-甘富林

feat(desktop): add home intent suggestion smoke hooks

Expose desktop smoke hooks for homepage intent suggestions and add an isolation smoke wrapper for the new decision flow.
Co-Authored-By: 's avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent 93078b84
...@@ -127,6 +127,13 @@ interface RendererSmokeState { ...@@ -127,6 +127,13 @@ interface RendererSmokeState {
statusLabels?: string[]; statusLabels?: string[];
lastError?: string; lastError?: string;
} | null; } | null;
ui?: {
currentProjectId?: string;
homeIntentSuggestionVisible?: boolean;
homeIntentSuggestionProjectId?: string;
homeIntentSuggestionProjectName?: string;
pendingHomeIntentPrompt?: string;
};
} }
const forcedUserDataPath = process.env.QJCLAW_USER_DATA_PATH?.trim(); const forcedUserDataPath = process.env.QJCLAW_USER_DATA_PATH?.trim();
...@@ -675,6 +682,7 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise< ...@@ -675,6 +682,7 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise<
? "settings" ? "settings"
: "chat"; : "chat";
const smokeProjectId = process.env.QJCLAW_SMOKE_PROJECT_ID?.trim() || ""; const smokeProjectId = process.env.QJCLAW_SMOKE_PROJECT_ID?.trim() || "";
const smokeSuggestionAction = process.env.QJCLAW_SMOKE_SUGGESTION_ACTION?.trim() || "";
const smokeAttachments = resolveSmokeAttachments(); const smokeAttachments = resolveSmokeAttachments();
await trace("runSmokeTest:before-send-script"); await trace("runSmokeTest:before-send-script");
const sendResult = await window.webContents.executeJavaScript(`(async () => { const sendResult = await window.webContents.executeJavaScript(`(async () => {
...@@ -693,6 +701,7 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise< ...@@ -693,6 +701,7 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise<
const preferredSkillId = ${JSON.stringify(process.env.QJCLAW_SMOKE_SKILL_ID?.trim() ?? "")}; const preferredSkillId = ${JSON.stringify(process.env.QJCLAW_SMOKE_SKILL_ID?.trim() ?? "")};
const smokeViewMode = ${JSON.stringify(smokeViewMode)}; const smokeViewMode = ${JSON.stringify(smokeViewMode)};
const smokeAttachments = ${JSON.stringify(smokeAttachments)}; const smokeAttachments = ${JSON.stringify(smokeAttachments)};
const smokeSuggestionAction = ${JSON.stringify(process.env.QJCLAW_SMOKE_SUGGESTION_ACTION?.trim() ?? "")};
if (smokeBaseUrl) { if (smokeBaseUrl) {
const current = await api.config.load(); const current = await api.config.load();
await api.config.save({ await api.config.save({
...@@ -872,6 +881,48 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise< ...@@ -872,6 +881,48 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise<
settingsSave: saved settingsSave: saved
}; };
})() })()
: smokeSuggestionAction
? await (async () => {
const suggestionState = await actions.resolveHomeIntentSuggestion();
if (!suggestionState.visible) {
throw new Error("Renderer smoke did not surface a home intent suggestion for action " + smokeSuggestionAction + ".");
}
if (smokeSuggestionAction === "continue-home") {
const continued = await actions.continueHomeIntentSuggestion();
return {
mode: "chat",
sessionId: "",
skillId: selectedSkillId,
homeIntentSuggestion: suggestionState,
homeIntentAction: smokeSuggestionAction,
homeIntentActionResult: continued
};
}
if (smokeSuggestionAction === "switch-expert") {
const switched = await actions.switchHomeIntentSuggestion();
return {
mode: "chat",
sessionId: "",
skillId: selectedSkillId,
homeIntentSuggestion: suggestionState,
homeIntentAction: smokeSuggestionAction,
homeIntentActionResult: switched
};
}
if (smokeSuggestionAction === "dismiss") {
const dismissed = await actions.dismissHomeIntentSuggestion();
return {
mode: "chat",
sessionId: "",
skillId: selectedSkillId,
homeIntentSuggestion: suggestionState,
homeIntentAction: smokeSuggestionAction,
homeIntentActionResult: dismissed,
homeIntentDismissed: true
};
}
throw new Error("Unsupported smoke suggestion action: " + smokeSuggestionAction);
})()
: await actions.sendConversationPrompt(${JSON.stringify(prompt)}, { : await actions.sendConversationPrompt(${JSON.stringify(prompt)}, {
mode: ${JSON.stringify(smokeViewMode)}, mode: ${JSON.stringify(smokeViewMode)},
projectId: ${JSON.stringify(smokeProjectId)}, projectId: ${JSON.stringify(smokeProjectId)},
...@@ -882,6 +933,7 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise< ...@@ -882,6 +933,7 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise<
prompt: ${JSON.stringify(prompt)}, prompt: ${JSON.stringify(prompt)},
smokeViewMode: ${JSON.stringify(smokeViewMode)}, smokeViewMode: ${JSON.stringify(smokeViewMode)},
smokeProjectId: ${JSON.stringify(smokeProjectId)}, smokeProjectId: ${JSON.stringify(smokeProjectId)},
smokeSuggestionAction,
smokeAttachments, smokeAttachments,
runtimeCloudStatus, runtimeCloudStatus,
runtimeCloudFetch, runtimeCloudFetch,
...@@ -900,6 +952,10 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise< ...@@ -900,6 +952,10 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise<
selectedSkillId: actionResult.skillId || selectedSkillId, selectedSkillId: actionResult.skillId || selectedSkillId,
initialSessionId: actionResult.sessionId, initialSessionId: actionResult.sessionId,
settingsSave: actionResult.settingsSave, settingsSave: actionResult.settingsSave,
homeIntentSuggestion: actionResult.homeIntentSuggestion,
homeIntentAction: actionResult.homeIntentAction,
homeIntentActionResult: actionResult.homeIntentActionResult,
homeIntentDismissed: actionResult.homeIntentDismissed,
system, system,
health: gatewayProbe.health, health: gatewayProbe.health,
status: gatewayProbe.status status: gatewayProbe.status
...@@ -935,6 +991,8 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise< ...@@ -935,6 +991,8 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise<
return; return;
} }
const streamState = smokeViewMode === "skills" const streamState = smokeViewMode === "skills"
? await waitForRendererSmokeState(window, 5000)
: sendResult.homeIntentDismissed
? await waitForRendererSmokeState(window, 5000) ? await waitForRendererSmokeState(window, 5000)
: await waitForRendererStreamSmoke(window, resolveSmokeStreamTimeoutMs()); : await waitForRendererStreamSmoke(window, resolveSmokeStreamTimeoutMs());
if (smokeViewMode === "skills") { if (smokeViewMode === "skills") {
...@@ -966,6 +1024,19 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise< ...@@ -966,6 +1024,19 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise<
app.quit(); app.quit();
return; return;
} }
if (sendResult.homeIntentDismissed) {
const finalState = await waitForRendererSmokeState(window, 5000);
result.sendResult = sendResult;
result.finalState = finalState;
result.ok = true;
await trace("runSmokeTest:home-intent-dismiss-success");
result.finishedAt = new Date().toISOString();
await trace("runSmokeTest:writing-output");
await writeFile(outputPath, JSON.stringify(result, null, 2), "utf8");
await trace("runSmokeTest:output-written");
app.quit();
return;
}
if (!streamState?.streamSmoke) { if (!streamState?.streamSmoke) {
throw new Error("Renderer stream smoke did not reach a terminal state."); throw new Error("Renderer stream smoke did not reach a terminal state.");
} }
...@@ -1036,7 +1107,12 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise< ...@@ -1036,7 +1107,12 @@ async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise<
logCount: logs.length, logCount: logs.length,
diagnostics, diagnostics,
health, health,
status status,
currentProjectId: state?.ui?.currentProjectId,
homeIntentSuggestionVisible: state?.ui?.homeIntentSuggestionVisible,
homeIntentSuggestionProjectId: state?.ui?.homeIntentSuggestionProjectId,
homeIntentSuggestionProjectName: state?.ui?.homeIntentSuggestionProjectName,
pendingHomeIntentPrompt: state?.ui?.pendingHomeIntentPrompt
}; };
})()`); })()`);
await trace("runSmokeTest:post-stream-script-finished"); await trace("runSmokeTest:post-stream-script-finished");
......
...@@ -23,6 +23,7 @@ export class ProjectChatTargetResolverService { ...@@ -23,6 +23,7 @@ export class ProjectChatTargetResolverService {
async resolve(sessionId: string, prompt: string, selectedSkillId?: string | null): Promise<ResolvedProjectChatTarget> { async resolve(sessionId: string, prompt: string, selectedSkillId?: string | null): Promise<ResolvedProjectChatTarget> {
const sessionState = await this.projectStore.getSessionState(sessionId); const sessionState = await this.projectStore.getSessionState(sessionId);
const selectedSkill = selectedSkillId?.trim() || null; const selectedSkill = selectedSkillId?.trim() || null;
void prompt;
if (selectedSkill || sessionState.projectId !== BUILTIN_HOME_PROJECT_ID) { if (selectedSkill || sessionState.projectId !== BUILTIN_HOME_PROJECT_ID) {
return { return {
...@@ -33,25 +34,21 @@ export class ProjectChatTargetResolverService { ...@@ -33,25 +34,21 @@ export class ProjectChatTargetResolverService {
}; };
} }
const route = await this.projectIntentRouter.resolve(prompt, sessionState.projectId);
if (!route || route.projectId === sessionState.projectId) {
return { return {
sessionState, sessionState,
route, route: null,
autoRouted: false, autoRouted: false,
previousProjectId: sessionState.projectId previousProjectId: sessionState.projectId
}; };
} }
await this.projectStore.setActiveProject(route.projectId); async resolveIntentSuggestion(prompt: string, currentProjectId?: string | null): Promise<ProjectIntentRoute | null> {
const sessions = await this.projectStore.listSessions(route.projectId); const normalizedCurrentProjectId = currentProjectId?.trim() || BUILTIN_HOME_PROJECT_ID;
const targetSession = sessions[0] ?? await this.projectStore.createSession(undefined, route.projectId); const route = await this.projectIntentRouter.resolve(prompt, normalizedCurrentProjectId);
if (!route || route.projectId === BUILTIN_HOME_PROJECT_ID || route.projectId === normalizedCurrentProjectId) {
return null;
}
return { return route;
sessionState: await this.projectStore.getSessionState(targetSession.id),
route,
autoRouted: true,
previousProjectId: sessionState.projectId
};
} }
} }
...@@ -267,11 +267,11 @@ export async function startSmokeCloudApiServer(baseUrl: string, token: string, r ...@@ -267,11 +267,11 @@ export async function startSmokeCloudApiServer(baseUrl: string, token: string, r
sendJson(200, { sendJson(200, {
changed: true, changed: true,
employee_id: "employee-smoke", employee_id: "employee-smoke",
name: "Smoke Lobster", name: "千匠问天 Smoke",
status: "running", status: "running",
deployment_type: "local", deployment_type: "local",
persona_prompt: "You are a smoke validation employee. Respond directly to the user.", persona_prompt: "You are 千匠问天, a highly capable general assistant. Help with Excel and spreadsheet analysis, information retrieval, research synthesis, task breakdown, and daily collaboration. When the user explicitly asks for Xiaohongshu or Douyin operations help, you may switch into the corresponding platform expert perspective. Default to the general assistant role unless the user clearly requests a platform-specific expert.",
welcome_message: "Hello, the smoke runtime configuration is connected.", welcome_message: "你好,我是千匠问天。可以帮你处理 Excel、整理信息、检索资料、拆解任务;如果你需要,我也可以切换到小红书或抖音运营专家模式,协助你完成内容策划与运营工作。",
work_hours: { work_hours: {
start: null, start: null,
end: null, end: null,
......
param(
[int]$GatewayPort = 18889,
[string]$GatewayToken = 'qjc-bundled-runtime-token',
[int]$SmokePort = 4318,
[string]$SmokeToken = 'smoke-token',
[string]$BaseOutputDir,
[int]$TimeoutSeconds = 180,
[switch]$SkipMaterializeRuntime
)
$ErrorActionPreference = 'Stop'
function Write-Utf8File {
param([string]$FilePath, [string]$Content)
$encoding = New-Object System.Text.UTF8Encoding $false
[System.IO.Directory]::CreateDirectory([System.IO.Path]::GetDirectoryName($FilePath)) | Out-Null
[System.IO.File]::WriteAllText($FilePath, $Content, $encoding)
}
function New-ExpertFixtureProject {
param(
[string]$ProjectsRoot,
[string]$ProjectId,
[string]$ProjectName,
[string]$Platform,
[string]$Description,
[string]$ReadmeBody,
[string]$UpdatedAt
)
$projectRoot = Join-Path $ProjectsRoot $ProjectId
New-Item -ItemType Directory -Force -Path $projectRoot, (Join-Path $projectRoot 'memory') | Out-Null
$projectPayload = [ordered]@{
id = $ProjectId
name = $ProjectName
description = $Description
platform = $Platform
ready = $true
updatedAt = $UpdatedAt
boundSkillIds = @()
workspaceEntryEnabled = $true
}
Write-Utf8File (Join-Path $projectRoot 'project.json') ($projectPayload | ConvertTo-Json -Depth 8)
Write-Utf8File (Join-Path $projectRoot 'README.md') ("# $ProjectName`n`n$ReadmeBody")
Write-Utf8File (Join-Path $projectRoot 'AGENTS.md') "# $ProjectName`n`nPassive expert fixture for desktop home intent suggestion smoke."
}
function Initialize-SmokeUserData {
param(
[string]$UserDataPath
)
$projectsRoot = Join-Path $UserDataPath 'projects'
$manifestsRoot = Join-Path $UserDataPath 'manifests'
if (Test-Path $UserDataPath) {
Remove-Item $UserDataPath -Recurse -Force -ErrorAction SilentlyContinue
}
New-Item -ItemType Directory -Force -Path $UserDataPath, $projectsRoot, $manifestsRoot | Out-Null
New-ExpertFixtureProject -ProjectsRoot $projectsRoot -ProjectId 'xhs' -ProjectName 'Xiaohongshu Expert Workspace' -Platform 'xiaohongshu' -Description '小红书运营专家,负责小红书文案、笔记、发布时间和发布流程。' -ReadmeBody '小红书专家夹具。处理小红书护肤笔记、文案、发布时间建议和发布任务。' -UpdatedAt '2026-04-16T00:00:00.000Z'
New-ExpertFixtureProject -ProjectsRoot $projectsRoot -ProjectId 'douyin-expert-smoke' -ProjectName 'Douyin Expert Workspace' -Platform 'douyin' -Description '抖音运营专家,负责抖音脚本、镜头节奏和发布建议。' -ReadmeBody '抖音专家夹具。处理抖音脚本、镜头节奏和口播任务。' -UpdatedAt '2026-04-16T00:01:00.000Z'
Write-Utf8File (Join-Path $manifestsRoot 'active-project.json') (@{ projectId = 'douyin-expert-smoke' } | ConvertTo-Json -Depth 3)
}
function Invoke-HomeIntentScenario {
param(
[string]$ScenarioName,
[string]$SuggestionAction,
[string]$Prompt,
[string]$BaseOutputDir,
[string]$ElectronSmokeScript,
[int]$SmokePort,
[string]$SmokeToken,
[int]$TimeoutSeconds
)
$scenarioRoot = Join-Path $BaseOutputDir $ScenarioName
$userDataPath = Join-Path $scenarioRoot 'user-data'
$logsPath = Join-Path $scenarioRoot 'logs'
$smokeOutput = Join-Path $scenarioRoot 'result.json'
Initialize-SmokeUserData -UserDataPath $userDataPath
if (Test-Path $logsPath) {
Remove-Item $logsPath -Recurse -Force -ErrorAction SilentlyContinue
}
New-Item -ItemType Directory -Force -Path $scenarioRoot, $logsPath | Out-Null
powershell -ExecutionPolicy Bypass -File $ElectronSmokeScript @(
'-SmokeOutput', $smokeOutput,
'-SmokePort', $SmokePort,
'-SmokeToken', $SmokeToken,
'-UserDataPath', $userDataPath,
'-LogsPath', $logsPath,
'-RuntimeMode', 'bundled-runtime',
'-ExpectBundledRuntime',
'-PreserveUserData',
'-SmokePrompt', $Prompt,
'-SmokeViewMode', 'chat',
'-SmokeSuggestionAction', $SuggestionAction,
'-TimeoutSeconds', $TimeoutSeconds
)
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
$summary = & node -e @"
const fs = require('fs');
const path = require('path');
const [scenarioName, smokeOutput, expectedAction, expectedPrompt] = process.argv.slice(1);
const result = JSON.parse(fs.readFileSync(smokeOutput, 'utf8'));
if (!result.ok) {
throw new Error(result.error || (scenarioName + ' smoke failed.'));
}
const sendResult = result.sendResult || {};
const streamSmoke = sendResult.streamSmoke || {};
const suggestion = sendResult.homeIntentSuggestion || {};
const actionResult = sendResult.homeIntentActionResult || {};
const finalState = result.finalState || {};
const finalWorkspace = finalState.workspaceSummary || {};
const finalSessionId = String(sendResult.sessionId || '');
if (String(sendResult.homeIntentAction || '') !== expectedAction) {
throw new Error('Unexpected home intent action: ' + String(sendResult.homeIntentAction || ''));
}
if (String(suggestion.projectId || '') !== 'xhs') {
throw new Error('Suggestion did not target xhs: ' + String(suggestion.projectId || ''));
}
if (String(suggestion.pendingPrompt || '') !== expectedPrompt) {
throw new Error('Suggestion pending prompt mismatch.');
}
if (String(sendResult.prompt || '') !== expectedPrompt) {
throw new Error('Final send prompt mismatch.');
}
if (String(sendResult.smokeViewMode || '') !== 'chat') {
throw new Error('Smoke did not run in chat view.');
}
if (String(sendResult.smokeProjectId || '')) {
throw new Error('Smoke unexpectedly targeted a fixed project: ' + String(sendResult.smokeProjectId || ''));
}
if (String(streamSmoke.phase || '') !== 'completed') {
throw new Error('Stream did not complete: ' + String(streamSmoke.phase || ''));
}
if (sendResult.homeIntentDismissed) {
throw new Error('Suggestion was unexpectedly marked dismissed.');
}
if (sendResult.homeIntentSuggestionVisible) {
throw new Error('Suggestion remained visible after decision.');
}
if (sendResult.pendingHomeIntentPrompt) {
throw new Error('Pending home intent prompt was not cleared.');
}
if (expectedAction === 'continue-home') {
if (!actionResult.continued) {
throw new Error('Continue-home action did not report continued=true.');
}
if (!finalSessionId.startsWith('project:home-chat:')) {
throw new Error('Continue-home did not send inside home-chat: ' + finalSessionId);
}
if (String(sendResult.currentProjectId || '') !== 'home-chat') {
throw new Error('Continue-home post-stream currentProjectId mismatch: ' + String(sendResult.currentProjectId || ''));
}
if (String(finalWorkspace.currentProjectId || '') !== 'home-chat') {
throw new Error('Continue-home final workspace project mismatch: ' + String(finalWorkspace.currentProjectId || ''));
}
} else if (expectedAction === 'switch-expert') {
if (!actionResult.switched || String(actionResult.projectId || '') !== 'xhs') {
throw new Error('Switch-expert action did not report switched xhs.');
}
if (!finalSessionId.startsWith('project:xhs:')) {
throw new Error('Switch-expert did not send inside xhs: ' + finalSessionId);
}
if (String(sendResult.currentProjectId || '') !== 'xhs') {
throw new Error('Switch-expert post-stream currentProjectId mismatch: ' + String(sendResult.currentProjectId || ''));
}
if (String(finalWorkspace.currentProjectId || '') !== 'xhs') {
throw new Error('Switch-expert final workspace project mismatch: ' + String(finalWorkspace.currentProjectId || ''));
}
} else {
throw new Error('Unsupported expected action: ' + expectedAction);
}
console.log(JSON.stringify({
ok: true,
scenarioName,
smokeOutput,
action: sendResult.homeIntentAction || null,
suggestedProjectId: suggestion.projectId || null,
suggestedProjectName: suggestion.projectName || null,
sessionId: sendResult.sessionId || null,
currentProjectId: finalWorkspace.currentProjectId || null,
streamPhase: streamSmoke.phase || null,
messageCount: sendResult.messageCount || null
}, null, 2));
"@ $ScenarioName $smokeOutput $SuggestionAction $Prompt
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Write-Output $summary
}
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
if (-not $BaseOutputDir) {
$BaseOutputDir = Join-Path $repoRoot '.tmp\desktop-home-intent-suggestion-smoke'
}
$BaseOutputDir = [System.IO.Path]::GetFullPath($BaseOutputDir)
$electronSmokeScript = Join-Path $repoRoot 'build\scripts\electron-smoke.ps1'
$homeIntentPrompt = '帮我写一个小红书护肤笔记并给发布时间建议'
if (Test-Path $BaseOutputDir) {
Remove-Item $BaseOutputDir -Recurse -Force -ErrorAction SilentlyContinue
}
New-Item -ItemType Directory -Force -Path $BaseOutputDir | Out-Null
if (-not $SkipMaterializeRuntime) {
Write-Host "Materializing bundled runtime payload on port $GatewayPort"
powershell -ExecutionPolicy Bypass -File (Join-Path $repoRoot 'build\scripts\materialize-runtime-payload.ps1') -GatewayPort $GatewayPort -GatewayToken $GatewayToken
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
}
Invoke-HomeIntentScenario -ScenarioName 'continue-home' -SuggestionAction 'continue-home' -Prompt $homeIntentPrompt -BaseOutputDir $BaseOutputDir -ElectronSmokeScript $electronSmokeScript -SmokePort $SmokePort -SmokeToken $SmokeToken -TimeoutSeconds $TimeoutSeconds
Invoke-HomeIntentScenario -ScenarioName 'switch-expert' -SuggestionAction 'switch-expert' -Prompt $homeIntentPrompt -BaseOutputDir $BaseOutputDir -ElectronSmokeScript $electronSmokeScript -SmokePort $SmokePort -SmokeToken $SmokeToken -TimeoutSeconds $TimeoutSeconds
...@@ -19,6 +19,8 @@ param( ...@@ -19,6 +19,8 @@ param(
[string]$WorkspaceMarkerFile = 'AGENT.md', [string]$WorkspaceMarkerFile = 'AGENT.md',
[string]$SmokeViewMode = 'chat', [string]$SmokeViewMode = 'chat',
[string]$SmokeProjectId, [string]$SmokeProjectId,
[ValidateSet('continue-home', 'switch-expert', 'dismiss', '')]
[string]$SmokeSuggestionAction = '',
[string]$ExpectedBundleSourceUrl, [string]$ExpectedBundleSourceUrl,
[string]$ExpectedBundleConfigVersion, [string]$ExpectedBundleConfigVersion,
[string]$ExpectedBundleFileName, [string]$ExpectedBundleFileName,
...@@ -195,7 +197,14 @@ if ($PSBoundParameters.ContainsKey('SmokeViewMode')) { ...@@ -195,7 +197,14 @@ if ($PSBoundParameters.ContainsKey('SmokeViewMode')) {
if ($PSBoundParameters.ContainsKey('SmokeProjectId')) { if ($PSBoundParameters.ContainsKey('SmokeProjectId')) {
$env:QJCLAW_SMOKE_PROJECT_ID = $SmokeProjectId $env:QJCLAW_SMOKE_PROJECT_ID = $SmokeProjectId
} }
if ($StartupOnly) { if ($PSBoundParameters.ContainsKey('SmokeSuggestionAction')) {
if ([string]::IsNullOrWhiteSpace($SmokeSuggestionAction)) {
Remove-Item Env:QJCLAW_SMOKE_SUGGESTION_ACTION -ErrorAction SilentlyContinue
} else {
$env:QJCLAW_SMOKE_SUGGESTION_ACTION = $SmokeSuggestionAction
}
}
if ($StartupOnly) {
$env:QJCLAW_SMOKE_STARTUP_ONLY = '1' $env:QJCLAW_SMOKE_STARTUP_ONLY = '1'
} else { } else {
Remove-Item Env:QJCLAW_SMOKE_STARTUP_ONLY -ErrorAction SilentlyContinue Remove-Item Env:QJCLAW_SMOKE_STARTUP_ONLY -ErrorAction SilentlyContinue
......
...@@ -26,6 +26,12 @@ if ($LASTEXITCODE -ne 0) { ...@@ -26,6 +26,12 @@ if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE exit $LASTEXITCODE
} }
Write-Host 'Running desktop home-intent suggestion smoke'
powershell -ExecutionPolicy Bypass -File (Join-Path $repoRoot 'build\scripts\desktop-home-intent-suggestion-smoke.ps1') -TimeoutSeconds $TimeoutSeconds -GatewayPort $GatewayPort -GatewayToken $GatewayToken -SkipMaterializeRuntime
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Write-Host 'Running project-routing isolation smoke' Write-Host 'Running project-routing isolation smoke'
powershell -ExecutionPolicy Bypass -File (Join-Path $repoRoot 'build\scripts\project-routing-smoke.ps1') powershell -ExecutionPolicy Bypass -File (Join-Path $repoRoot 'build\scripts\project-routing-smoke.ps1')
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
......
...@@ -130,10 +130,20 @@ description: douyin script writing skill ...@@ -130,10 +130,20 @@ description: douyin script writing skill
assert(preservedTarget.sessionState.sessionId === seedSession.id, "Chat target resolver should reuse the original non-home project session."); assert(preservedTarget.sessionState.sessionId === seedSession.id, "Chat target resolver should reuse the original non-home project session.");
const resolvedTarget = await chatTargetResolver.resolve(homeSession.id, publishPrompt, null); const resolvedTarget = await chatTargetResolver.resolve(homeSession.id, publishPrompt, null);
assert(resolvedTarget.autoRouted, "Chat target resolver did not auto-route the home chat request."); assert(!resolvedTarget.autoRouted, "Chat target resolver should keep the home chat request inside home-chat.");
assert(resolvedTarget.previousProjectId === "home-chat", "Chat target resolver should report home-chat as the previous project."); assert(resolvedTarget.previousProjectId === "home-chat", "Chat target resolver should report home-chat as the previous project.");
assert(resolvedTarget.sessionState.projectId === xiaohongshu.id, "Chat target resolver did not rebind the home chat request into the Xiaohongshu workspace session."); assert(resolvedTarget.sessionState.projectId === "home-chat", "Chat target resolver should preserve the home-chat project for default chat requests.");
assert(resolvedTarget.sessionState.sessionId !== homeSession.id, "Chat target resolver should not reuse the original home session for a Xiaohongshu request."); assert(resolvedTarget.sessionState.sessionId === homeSession.id, "Chat target resolver should reuse the original home session for default chat requests.");
const homeIntentSuggestion = await chatTargetResolver.resolveIntentSuggestion(publishPrompt, "home-chat");
assert(homeIntentSuggestion?.projectId === xiaohongshu.id, "Home chat suggestion should recommend the Xiaohongshu workspace for Xiaohongshu publish prompts.");
const sameProjectSuggestion = await chatTargetResolver.resolveIntentSuggestion(publishPrompt, xiaohongshu.id);
assert(sameProjectSuggestion === null, "Suggestion lookup should not recommend switching when the current project already matches the resolved workspace.");
const ambiguousPrompt = "帮我看一下这周的工作安排并整理成表格";
const ambiguousSuggestion = await chatTargetResolver.resolveIntentSuggestion(ambiguousPrompt, "home-chat");
assert(ambiguousSuggestion === null, "Home chat suggestion should stay silent for generic non-platform prompts.");
const pipelineRoute = await skillRouter.resolve(xiaohongshu.id, publishPrompt); const pipelineRoute = await skillRouter.resolve(xiaohongshu.id, publishPrompt);
assert(pipelineRoute?.skillId === "xiaohongshu-pipeline", "Skill router did not choose the Xiaohongshu pipeline skill for the publish-style request."); assert(pipelineRoute?.skillId === "xiaohongshu-pipeline", "Skill router did not choose the Xiaohongshu pipeline skill for the publish-style request.");
...@@ -150,9 +160,10 @@ description: douyin script writing skill ...@@ -150,9 +160,10 @@ description: douyin script writing skill
id: "xiaohongshu-plugin", id: "xiaohongshu-plugin",
name: "Xiaohongshu Plugin" name: "Xiaohongshu Plugin"
}, null, 2), "utf8"); }, null, 2), "utf8");
const xiaohongshuSession = await projectStore.createSession("Xiaohongshu Session", xiaohongshu.id);
const xiaohongshuSnapshot = await projectContextService.getSnapshot(xiaohongshu.id); const xiaohongshuSnapshot = await projectContextService.getSnapshot(xiaohongshu.id);
const workspaceEntryDecision = await projectExecutionRouter.decide({ const workspaceEntryDecision = await projectExecutionRouter.decide({
sessionId: resolvedTarget.sessionState.sessionId, sessionId: xiaohongshuSession.id,
projectId: xiaohongshu.id, projectId: xiaohongshu.id,
projectRoot: xiaohongshuProjectRoot, projectRoot: xiaohongshuProjectRoot,
userPrompt: publishPrompt, userPrompt: publishPrompt,
...@@ -197,6 +208,10 @@ description: douyin script writing skill ...@@ -197,6 +208,10 @@ description: douyin script writing skill
preservedSessionProjectId: preservedTarget.sessionState.projectId, preservedSessionProjectId: preservedTarget.sessionState.projectId,
routedSessionId: resolvedTarget.sessionState.sessionId, routedSessionId: resolvedTarget.sessionState.sessionId,
routedSessionProjectId: resolvedTarget.sessionState.projectId, routedSessionProjectId: resolvedTarget.sessionState.projectId,
homeSuggestionProjectId: homeIntentSuggestion?.projectId ?? null,
homeSuggestionReason: homeIntentSuggestion?.reason ?? null,
sameProjectSuggestionSuppressed: sameProjectSuggestion === null,
ambiguousSuggestionSuppressed: ambiguousSuggestion === null,
pipelineSkillId: pipelineRoute?.skillId ?? null, pipelineSkillId: pipelineRoute?.skillId ?? null,
pipelineRouteReason: pipelineRoute?.reason ?? null, pipelineRouteReason: pipelineRoute?.reason ?? null,
writerSkillId: writerRoute?.skillId ?? null, writerSkillId: writerRoute?.skillId ?? null,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment