diff --git a/docs-ai/053-agent-profiles/000-plan.md b/docs-ai/053-agent-profiles/000-plan.md index 0c396b01..7f0db1b2 100644 --- a/docs-ai/053-agent-profiles/000-plan.md +++ b/docs-ai/053-agent-profiles/000-plan.md @@ -330,6 +330,9 @@ agent 列表不变。 ## Amendments +- 2026-07-30 — Review 四轮(P2):palette 的 Launch Agent 条目工厂改用与 launch + action 相同的 action-target 解析(selectedTerminalWorktree ?? Canvas 聚焦卡), + Canvas 模式下条目不再缺席;补 Canvas 聚焦卡回归测试。 - 2026-07-30 — Review 三轮(P2/P3):(a) surface 的 launch 身份增加 runtime,config root 只对**同 runtime 的检测**生效——bound pane 内手动启动其他 agent 时不再张冠 李戴、也不再压制默认布局扫描;(b) 删除确认的判断从"当前 bindsDedicatedHome 意愿" diff --git a/supacode/Features/CommandPalette/Reducer/CommandPaletteFeature.swift b/supacode/Features/CommandPalette/Reducer/CommandPaletteFeature.swift index dffafa52..14b0aa13 100644 --- a/supacode/Features/CommandPalette/Reducer/CommandPaletteFeature.swift +++ b/supacode/Features/CommandPalette/Reducer/CommandPaletteFeature.swift @@ -262,7 +262,12 @@ struct CommandPaletteFeature { } items.append(contentsOf: customCommandItems(customCommands)) items.append(contentsOf: handoffCommandItems(repositories)) - items.append(contentsOf: agentProfileLaunchItems(repositories)) + items.append( + contentsOf: agentProfileLaunchItems( + repositories, + actionTargetWorktreeID: worktreeActionTargetID + ) + ) if let terminalWorktree = repositories.selectedTerminalWorktree { items.append( CommandPaletteItem( @@ -532,12 +537,20 @@ private func customCommandItems(_ commands: [EffectiveCustomCommand]) -> [Comman } } -/// Launch rows for enabled agent profiles: the selected worktree's -/// Recommended profile first, then list order. Dispatches the same single -/// profile-launch action as the toolbar Agents menu (docs-ai 053) — the -/// palette is an entry point, never a second launch path. Internal for tests. -func agentProfileLaunchItems(_ repositories: RepositoriesFeature.State) -> [CommandPaletteItem] { - guard let worktree = repositories.selectedTerminalWorktree else { return [] } +/// Launch rows for enabled agent profiles: the target worktree's Recommended +/// profile first, then list order. Dispatches the same single profile-launch +/// action as the toolbar Agents menu (docs-ai 053) — the palette is an entry +/// point, never a second launch path. The target mirrors the launch action's +/// resolution: the selected terminal worktree in Normal mode, the focused +/// Canvas card otherwise. Internal for tests. +func agentProfileLaunchItems( + _ repositories: RepositoriesFeature.State, + actionTargetWorktreeID: Worktree.ID? = nil +) -> [CommandPaletteItem] { + let target = + repositories.selectedTerminalWorktree + ?? actionTargetWorktreeID.flatMap { repositories.worktree(for: $0) } + guard let worktree = target else { return [] } @Shared(.userGlobalSettings) var globalSettings let profiles = globalSettings.agentProfiles let enabled = profiles.filter(\.isEnabled) diff --git a/supacodeTests/AppFeatureAgentProfileTests.swift b/supacodeTests/AppFeatureAgentProfileTests.swift index 568c7c8b..5c7e4428 100644 --- a/supacodeTests/AppFeatureAgentProfileTests.swift +++ b/supacodeTests/AppFeatureAgentProfileTests.swift @@ -124,6 +124,29 @@ struct AppFeatureAgentProfileTests { } } + @Test(.dependencies) func paletteBuildsLaunchItemsForFocusedCanvasCard() { + let worktree = makeWorktree() + var repositories = makeRepositoriesState(worktree: worktree) + // Canvas: no selected terminal worktree, only the focused card passed as + // the action target. + repositories.selection = nil + let storage = SettingsTestStorage() + let localStorage = RepositoryLocalSettingsTestStorage() + withDependencies { + $0.settingsFileStorage = storage.storage + $0.repositoryLocalSettingsStorage = localStorage.storage + } operation: { + let profile = AgentProfile(name: "Codex", runtime: .codex) + @Shared(.userGlobalSettings) var settings + $settings.withLock { $0.agentProfiles = [profile] } + + #expect(agentProfileLaunchItems(repositories).isEmpty) + let items = agentProfileLaunchItems(repositories, actionTargetWorktreeID: worktree.id) + #expect(items.map(\.title) == ["Launch Agent: Codex"]) + #expect(items.first?.subtitle?.contains(worktree.name) == true) + } + } + @Test func launchedSurfaceEntryShowsProfileName() { var entry = ActiveAgentEntry( id: UUID(),