From 3b128443420d90a9821753d213941b31d36791bb Mon Sep 17 00:00:00 2001 From: KimiSwitch Dev Date: Thu, 9 Jul 2026 08:41:28 +0800 Subject: [PATCH] docs: add design specs and implementation plans for reload hint and global settings panel --- .../plans/2026-07-08-kimicode-reload-hint.md | 192 ++++ ...26-07-09-kimicode-global-settings-panel.md | 937 ++++++++++++++++++ .../2026-07-08-kimicode-reload-hint-design.md | 47 + ...9-kimicode-global-settings-panel-design.md | 253 +++++ 4 files changed, 1429 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-08-kimicode-reload-hint.md create mode 100644 docs/superpowers/plans/2026-07-09-kimicode-global-settings-panel.md create mode 100644 docs/superpowers/specs/2026-07-08-kimicode-reload-hint-design.md create mode 100644 docs/superpowers/specs/2026-07-09-kimicode-global-settings-panel-design.md diff --git a/docs/superpowers/plans/2026-07-08-kimicode-reload-hint.md b/docs/superpowers/plans/2026-07-08-kimicode-reload-hint.md new file mode 100644 index 0000000..41f5080 --- /dev/null +++ b/docs/superpowers/plans/2026-07-08-kimicode-reload-hint.md @@ -0,0 +1,192 @@ +# Kimi Code /reload Hint Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add an info icon tooltip next to the "switch provider" button, visible only in the Kimi Code tab, reminding users to run `/reload` after switching providers. + +**Architecture:** Extend `ProviderList` to receive the current `agent` and conditionally render a hint icon beside the switch button. Keep changes minimal and UI-only; no state or backend changes. + +**Tech Stack:** React 18, TypeScript, Tailwind CSS, custom i18n module (`src/i18n`). + +--- + +### Task 1: Add i18n translation keys + +**Files:** +- Modify: `src/i18n/zh.ts` +- Modify: `src/i18n/en.ts` + +- [ ] **Step 1: Add Chinese translation** + +Insert `switchReloadHint` into the "Provider list" section of `src/i18n/zh.ts`, e.g. after `switchTo`: + +```ts + switchReloadHint: "切换供应商后,请在 Kimi Code 中执行 /reload 以生效。", +``` + +- [ ] **Step 2: Add English translation** + +Insert the matching key into `src/i18n/en.ts`: + +```ts + switchReloadHint: "After switching providers, run /reload in Kimi Code to apply the change.", +``` + +- [ ] **Step 3: Commit** + +```bash +git add src/i18n/zh.ts src/i18n/en.ts +git commit -m "i18n: add switchReloadHint for Kimi Code reload reminder" +``` + +--- + +### Task 2: Update ProviderList to accept agent and render hint + +**Files:** +- Modify: `src/components/ProviderList.tsx` + +- [ ] **Step 1: Import Agent type and update props** + +Add `Agent` to the import from `../types` and add `agent` to `ProviderListProps`: + +```tsx +import { useTranslation } from "../i18n"; +import type { Agent, Model, Provider } from "../types"; + +interface ProviderListProps { + providers: Provider[]; + defaultModel: string | null; + models: Record; + onEdit: (name: string) => void; + onDelete: (name: string) => void; + onAdd: () => void; + onSwitchProvider: (name: string) => void; + agent: Agent; +} +``` + +- [ ] **Step 2: Destructure agent in component signature** + +```tsx +export function ProviderList({ + providers, + defaultModel, + models, + onEdit, + onDelete, + onAdd, + onSwitchProvider, + agent, +}: ProviderListProps) { +``` + +- [ ] **Step 3: Render hint icon next to the switch button** + +In the supplier card actions area, place an info icon immediately after the "切换使用" / "Switch to" button, rendered only when `agent === "kimi_code"`: + +```tsx + + {agent === "kimi_code" && ( + + ⓘ + + )} +``` + +- [ ] **Step 4: Commit** + +```bash +git add src/components/ProviderList.tsx +git commit -m "feat: show /reload hint next to switch provider button in Kimi Code tab" +``` + +--- + +### Task 3: Pass agent from App to ProviderList + +**Files:** +- Modify: `src/App.tsx` + +- [ ] **Step 1: Add agent prop to ProviderList usage** + +Locate the `` call in `src/App.tsx` and add `agent={agent}`: + +```tsx + { + setEditingProvider(name); + setView("edit"); + }} + onDelete={handleDeleteProvider} + onAdd={handleAddProvider} + onSwitchProvider={handleSwitchProvider} + agent={agent} + /> +``` + +- [ ] **Step 2: Commit** + +```bash +git add src/App.tsx +git commit -m "chore: pass current agent to ProviderList" +``` + +--- + +### Task 4: Verify with type check and manual test + +- [ ] **Step 1: Run TypeScript compilation** + +```bash +npm run build +``` + +Expected: `tsc` completes without errors and Vite builds successfully. + +- [ ] **Step 2: Run the app** + +```bash +npm run tauri-dev +``` + +- [ ] **Step 3: Manual verification** + +1. Select the **Kimi Code** tab. +2. Hover over the `ⓘ` icon next to any provider's "切换使用" / "Switch to" button. +3. Confirm the tooltip reads: + - 中文:`切换供应商后,请在 Kimi Code 中执行 /reload 以生效。` + - English: `After switching providers, run /reload in Kimi Code to apply the change.` +4. Switch to the **Pi** tab and confirm the `ⓘ` icon is **not** present. + +- [ ] **Step 4: Commit verification notes (optional)** + +If any fixes were needed, commit them; otherwise no additional commit is required. + +--- + +## Self-Review + +- **Spec coverage:** Every design requirement (icon next to switch button, Kimi Code only, i18n, no new dependencies) maps to a task above. +- **Placeholder scan:** No TBD/TODO/fill-in-details present. +- **Type consistency:** `Agent` type is imported from `../types` and used consistently in props and conditional rendering. diff --git a/docs/superpowers/plans/2026-07-09-kimicode-global-settings-panel.md b/docs/superpowers/plans/2026-07-09-kimicode-global-settings-panel.md new file mode 100644 index 0000000..8ceb95b --- /dev/null +++ b/docs/superpowers/plans/2026-07-09-kimicode-global-settings-panel.md @@ -0,0 +1,937 @@ +# Kimi Code 全局配置面板与模型默认上下文 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a Kimi Code global settings panel inside ProviderEdit and auto-set `max_context_size` for new models based on a model-ID lookup table. + +**Architecture:** Extend the shared `Config` type with typed agent settings, add two pure helper modules (`agent-settings.ts` for `raw_other` IO and `model-defaults.ts` for context lookup), and render the panel in a new React component wired through `ProviderEdit`. + +**Tech Stack:** React 18, TypeScript, Tailwind CSS, custom i18n, Tauri v2 backend (no Rust changes). + +--- + +### Task 1: Add agent settings types + +**Files:** +- Modify: `src/types/index.ts` + +- [ ] **Step 1: Append new types** + +Insert at the end of `src/types/index.ts`: + +```ts +export interface ThinkingConfig { + enabled?: boolean; + effort?: "low" | "medium" | "high" | "max"; + keep?: "all" | false | 0 | "no" | "off" | "none" | null; +} + +export interface LoopControlConfig { + max_retries_per_step?: number; + reserved_context_size?: number; +} + +export interface BackgroundConfig { + max_running_tasks?: number; + keep_alive_on_exit?: boolean; +} + +export interface PermissionRule { + decision?: "allow" | "deny" | "ask"; + scope?: string; + pattern?: string; + reason?: string; +} + +export interface Hook { + event?: string; + matcher?: string; + command?: string; + timeout?: number; +} + +export interface AgentSettings { + thinking?: ThinkingConfig; + loop_control?: LoopControlConfig; + background?: BackgroundConfig; + permission?: { rules?: PermissionRule[] }; + hooks?: Hook[]; +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add src/types/index.ts +git commit -m "types: add AgentSettings and related Kimi Code config types" +``` + +--- + +### Task 2: Create model default context size lookup + +**Files:** +- Create: `src/lib/model-defaults.ts` + +- [ ] **Step 1: Create the lookup module** + +Create `src/lib/model-defaults.ts`: + +```ts +export const DEFAULT_MAX_CONTEXT_SIZE = 256000; + +interface ModelContextRule { + pattern: RegExp; + max_context_size: number; +} + +const MODEL_CONTEXT_RULES: ModelContextRule[] = [ + // Kimi + { pattern: /^kimi-for-coding$/i, max_context_size: 262144 }, + { pattern: /^kimi-k2\.5/i, max_context_size: 256000 }, + { pattern: /^kimi-k2/i, max_context_size: 256000 }, + { pattern: /^kimi-/i, max_context_size: 256000 }, + // GLM + { pattern: /^glm-5\.2/i, max_context_size: 1000000 }, + { pattern: /^glm-5\.1/i, max_context_size: 256000 }, + { pattern: /^glm-5/i, max_context_size: 256000 }, + { pattern: /^glm-4/i, max_context_size: 128000 }, + { pattern: /^glm-/i, max_context_size: 128000 }, + // MiniMax + { pattern: /^MiniMax-M3/i, max_context_size: 1000000 }, + { pattern: /^MiniMax-Text-01/i, max_context_size: 400000 }, + { pattern: /^MiniMax-/i, max_context_size: 256000 }, + // Qwen + { pattern: /^qwen2\.5/i, max_context_size: 128000 }, + { pattern: /^qwen-max/i, max_context_size: 128000 }, + { pattern: /^qwen-plus/i, max_context_size: 128000 }, + { pattern: /^qwen-turbo/i, max_context_size: 128000 }, + { pattern: /^qwen-coder/i, max_context_size: 128000 }, + { pattern: /^qwen-/i, max_context_size: 128000 }, + // DeepSeek + { pattern: /^deepseek-r1/i, max_context_size: 64000 }, + { pattern: /^deepseek-v3/i, max_context_size: 64000 }, + { pattern: /^deepseek-coder/i, max_context_size: 64000 }, + { pattern: /^deepseek-/i, max_context_size: 64000 }, + // Hunyuan + { pattern: /^hunyuan-pro/i, max_context_size: 32000 }, + { pattern: /^hunyuan-standard/i, max_context_size: 32000 }, + { pattern: /^hunyuan-lite/i, max_context_size: 32000 }, + { pattern: /^hunyuan-/i, max_context_size: 32000 }, + // Doubao + { pattern: /^doubao-pro/i, max_context_size: 128000 }, + { pattern: /^doubao-lite/i, max_context_size: 128000 }, + { pattern: /^doubao-vision/i, max_context_size: 128000 }, + { pattern: /^doubao-/i, max_context_size: 128000 }, + // ERNIE + { pattern: /^ernie-4\.0/i, max_context_size: 128000 }, + { pattern: /^ernie-3\.5/i, max_context_size: 128000 }, + { pattern: /^ernie-speed/i, max_context_size: 128000 }, + { pattern: /^ernie-lite/i, max_context_size: 128000 }, + { pattern: /^ernie-/i, max_context_size: 128000 }, + // Spark + { pattern: /^spark-v4/i, max_context_size: 32000 }, + { pattern: /^spark-v3\.5/i, max_context_size: 32000 }, + { pattern: /^spark-pro/i, max_context_size: 32000 }, + { pattern: /^spark-max/i, max_context_size: 32000 }, + { pattern: /^spark-/i, max_context_size: 32000 }, + // SenseChat + { pattern: /^sensechat-/i, max_context_size: 128000 }, + // Baichuan + { pattern: /^baichuan-4/i, max_context_size: 128000 }, + { pattern: /^baichuan-3/i, max_context_size: 128000 }, + { pattern: /^baichuan-/i, max_context_size: 128000 }, + // Yi + { pattern: /^yi-/i, max_context_size: 128000 }, + // Claude + { pattern: /^claude-opus/i, max_context_size: 200000 }, + { pattern: /^claude-sonnet/i, max_context_size: 200000 }, + { pattern: /^claude-haiku/i, max_context_size: 200000 }, + { pattern: /^claude-/i, max_context_size: 200000 }, + // OpenAI + { pattern: /^gpt-4\.1/i, max_context_size: 1047576 }, + { pattern: /^gpt-4o/i, max_context_size: 128000 }, + { pattern: /^gpt-4-turbo/i, max_context_size: 128000 }, + { pattern: /^gpt-4-/i, max_context_size: 128000 }, + // Gemini + { pattern: /^gemini-2\.0-flash/i, max_context_size: 1048576 }, + { pattern: /^gemini-1\.5-pro/i, max_context_size: 2097152 }, + { pattern: /^gemini-1\.5-flash/i, max_context_size: 1048576 }, + { pattern: /^gemini-/i, max_context_size: 1048576 }, +]; + +export function getDefaultMaxContextSize(modelId: string): number { + for (const rule of MODEL_CONTEXT_RULES) { + if (rule.pattern.test(modelId)) { + return rule.max_context_size; + } + } + return DEFAULT_MAX_CONTEXT_SIZE; +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add src/lib/model-defaults.ts +git commit -m "feat: add model default max_context_size lookup table" +``` + +--- + +### Task 3: Create agent settings IO helpers + +**Files:** +- Create: `src/lib/agent-settings.ts` + +- [ ] **Step 1: Create helper module** + +Create `src/lib/agent-settings.ts`: + +```ts +import type { AgentSettings } from "../types"; + +const DEFAULT_SETTINGS: AgentSettings = { + thinking: { + enabled: true, + effort: "medium", + keep: "all", + }, + loop_control: { + max_retries_per_step: 3, + reserved_context_size: 50000, + }, + background: { + keep_alive_on_exit: false, + }, + permission: { rules: [] }, + hooks: [], +}; + +function asRecord(value: unknown): Record { + if (value && typeof value === "object" && !Array.isArray(value)) { + return value as Record; + } + return {}; +} + +function getSection(rawOther: unknown, key: string): T | undefined { + const root = asRecord(rawOther); + const section = root[key]; + if (section === undefined || section === null) return undefined; + return section as T; +} + +export function getAgentSettings(rawOther: unknown): AgentSettings { + return { + thinking: { + ...DEFAULT_SETTINGS.thinking, + ...getSection(rawOther, "thinking"), + }, + loop_control: { + ...DEFAULT_SETTINGS.loop_control, + ...getSection(rawOther, "loop_control"), + }, + background: { + ...DEFAULT_SETTINGS.background, + ...getSection(rawOther, "background"), + }, + permission: { + rules: getSection(rawOther, "permission")?.rules ?? [], + }, + hooks: getSection(rawOther, "hooks") ?? [], + }; +} + +export function setAgentSettings( + rawOther: unknown, + patch: Partial +): unknown { + const root = { ...asRecord(rawOther) }; + const current = getAgentSettings(rawOther); + const next: AgentSettings = { + thinking: { ...current.thinking, ...patch.thinking }, + loop_control: { ...current.loop_control, ...patch.loop_control }, + background: { ...current.background, ...patch.background }, + permission: { rules: patch.permission?.rules ?? current.permission?.rules ?? [] }, + hooks: patch.hooks ?? current.hooks ?? [], + }; + + if (next.thinking) root.thinking = next.thinking; + if (next.loop_control) root.loop_control = next.loop_control; + if (next.background) root.background = next.background; + if (next.permission?.rules && next.permission.rules.length > 0) { + root.permission = next.permission; + } else { + delete root.permission; + } + if (next.hooks && next.hooks.length > 0) { + root.hooks = next.hooks; + } else { + delete root.hooks; + } + + return root; +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add src/lib/agent-settings.ts +git commit -m "feat: add agent settings read/write helpers" +``` + +--- + +### Task 4: Add i18n keys + +**Files:** +- Modify: `src/i18n/zh.ts` +- Modify: `src/i18n/en.ts` + +- [ ] **Step 1: Add Chinese keys** + +Insert into `src/i18n/zh.ts` in the "Agent / target" section: + +```ts + agentSettings: "全局配置", + enableThinking: "启用思考", + thinkingLevel: "思考等级", + thinkingKeep: "保留思考内容", + thinkingLow: "低", + thinkingMedium: "中", + thinkingHigh: "高", + thinkingMax: "最大", + thinkingContextHint: "启用思考会占用更多上下文,请确保模型上下文长度和预留空间足够。", + loopControlSettings: "循环控制", + maxRetriesPerStep: "单步重试次数", + reservedContextSize: "上下文预留大小", + backgroundSettings: "后台任务", + maxRunningTasks: "最大并发数", + keepAliveOnExit: "退出时保持运行", + permissionRules: "权限规则", + permissionDecision: "处置", + permissionPattern: "模式", + permissionAllow: "允许", + permissionDeny: "拒绝", + permissionAsk: "询问", + addRule: "+ 添加规则", + addCommonRules: "添加常用规则", + hooks: "生命周期钩子", + hookEvent: "事件", + hookMatcher: "匹配器", + hookCommand: "命令", + hookTimeout: "超时", + addHook: "+ 添加钩子", +``` + +- [ ] **Step 2: Add English keys** + +Insert matching keys into `src/i18n/en.ts`: + +```ts + agentSettings: "Global Settings", + enableThinking: "Enable thinking", + thinkingLevel: "Thinking level", + thinkingKeep: "Keep thinking content", + thinkingLow: "Low", + thinkingMedium: "Medium", + thinkingHigh: "High", + thinkingMax: "Max", + thinkingContextHint: "Thinking uses more context. Ensure the model context length and reserved size are sufficient.", + loopControlSettings: "Loop Control", + maxRetriesPerStep: "Max retries per step", + reservedContextSize: "Reserved context size", + backgroundSettings: "Background Tasks", + maxRunningTasks: "Max running tasks", + keepAliveOnExit: "Keep alive on exit", + permissionRules: "Permission Rules", + permissionDecision: "Decision", + permissionPattern: "Pattern", + permissionAllow: "Allow", + permissionDeny: "Deny", + permissionAsk: "Ask", + addRule: "+ Add rule", + addCommonRules: "Add common rules", + hooks: "Lifecycle Hooks", + hookEvent: "Event", + hookMatcher: "Matcher", + hookCommand: "Command", + hookTimeout: "Timeout", + addHook: "+ Add hook", +``` + +- [ ] **Step 3: Commit** + +```bash +git add src/i18n/zh.ts src/i18n/en.ts +git commit -m "i18n: add agent settings panel translations" +``` + +--- + +### Task 5: Build the AgentSettingsPanel component + +**Files:** +- Create: `src/components/AgentSettingsPanel.tsx` + +- [ ] **Step 1: Create the panel component** + +Create `src/components/AgentSettingsPanel.tsx`: + +```tsx +import { useTranslation } from "../i18n"; +import { getAgentSettings, setAgentSettings } from "../lib/agent-settings"; +import type { AgentSettings, PermissionRule, Hook } from "../types"; +import type { ReactNode } from "react"; +import type { TranslationKey } from "../i18n/zh"; + +interface AgentSettingsPanelProps { + rawOther: unknown; + onChange: (nextRawOther: unknown) => void; +} + +const THINKING_LEVELS = ["low", "medium", "high", "max"] as const; +const THINKING_LABELS: Record<(typeof THINKING_LEVELS)[number], TranslationKey> = { + low: "thinkingLow", + medium: "thinkingMedium", + high: "thinkingHigh", + max: "thinkingMax", +}; +const PERMISSION_DECISIONS = ["allow", "deny", "ask"] as const; +const PERMISSION_LABELS: Record<(typeof PERMISSION_DECISIONS)[number], TranslationKey> = { + allow: "permissionAllow", + deny: "permissionDeny", + ask: "permissionAsk", +}; +const COMMON_EVENTS = ["PreToolUse", "PostToolUse"] as const; + +export function AgentSettingsPanel({ rawOther, onChange }: AgentSettingsPanelProps) { + const { t } = useTranslation(); + const settings = getAgentSettings(rawOther); + + const update = (patch: Partial) => { + onChange(setAgentSettings(rawOther, patch)); + }; + + const updateThinking = (patch: Partial) => { + update({ thinking: { ...settings.thinking, ...patch } }); + }; + + const updateLoopControl = (patch: Partial) => { + update({ loop_control: { ...settings.loop_control, ...patch } }); + }; + + const updateBackground = (patch: Partial) => { + update({ background: { ...settings.background, ...patch } }); + }; + + const setRules = (rules: PermissionRule[]) => { + update({ permission: { rules } }); + }; + + const setHooks = (hooks: Hook[]) => { + update({ hooks }); + }; + + const thinkingEnabled = settings.thinking?.enabled ?? true; + + return ( +
+

{t("agentSettings")}

+ + {/* Thinking */} + + updateThinking({ enabled: checked })} + /> +
+ {t("thinkingLevel")} + ({ key: lvl, label: t(THINKING_LABELS[lvl]) }))} + value={settings.thinking?.effort ?? "medium"} + onChange={(effort) => updateThinking({ effort })} + disabled={!thinkingEnabled} + /> +
+ updateThinking({ keep: checked ? "all" : false })} + /> +

{t("thinkingContextHint")}

+
+ + {/* Loop Control */} + + updateLoopControl({ max_retries_per_step: v })} + /> + updateLoopControl({ reserved_context_size: v })} + /> + + + {/* Background */} + + updateBackground({ max_running_tasks: v })} + /> + updateBackground({ keep_alive_on_exit: checked })} + /> + + + {/* Permission Rules */} + +
+ {(settings.permission?.rules ?? []).map((rule, idx) => ( +
+ + { + const rules = [...(settings.permission?.rules ?? [])]; + rules[idx] = { ...rule, pattern: e.target.value }; + setRules(rules); + }} + /> + +
+ ))} +
+
+ + +
+
+ + {/* Hooks */} + +
+ {(settings.hooks ?? []).map((hook, idx) => ( +
+ { + const hooks = [...(settings.hooks ?? [])]; + hooks[idx] = { ...hook, event: e.target.value }; + setHooks(hooks); + }} + /> + + {COMMON_EVENTS.map((e) => + { + const hooks = [...(settings.hooks ?? [])]; + hooks[idx] = { ...hook, matcher: e.target.value }; + setHooks(hooks); + }} + /> + { + const hooks = [...(settings.hooks ?? [])]; + hooks[idx] = { ...hook, command: e.target.value }; + setHooks(hooks); + }} + /> + { + const hooks = [...(settings.hooks ?? [])]; + hooks[idx] = { ...hook, timeout: e.target.value === "" ? undefined : Number(e.target.value) }; + setHooks(hooks); + }} + /> + +
+ ))} +
+ +
+
+ ); +} + +function Card({ title, children }: { title: string; children: ReactNode }) { + return ( +
+

{title}

+ {children} +
+ ); +} + +function Checkbox({ + label, + checked, + disabled, + onChange, +}: { + label: string; + checked: boolean; + disabled?: boolean; + onChange: (checked: boolean) => void; +}) { + return ( + + ); +} + +function NumberField({ + label, + value, + onChange, +}: { + label: string; + value?: number; + onChange: (value: number | undefined) => void; +}) { + return ( + + ); +} + +function Segmented({ + options, + value, + onChange, + disabled, +}: { + options: { key: string; label: string }[]; + value: string; + onChange: (value: string) => void; + disabled?: boolean; +}) { + return ( +
+ {options.map((opt) => ( + + ))} +
+ ); +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add src/components/AgentSettingsPanel.tsx +git commit -m "feat: add AgentSettingsPanel component" +``` + +--- + +### Task 6: Wire AgentSettingsPanel into ProviderEdit + +**Files:** +- Modify: `src/components/ProviderEdit.tsx` + +- [ ] **Step 1: Update ProviderEditProps** + +Add `rawOther` and `onRawOtherChange` to the props interface: + +```tsx +interface ProviderEditProps { + agent: Agent; + provider: Provider; + models: Model[]; + defaultModel: string | null; + rawOther: unknown; + onRawOtherChange: (nextRawOther: unknown) => void; + onBack: () => void; + // ... existing callbacks +} +``` + +- [ ] **Step 2: Destructure new props** + +```tsx +export function ProviderEdit({ + agent, + provider, + models, + defaultModel, + rawOther, + onRawOtherChange, + onBack, + // ... existing +}: ProviderEditProps) { +``` + +- [ ] **Step 3: Render the panel in the model mapping tab** + +Locate the model mapping tab content (around the model table and "+ 添加模型映射" button) and insert `` at the bottom, conditionally for Kimi Code: + +```tsx + {agent === "kimi_code" && ( + + )} +``` + +- [ ] **Step 4: Commit** + +```bash +git add src/components/ProviderEdit.tsx +git commit -m "feat: render AgentSettingsPanel in ProviderEdit for Kimi Code" +``` + +--- + +### Task 7: Wire ProviderEdit to App.tsx + +**Files:** +- Modify: `src/App.tsx` + +- [ ] **Step 1: Pass rawOther and updater to ProviderEdit** + +Find the `` call and add: + +```tsx + + updateConfig((cfg) => ({ ...cfg, raw_other: nextRawOther })) + } + // ... existing props + /> +``` + +- [ ] **Step 2: Commit** + +```bash +git add src/App.tsx +git commit -m "chore: pass rawOther to ProviderEdit" +``` + +--- + +### Task 8: Apply smart max_context_size defaults when adding models + +**Files:** +- Modify: `src/App.tsx` + +- [ ] **Step 1: Import helper in App.tsx** + +In `src/App.tsx`, import: + +```tsx +import { getDefaultMaxContextSize } from "./lib/model-defaults"; +``` + +- [ ] **Step 2: Use lookup in onModelAdd** + +Find the `onModelAdd` prop passed to `` and update the default `max_context_size`: + +```tsx + onModelAdd={() => { + const providerModels = Object.values(config.models).filter( + (m) => m.provider === currentProvider.name + ); + const alias = `${currentProvider.name}-${providerModels.length + 1}`; + updateConfig((cfg) => ({ + ...cfg, + models: { + ...cfg.models, + [alias]: { + alias, + provider: currentProvider.name, + model: "", + max_context_size: getDefaultMaxContextSize(alias), + display_name: null, + role: null, + supports_1m: false, + capabilities: [], + }, + }, + })); + }} +``` + +- [ ] **Step 3: Use lookup in handleApplyProviderJson** + +Update the model creation loop in `handleApplyProviderJson`: + +```tsx + updatedModels[alias] = { + ...m, + alias, + provider: provider.name, + max_context_size: m.max_context_size ?? getDefaultMaxContextSize(m.model), + }; +``` + +- [ ] **Step 4: Commit** + +```bash +git add src/App.tsx +git commit -m "feat: apply smart default max_context_size when adding models" +``` + +--- + +### Task 9: Verify with build and manual checks + +- [ ] **Step 1: Type check** + +```bash +npm run build +``` + +Expected: `tsc` and Vite build succeed. + +- [ ] **Step 2: Run the app** + +```bash +npm run tauri-dev +``` + +- [ ] **Step 3: Manual verification** + +1. Kimi Code tab → edit a provider → "模型映射" tab → scroll to bottom. +2. Confirm "全局配置" panel shows 5 cards. +3. Toggle "启用思考" and verify effort/keep enable/disable. +4. Change thinking effort, save, open `~/.kimi-code/config.toml`, verify `[thinking]` block. +5. Add a permission rule and a hook, save, verify `[[permission.rules]]` and `[[hooks]]` in TOML. +6. Add a new model with alias `glm-5.2-xxx`, verify `max_context_size` defaults to `1000000`. +7. Add a new model with unknown alias, verify `max_context_size` defaults to `256000`. +8. Switch to Pi tab, confirm global settings panel is not visible. + +- [ ] **Step 4: Commit any fixes** + +If any fixes were needed during verification, commit them. + +--- + +## Self-Review + +- **Spec coverage:** Every requirement (5 cards, model defaults, i18n, Kimi-only visibility, manual override) maps to a task. +- **Placeholder scan:** No TBD/TODO/fill-in-details; all code is shown. +- **Type consistency:** `AgentSettings`, helper signatures, and component props match across tasks. diff --git a/docs/superpowers/specs/2026-07-08-kimicode-reload-hint-design.md b/docs/superpowers/specs/2026-07-08-kimicode-reload-hint-design.md new file mode 100644 index 0000000..53e1178 --- /dev/null +++ b/docs/superpowers/specs/2026-07-08-kimicode-reload-hint-design.md @@ -0,0 +1,47 @@ +# Kimi Code 选项卡 /reload 提示设计 + +## 背景 + +用户在 Kimi Code 选项卡下切换供应商后,Kimi Code CLI 需要执行 `/reload` 才能加载新的提供方配置。当前界面没有给出该提示,用户容易遗漏这一步。 + +## 目标 + +在 Kimi Code 选项卡下,给每个供应商的"切换使用"按钮旁边增加一个信息提示,告知用户切换供应商后需要执行 `/reload`。 + +## 设计 + +### 位置与交互 + +- 在 `ProviderList` 组件中,每个供应商卡片右侧的"切换使用"按钮旁边添加一个信息图标 `ⓘ`。 +- 使用原生 `title` 属性展示 tooltip,鼠标悬停时显示提示文案。 +- 仅在当前选中 `agent === "kimi_code"` 时显示该图标;Pi 选项卡下不显示。 + +### 文案 + +- 中文:`切换供应商后,请在 Kimi Code 中执行 /reload 以生效。` +- 英文:`After switching providers, run /reload in Kimi Code to apply the change.` + +### 样式 + +- 图标大小约 14px,颜色为 `text-gray-500`,hover 时变为 `text-gray-300`。 +- 图标与"切换使用"按钮间距约 8px(`gap-2`)。 +- 不引入额外依赖或自定义 tooltip 组件。 + +### 组件与数据流 + +- `src/components/ProviderList.tsx`: + - `ProviderListProps` 新增 `agent: Agent` 字段。 + - 根据 `agent` 决定是否渲染提示图标。 + - 图标 `title` 使用 `t("switchReloadHint")`。 +- `src/App.tsx`: + - 渲染 `` 时传入 `agent={agent}`。 +- `src/i18n/zh.ts` 与 `src/i18n/en.ts`: + - 新增键 `switchReloadHint`。 + +## 验收标准 + +- [ ] 切换到 Kimi Code 选项卡时,每个供应商的"切换使用"按钮旁出现 `ⓘ` 图标。 +- [ ] 鼠标悬停在图标上时,显示正确语言版本的 `/reload` 提示。 +- [ ] 切换到 Pi 选项卡时,该图标不显示。 +- [ ] 不引入新的运行时依赖。 +- [ ] TypeScript 编译与项目构建保持通过。 diff --git a/docs/superpowers/specs/2026-07-09-kimicode-global-settings-panel-design.md b/docs/superpowers/specs/2026-07-09-kimicode-global-settings-panel-design.md new file mode 100644 index 0000000..e9ecbf7 --- /dev/null +++ b/docs/superpowers/specs/2026-07-09-kimicode-global-settings-panel-design.md @@ -0,0 +1,253 @@ +# Kimi Code 全局配置面板设计 + +## 背景 + +Kimi Code CLI 的 `~/.kimi-code/config.toml` 包含大量全局设置:`[thinking]`、`[loop_control]`、`[background]`、`[[permission.rules]]`、`[[hooks]]` 等。当前 Pi Switch 只暴露了 `providers` 和 `models` 的可视化编辑,其他全局配置只能通过"配置 JSON"标签页手动修改,不够便捷。 + +## 目标 + +1. 在 Kimi Code 选项卡的供应商编辑页面(`ProviderEdit`)中,"模型映射"标签页下方新增一个"全局配置"面板,用图形化控件快速编辑上述 Kimi Code 全局设置。 +2. 根据模型 ID 自动为新添加/发现的模型设置默认 `max_context_size`,全局默认值为 256000,具体模型按可扩展的键值对表格匹配。 + +模型级的 `max_context_size` 已在模型映射表格中实现,用户仍可手动覆盖自动默认值。 + +## 设计 + +### 位置 + +- 组件:`src/components/ProviderEdit.tsx` +- 仅在 `agent === "kimi_code"` 时显示。 +- 位于"模型映射"标签页的内容区域内,在模型映射表格和"+ 添加模型映射"按钮的下方。 +- 垂直排列,超出区域高度时由页面滚动条处理。 + +### 分组与控件 + +面板按功能分为 5 张卡片: + +#### 卡片 1:思考模式 `[thinking]` + +| 控件 | 字段 | 说明 | +| --- | --- | --- | +| 复选框 | `enabled` | 是否默认开启思考 | +| 分段按钮 | `effort` | 思考强度:`low` / `medium` / `high` / `max` | +| 复选框 | `keep` | 是否保留历史思考内容(`"all"` 或关值) | + +- 思考等级和保留思考内容仅在"启用思考"勾选时可用,禁用时置灰。 +- "保留思考内容"未勾选时,写入 `[thinking].keep = false`(不能省略,否则 CLI 会回退到默认值 `"all"`)。 +- 卡片底部小字提示:"启用思考会占用更多上下文,请确保模型上下文长度和预留空间足够。" + +#### 卡片 2:循环控制 `[loop_control]` + +| 控件 | 字段 | 说明 | +| --- | --- | --- | +| 数字输入框 | `max_retries_per_step` | 单步失败后最大重试次数,默认 3 | +| 数字输入框 | `reserved_context_size` | 预留给模型输出的 token 数,单位 token | + +#### 卡片 3:后台任务 `[background]` + +| 控件 | 字段 | 说明 | +| --- | --- | --- | +| 数字输入框 | `max_running_tasks` | 同时运行的最大后台任务数 | +| 复选框 | `keep_alive_on_exit` | 会话关闭时是否保留后台任务 | + +#### 卡片 4:权限规则 `[[permission.rules]]` + +- 可添加/删除的规则列表,默认空列表。 +- 每行包含: + - **处置** 下拉框:`allow` / `deny` / `ask` + - **模式** 输入框:如 `Read`、`Bash(rm -rf*)` + - **删除** 按钮 +- 提供"添加常用规则"快捷入口,可一键插入: + - `allow` + `Read` + - `deny` + `Bash(rm -rf*)` + +#### 卡片 5:生命周期钩子 `[[hooks]]` + +- 可添加/删除的钩子列表,默认空列表。 +- 每行包含: + - **事件** 下拉框:常见事件如 `PreToolUse` / `PostToolUse`,也可手动输入其他事件 + - **匹配器** 输入框:如 `Bash` + - **命令** 输入框:如 `node ~/.kimi-code/hooks/check-bash.mjs` + - **超时** 数字输入框(秒) + - **删除** 按钮 + +### 样式 + +- 卡片容器:`bg-[#16161a] border border-[#2a2a2e] rounded-xl p-4` +- 卡片间距:`gap-4` +- 卡片标题:`text-gray-400 text-sm font-medium mb-3` +- 表单项:`flex items-center gap-4 flex-wrap` +- 数字输入框宽度:`w-32` +- 禁用状态:文字 `text-gray-600`,按钮不可交互 + +### 字段默认值 + +当 `raw_other` 中不存在对应表时,控件按以下默认值展示: + +| 配置块 | 字段 | UI 默认值 | +| --- | --- | --- | +| `[thinking]` | `enabled` | `true` | +| `[thinking]` | `effort` | `"medium"` | +| `[thinking]` | `keep` | `"all"`(勾选) | +| `[loop_control]` | `max_retries_per_step` | `3` | +| `[loop_control]` | `reserved_context_size` | `50000` | +| `[background]` | `max_running_tasks` | 空(不写入) | +| `[background]` | `keep_alive_on_exit` | `false` | +| `[[permission.rules]]` | — | 空列表 | +| `[[hooks]]` | — | 空列表 | + +数字输入框允许清空,表示不写入配置文件,由 Kimi Code CLI 使用其内置默认值。 + +### 数据流 + +- `App.tsx` 向 `ProviderEdit` 传入: + - `agent` + - `rawOther`(即 `config.raw_other`) + - `updateRawOther: (updater: (rawOther: unknown) => unknown) => void` +- `ProviderEdit` 中新增内部子组件 `AgentSettingsPanel`,封装所有全局配置 UI。 +- 新增 TypeScript 类型(`src/types/index.ts`): + - `ThinkingConfig` + - `LoopControlConfig` + - `BackgroundConfig` + - `PermissionRule` + - `Hook` + - `AgentSettings` +- 新增 helper(`src/components/AgentSettingsPanel.tsx` 或 `src/lib/agent-settings.ts`): + - `getAgentSettings(rawOther: unknown): AgentSettings` + - `setAgentSettings(rawOther: unknown, patch: Partial): unknown` + - 统一从 `raw_other` 中读取/写入 `[thinking]`、`[loop_control]`、`[background]`、`[[permission.rules]]`、`[[hooks]]`。 +- 后端无需改动:这些顶层表原本就通过 `Config.raw_other` 在导入/导出时保留。 + +### 模型默认上下文长度 + +在新增模型(手动添加、一键设置、从 API 发现模型)时,根据模型 ID 自动推荐 `max_context_size`。 + +- 默认回退值:`256000` +- 匹配方式:按模型 ID 字符串进行不区分大小写的正则前缀/全名匹配(代码中使用 `/pattern/i`),命中第一条规则即返回。 +- 规则表(按匹配优先级排列): + +| 匹配规则(不区分大小写) | `max_context_size` | 说明 | +| --- | --- | --- | +| **国内模型(优先)** | | | +| `^kimi-for-coding$` | `262144` | Kimi Code 官方托管模型 | +| `^kimi-k2\.5` | `256000` | Kimi K2.5 系列 | +| `^kimi-k2` | `256000` | Kimi K2 系列 | +| `^kimi-` | `256000` | 其他 Kimi 模型 | +| `^glm-5\.2` | `1000000` | GLM-5.2 | +| `^glm-5\.1` | `256000` | GLM-5.1 | +| `^glm-5` | `256000` | GLM-5 系列 | +| `^glm-4` | `128000` | GLM-4 / GLM-4-Plus / GLM-4-Flash | +| `^glm-` | `128000` | 其他 GLM 模型 | +| `^MiniMax-M3` | `1000000` | MiniMax-M3 | +| `^MiniMax-Text-01` | `400000` | MiniMax-Text-01 | +| `^MiniMax-` | `256000` | 其他 MiniMax 模型 | +| `^qwen2\.5` | `128000` | 通义千问 Qwen2.5 系列 | +| `^qwen-max` | `128000` | 通义千问 Max | +| `^qwen-plus` | `128000` | 通义千问 Plus | +| `^qwen-turbo` | `128000` | 通义千问 Turbo | +| `^qwen-coder` | `128000` | 通义千问 Coder | +| `^qwen-` | `128000` | 其他通义千问模型 | +| `^deepseek-r1` | `64000` | DeepSeek-R1 | +| `^deepseek-v3` | `64000` | DeepSeek-V3 | +| `^deepseek-coder` | `64000` | DeepSeek-Coder | +| `^deepseek-` | `64000` | 其他 DeepSeek 模型 | +| `^hunyuan-pro` | `32000` | 腾讯 Hunyuan Pro | +| `^hunyuan-standard` | `32000` | 腾讯 Hunyuan Standard | +| `^hunyuan-lite` | `32000` | 腾讯 Hunyuan Lite | +| `^hunyuan-` | `32000` | 其他 Hunyuan 模型 | +| `^doubao-pro` | `128000` | 字节 Doubao Pro | +| `^doubao-lite` | `128000` | 字节 Doubao Lite | +| `^doubao-vision` | `128000` | 字节 Doubao Vision | +| `^doubao-` | `128000` | 其他 Doubao 模型 | +| `^ernie-4\.0` | `128000` | 百度文心 4.0 | +| `^ernie-3\.5` | `128000` | 百度文心 3.5 | +| `^ernie-speed` | `128000` | 百度文心 Speed | +| `^ernie-lite` | `128000` | 百度文心 Lite | +| `^ernie-` | `128000` | 其他文心模型 | +| `^spark-v4` | `32000` | 讯飞星火 V4 | +| `^spark-v3\.5` | `32000` | 讯飞星火 V3.5 | +| `^spark-pro` | `32000` | 讯飞星火 Pro | +| `^spark-max` | `32000` | 讯飞星火 Max | +| `^spark-` | `32000` | 其他星火模型 | +| `^sensechat-` | `128000` | 商汤 SenseChat | +| `^baichuan-4` | `128000` | 百川 Baichuan 4 | +| `^baichuan-3` | `128000` | 百川 Baichuan 3 | +| `^baichuan-` | `128000` | 其他百川模型 | +| `^yi-` | `128000` | 零一万物 Yi 系列 | +| **国际模型** | | | +| `^claude-opus` | `200000` | Claude Opus 系列(多数版本为 200K;Claude 4 部分版本可达 1M,这里取保守值) | +| `^claude-sonnet` | `200000` | Claude Sonnet 系列 | +| `^claude-haiku` | `200000` | Claude Haiku 系列 | +| `^claude-` | `200000` | 其他 Claude 模型 | +| `^gpt-4\.1` | `1047576` | GPT-4.1 系列(1M tokens) | +| `^gpt-4o` | `128000` | GPT-4o 系列 | +| `^gpt-4-turbo` | `128000` | GPT-4 Turbo | +| `^gpt-4-` | `128000` | 其他 GPT-4 模型 | +| `^gemini-2\.0-flash` | `1048576` | Gemini 2.0 Flash(1M tokens) | +| `^gemini-1\.5-pro` | `2097152` | Gemini 1.5 Pro(2M tokens) | +| `^gemini-1\.5-flash` | `1048576` | Gemini 1.5 Flash(1M tokens) | +| `^gemini-` | `1048576` | 其他 Gemini 模型 | +| (默认) | `256000` | 未命中任何规则 | + +> 注:模型上下文长度取公开资料典型值,部分国内/国际模型存在多个版本导致数值差异,实际以供应商文档为准。规则表以代码常量形式存在,按 specificity 从高到低排列,命中第一条即返回,未命中时回退到 256000。用户仍可在模型映射表格中手动覆盖。 + +- 实现位置:`src/lib/model-defaults.ts`(或类似纯函数文件)。 +- 核心函数: + ```ts + export function getDefaultMaxContextSize(modelId: string): number; + ``` +- 调用点: + - `ProviderEdit` 中手动添加模型时。 + - `ProviderEdit` 中通过"获取模型列表"发现模型并添加时。 + - `handleAddProvider` / `handleApplyProviderJson` 等生成默认模型的地方,可一并使用此函数作为更智能的默认值。 + +### i18n 键 + +新增键: + +- `agentSettings`: "全局配置" +- `enableThinking`: "启用思考" +- `thinkingLevel`: "思考等级" +- `thinkingKeep`: "保留思考内容" +- `thinkingLow`: "低" +- `thinkingMedium`: "中" +- `thinkingHigh`: "高" +- `thinkingMax`: "最大" +- `thinkingContextHint`: "启用思考会占用更多上下文,请确保模型上下文长度和预留空间足够。" +- `loopControlSettings`: "循环控制" +- `maxRetriesPerStep`: "单步重试次数" +- `reservedContextSize`: "上下文预留大小" +- `backgroundSettings`: "后台任务" +- `maxRunningTasks`: "最大并发数" +- `keepAliveOnExit`: "退出时保持运行" +- `permissionRules`: "权限规则" +- `permissionDecision`: "处置" +- `permissionPattern`: "模式" +- `permissionAllow`: "允许" +- `permissionDeny`: "拒绝" +- `permissionAsk`: "询问" +- `addRule`: "+ 添加规则" +- `addCommonRules`: "添加常用规则" +- `hooks`: "生命周期钩子" +- `hookEvent`: "事件" +- `hookMatcher`: "匹配器" +- `hookCommand`: "命令" +- `hookTimeout`: "超时" +- `addHook`: "+ 添加钩子" + +## 验收标准 + +- [ ] 在 Kimi Code 选项卡下进入供应商编辑页面,"模型映射"标签页下方出现"全局配置"区域。 +- [ ] Pi 选项卡下不显示该全局配置区域。 +- [ ] "启用思考"复选框控制 `[thinking].enabled`。 +- [ ] 启用思考后,"思考等级"和"保留思考内容"可用;未启用时禁用。 +- [ ] "思考等级"分段按钮写入 `[thinking].effort`。 +- [ ] "保留思考内容"写入 `[thinking].keep = "all"`,未勾选时写入关值。 +- [ ] `[loop_control]` 和 `[background]` 的数字/复选框正确读写。 +- [ ] 权限规则列表可增删改,导出为 `[[permission.rules]]`。 +- [ ] 钩子列表可增删改,导出为 `[[hooks]]`。 +- [ ] 保存配置后,Kimi Code 的 `config.toml` 中对应顶层表内容正确。 +- [ ] 不修改模型映射表格中已有的 `max_context_size` 手动编辑逻辑。 +- [ ] 添加新模型时,`max_context_size` 默认按规则表自动填充,匹配不区分大小写,未命中规则时为 256000。 +- [ ] 用户仍可在模型映射表格中手动覆盖自动填充的 `max_context_size`。 +- [ ] `npm run build` 通过。