Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint", "lokalise.i18n-ally", "esbenp.prettier-vscode"]
"recommendations": ["dbaeumer.vscode-eslint", "lokalise.i18n-ally", "esbenp.prettier-vscode", "TypeScriptTeam.native-preview"]
}
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
"i18n-ally.keystyle": "nested",
"i18n-ally.sourceLanguage": "zh-CN",
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json"
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
"unocss.disable": true,
"typescript.experimental.useTsgo": true
}
37 changes: 17 additions & 20 deletions docs/architecture/tool-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ graph TB

AgentToolMgr[AgentToolManager]
FsHandler[AgentFileSystemHandler]
Browser[Yo Browser Tools]
YoBrowser[Yo Browser CDP]
end

subgraph "外部服务"
Expand All @@ -48,10 +48,10 @@ graph TB
McpClient --> MCPServers

AgentToolMgr --> FsHandler
AgentToolMgr --> Browser
AgentToolMgr --> YoBrowser

FsHandler --> Files
Browser --> Web
YoBrowser --> Web

classDef router fill:#e3f2fd
classDef mcp fill:#fff3e0
Expand All @@ -60,7 +60,7 @@ graph TB

class ToolP,Mapper router
class McpP,ServerMgr,ToolMgr,McpClient mcp
class AgentToolMgr,FsHandler,Browser agent
class AgentToolMgr,FsHandler,YoBrowser agent
class MCPServers,Files,Web external
```

Expand Down Expand Up @@ -622,23 +622,20 @@ class AgentFileSystemHandler {
3. **边界检查**:防止 `../` 越界访问
4. **正则验证**:`grep_search` 和 `text_replace` 使用 `validateRegexPattern` 防 ReDoS

### Browser 工具
### YoBrowser CDP 工具

```typescript
// 通过 Yo Browser Presenter 调用
async callBrowserTool(toolName: string, args: any): Promise<string> {
switch (toolName) {
case 'browser_navigate':
return await this.yoBrowserPresenter.navigate(args.url)
case 'browser_scrape':
return await this.yoBrowserPresenter.scrape(args.url)
case 'browser_screenshot':
return await this.yoBrowserPresenter.screenshot(args.url)
default:
throw new Error(`未知的 Browser 工具: ${toolName}`)
}
}
```
YoBrowser 提供基于 Chrome DevTools Protocol (CDP) 的最小工具集,在 agent 模式下直接可用。

**可用工具**:
- `yo_browser_tab_list` - 列出所有浏览器 tabs
- `yo_browser_tab_new` - 创建新 tab
- `yo_browser_tab_activate` - 激活指定 tab
- `yo_browser_tab_close` - 关闭 tab
- `yo_browser_cdp_send` - 发送 CDP 命令

**安全约束**:
- `local://` URL 禁止 CDP attach(在 `BrowserTab.ensureSession()` 中检查)
- 所有 CDP 命令通过 `webContents.debugger.sendCommand()` 执行

## 🔐 权限系统

Expand Down
2 changes: 1 addition & 1 deletion docs/archives/workspace-agent-refactoring-summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ graph TB

- MCP 工具:保持原始命名
- Agent FileSystem 工具:不加前缀(`read_file` 等)
- Yo Browser:保留 `browser_` 前缀
- Yo Browser:使用 `yo_browser_` 前缀

### 工具路由机制

Expand Down
68 changes: 68 additions & 0 deletions docs/specs/agent-provider-simplification/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Plan: Agent Provider Simplification (ACP-only)

## Summary

Replace the “agent provider” abstraction and detection logic with a single explicit rule: **ACP is the only agent provider and is identified by `providerId === 'acp'`.**

## Current Call Flow (relevant parts)

- Main:
- `ProviderInstanceManager.createProviderInstance()` already special-cases `provider.id === 'acp'`.
- `ProviderInstanceManager.isAgentProvider()` uses `instanceof BaseAgentProvider` and (if instance not created) a constructor prototype check (`isAgentConstructor`).
- `LLMProviderPresenter.isAgentProvider()` exposes this to the renderer via `ILlmProviderPresenter`.
- Renderer:
- `src/renderer/src/stores/modelStore.ts` calls `llmproviderPresenter.isAgentProvider(providerId)` over IPC to choose between:
- `agentModelStore.refreshAgentModels(providerId)` (ACP path)
- `refreshStandardModels + refreshCustomModels` (standard path)
- Other renderer logic already treats ACP as special via `provider.id === 'acp'`.

## Proposed Changes

### 1) Remove agent-provider classification API

- Remove `isAgentProvider(providerId: string)` from:
- `src/shared/types/presenters/llmprovider.presenter.d.ts`
- `src/shared/types/presenters/legacy.presenters.d.ts`
- `src/main/presenter/llmProviderPresenter/index.ts`
- `src/main/presenter/llmProviderPresenter/managers/providerInstanceManager.ts`

Rationale: It is only used by the renderer for ACP gating, and ACP can be identified locally by ID.

### 2) Replace renderer gating with an explicit ACP check

- In `src/renderer/src/stores/modelStore.ts`:
- Remove the async IPC call `llmP.isAgentProvider(providerId)`.
- Replace with a local predicate: `providerId === 'acp'`.
- Keep the existing ACP refresh path using `agentModelStore.refreshAgentModels('acp')` (no behavioral change).

### 3) Remove `BaseAgentProvider` (optional but preferred)

Because `BaseAgentProvider` is only used by `AcpProvider`, delete the base class and:

- Make `AcpProvider` extend `BaseLLMProvider` directly.
- Move `cleanup()` logic into `AcpProvider` (or delegate to `AcpSessionManager` / `AcpProcessManager`).
- Ensure `cleanup()` is safe to call multiple times and during shutdown.

Notes:
- `acpCleanupHook` currently awaits `cleanup()` even though `BaseAgentProvider.cleanup()` is `void`. Consider standardizing ACP cleanup to `Promise<void>` to match usage.

## Compatibility / Migration

- No user data migration.
- Provider ID `acp` remains unchanged and is treated as a stable internal contract.
- Any internal IPC typing generation must be updated to reflect removal of `isAgentProvider`.

## Test Strategy

Add minimal tests focusing on the only behavioral dependency (renderer model refresh selection):

- Renderer unit test for `modelStore.refreshProviderModels()`:
- When `providerId === 'acp'`, it uses `agentModelStore.refreshAgentModels`.
- When `providerId !== 'acp'`, it uses standard refresh path.

Main-process unit tests are optional; the change is mostly removal and ACP-id checks.

## Rollout

Single PR is acceptable if changes stay localized (types + modelStore + ACP provider base class cleanup).

43 changes: 43 additions & 0 deletions docs/specs/agent-provider-simplification/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Agent Provider Simplification (ACP-only)

## Background

DeepChat currently distinguishes between:

- **LLM providers**: network-backed providers that implement `BaseLLMProvider` (OpenAI/Anthropic/etc).
- **Agent providers**: providers that manage local agent sessions/processes (currently only `acp` via `AcpProvider`).

The codebase implements this distinction via a dedicated base class (`BaseAgentProvider`) and a runtime/type-detection API (`isAgentProvider`), which is then consumed from the renderer via IPC.

## Problem

- `BaseAgentProvider` is only used by `AcpProvider`, so the abstraction adds indirection without real reuse.
- Provider type detection is over-engineered (`isAgentConstructor` + prototype checks) and duplicates existing ACP-specific branching.
- The renderer calls `llmproviderPresenter.isAgentProvider(providerId)` over IPC, but the only “agent provider” is `providerId === 'acp'`. This creates unnecessary main↔renderer coupling and call complexity.

## Goals

- Treat **ACP as the only agent provider** and identify it **only by `providerId === 'acp'`**.
- Remove the generic “agent provider type detection” path and the renderer IPC dependency for this decision.
- Keep user-visible behavior unchanged:
- ACP agents still appear as selectable models when ACP is enabled.
- Non-ACP providers keep the standard model/custom-model refresh behavior.
- Shutdown and provider disable still clean up ACP resources.

## Non-goals

- Supporting multiple agent providers beyond ACP.
- Redesigning ACP model derivation (agents-as-models) or session/workspace semantics.
- Changing persisted provider IDs or stored settings schemas.

## Acceptance Criteria

- Renderer no longer calls `llmproviderPresenter.isAgentProvider(...)`; ACP decision is local (`providerId === 'acp'`).
- Main process no longer needs `isAgentConstructor` / prototype-based provider classification.
- No remaining runtime dependency on `BaseAgentProvider` for correctness (ACP cleanup remains correct).
- `pnpm run typecheck`, `pnpm test`, `pnpm run lint` pass.

## Open Questions

- None.

26 changes: 26 additions & 0 deletions docs/specs/agent-provider-simplification/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Tasks: Agent Provider Simplification (ACP-only)

1. Update renderer to stop using IPC for agent-provider detection
- Remove `llmproviderPresenter.isAgentProvider` usage from `src/renderer/src/stores/modelStore.ts`.
- Gate ACP behavior by `providerId === 'acp'`.

2. Remove `isAgentProvider` from the presenter contract
- Remove from `src/shared/types/presenters/llmprovider.presenter.d.ts`.
- Remove from `src/shared/types/presenters/legacy.presenters.d.ts`.
- Remove implementation from `src/main/presenter/llmProviderPresenter/index.ts`.

3. Remove main-side agent-provider classification implementation
- Delete `ProviderInstanceManager.isAgentProvider()` and `isAgentConstructor()` in `src/main/presenter/llmProviderPresenter/managers/providerInstanceManager.ts`.
- Ensure no other code path depends on `BaseAgentProvider` type checks.

4. Remove `BaseAgentProvider` abstraction (preferred)
- Delete `src/main/presenter/llmProviderPresenter/baseAgentProvider.ts`.
- Update `src/main/presenter/llmProviderPresenter/providers/acpProvider.ts` to extend `BaseLLMProvider` directly.
- Keep/adjust ACP cleanup semantics (safe shutdown, provider disable, app quit).

5. Add/adjust tests
- Add a Vitest suite under `test/renderer/**` validating model refresh selection for ACP vs non-ACP.

6. Quality gates
- Run `pnpm run format`, `pnpm run lint`, `pnpm run typecheck`, and `pnpm test`.

122 changes: 122 additions & 0 deletions docs/specs/chat-settings-control/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Plan: Control Settings via Chat

## Key Decision: Skill-Based Context Control

This feature MUST be described and delivered as a DeepChat skill so that additional instructions/context are only injected when the user actually requests to change DeepChat settings.

- Skill Name (suggested): `deepchat-settings`
- Activation: Activated via `skill_control` **ONLY** when the user request involves DeepChat settings/preferences.
- Deactivation: Call `skill_control` after completing the setting change to keep context lean.

## Tool Injection Control (No Skill, No Tools)

Configuration-related tools MUST NOT appear in the LLM tool list (and MUST NOT be mentioned in the system prompt) unless the `deepchat-settings` skill is active.

Implementation intent:

- Define dedicated tools (MCP-format function definitions):
- `deepchat_settings_toggle`
- `deepchat_settings_set_language`
- `deepchat_settings_set_theme`
- `deepchat_settings_set_font_size`
- `deepchat_settings_open`
- **DO NOT** expose them through MCP server/tool list UI (avoid being auto-enabled into `enabledMcpTools`).
- Only inject these tool definitions when:
- `deepchat-settings` is enabled for the current conversation, AND
- The skill's pre-metadata `allowedTools` includes the tool name.

This requires conversation-scoped tool definition construction:

- Extend tool definition construction context to include `conversationId`.
- Retrieve `skillsAllowedTools` for that conversation (via `SkillPresenter.getActiveSkillsAllowedTools`).
- Only conditionally append `deepchat_settings_*` tool definitions when allowed.

## Step 1: Safe Settings Application API (Main Process)

### Entry Point

Implement a narrow, validated application surface in the main process (presenter method or agent tool handler) for:

- Accepting `unknown` input and validating it (Zod-style, similar to `AgentFileSystemHandler`).
- Using an allowlist of setting IDs.
- Applying changes by calling existing `ConfigPresenter` methods so existing event broadcasts remain correct.
- Returning structured results to render confirmation/error messages.

### Allowlisted Settings and Mapping

Toggle settings:

- `soundEnabled` -> `ConfigPresenter.setSoundEnabled(boolean)` (broadcasts: `CONFIG_EVENTS.SOUND_ENABLED_CHANGED`)
- `copyWithCotEnabled` -> `ConfigPresenter.setCopyWithCotEnabled(boolean)` (broadcasts: `CONFIG_EVENTS.COPY_WITH_COT_CHANGED`)

Enum settings:

- `language` -> `ConfigPresenter.setLanguage(locale)` (broadcasts: `CONFIG_EVENTS.LANGUAGE_CHANGED`)
- `theme` -> `ConfigPresenter.setTheme('dark' | 'light' | 'system')` (broadcasts: `CONFIG_EVENTS.THEME_CHANGED`)
- `fontSizeLevel` -> `ConfigPresenter.setSetting('fontSizeLevel', level)` (broadcasts `CONFIG_EVENTS.FONT_SIZE_CHANGED` via special case)

### Validation Rules

- Strict allowlist; reject unknown IDs.
- No implicit type conversion in Step 1.
- Validation per setting:
- Booleans: must be boolean type
- Enum values: must match allowed set
- `fontSizeLevel`: must be integer within supported range (source of truth TBD; may align with `uiSettingsStore` constants)
- `language`: must be one of supported locales (reuse support list from config)

### Defense in Depth: Require Skill Activity

Even with controlled tool injection, maintain runtime checks:

- If `deepchat-settings` is **NOT** enabled for the conversation, reject application and return error telling the model/user to activate it.
- This ensures settings don't accidentally change due to unrelated agent behavior.

## Step 2: Skill Definition (Natural Language Behavior)

### Built-in Skill Artifact

Add `resources/skills/deepchat-settings/SKILL.md`:

- Pre-metadata `description` MUST explicitly state:
- This is ONLY for changing DeepChat application settings.
- Activate ONLY when user requests setting changes (settings/preferences/theme/language/font/sound/copy COT).
- Do NOT activate for OS settings or programming/code settings.
- Body MUST define:
- Supported settings (allowlist) and canonical values.
- How to ask clarifying questions when ambiguous.
- When to refuse and instead open settings.
- Always deactivate after completing setting tasks.

### Disallowed Settings -> Open Settings

For requests involving MCP configuration, prompts, providers, API keys, etc.:

- Do NOT apply via tools.
- Provide precise instructions telling user where to change them.
- Open settings window and navigate to relevant section if possible.

Implementation options for opening/navigating settings:

- Use `presenter.windowPresenter.createSettingsWindow()`.
- Optionally `executeJavaScript` to set localStorage navigation hint that UI can read.
- Or add dedicated IPC channel from main process -> settings renderer to navigate to tab/section.

## Data Model

Introduce shared request/response types (for Step 1 entry point + tools):

- `ChatSettingId` (union of allowlisted IDs)
- `ApplyChatSettingRequest` (discriminated union `{ id, value }`)
- `ApplyChatSettingResult`
- `{ ok: true; id; value; previousValue?; appliedAt }`
- `{ ok: false; errorCode; message; details? }`

## Testing Strategy

- Main process (Vitest):
- Allowlist + validation (reject invalid values, no writes)
- Each supported setting maps to correct `ConfigPresenter` method
- Skill requirement enforcement works (tool rejects when skill inactive)
- Renderer/UI (if any navigation hints added):
- Settings page navigation handler tests (optional)
Loading