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
1 change: 0 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ Coding Code 的核心哲学是所有行为都可配置。本文档详细介绍
| `rules.md` | `~/.codingcode/rules.md` + `./AGENTS.md` | 全局 + 项目级规则 | 本文档 |
| `mcp.yaml` | `~/.codingcode/mcp.yaml` + `.codingcode/mcp.yaml` | MCP 服务配置 | [→ mcp.md](mcp.md) |
| `hooks.yaml` | `~/.codingcode/hooks.yaml` + `.codingcode/hooks.yaml` | 钩子配置 | [→ hooks.md](hooks.md) |
| `agents/*.md` | `~/.codingcode/agents/` + `.codingcode/agents/` | 子智能体 profile | [→ subagent.md](subagent.md) |
| `memory.md` | `./.codingcode/memory.md` | 长期记忆(项目级) | [→ memory.md](memory.md) |

---
Expand Down
16 changes: 2 additions & 14 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,6 @@ MCP 连接使用 lease 机制管理会话级生命周期:

---

## MCP 工具白名单
## 子智能体

在子智能体 profile 中通过 `mcpServers` 字段指定允许的 MCP 服务:

```yaml
# .codingcode/agents/my-agent.md
---
name: my-agent
description: 使用特定 MCP 服务的 Agent
tools: ["read_file", "search_code"]
mcpServers: ["filesystem"] # 只允许使用 filesystem 服务的工具
---
```

在 `ToolVisibilityPolicy` 中通过 `allowedMcpServers` 控制可见的 MCP 服务。
子智能体不再通过 profile 文件配置 MCP 服务或工具白名单。MCP 工具由当前项目的 MCP 配置提供;`plan` 模式仅由其独立工具策略限制。
167 changes: 2 additions & 165 deletions docs/subagent.md
Original file line number Diff line number Diff line change
@@ -1,168 +1,5 @@
# 子智能体系统

每个子 Agent 是独立的 ReAct 引擎实例,拥有受限的工具集和独立的上下文。本文档介绍子 Agent 的特性、配置格式、内置 profile 和执行流程
当前运行时只保留 `build` 和 `plan` 两个内置 profile。子智能体不再从项目或全局配置文件加载,也不再支持用户通过 profile 配置工具、模型、MCP、hooks、步数或禁用状态

---

## 特性

- **独立执行**:子 Agent 在独立的 Effect Context 中运行
- **受限工具集**:每个子 Agent profile 定义自己的工具白名单
- **独立上下文**:不共享主 Agent 的消息历史
- **独立模型**:可指定与主 Agent 不同的模型
- **独立 MCP**:可连接指定的 MCP 服务器
- **独立钩子**:可附加专属的钩子配置
- **自由定义**:用户可配置任意数量的子 Agent profile

---

## AgentProfile 类型定义

```typescript
interface AgentProfile {
name: string; // profile 名称,用于 dispatch_agent 引用
description: string; // 功能描述,LLM 据此决定是否委派
systemPrompt?: string; // 自定义系统提示词
tools?: string[]; // 允许使用的工具白名单
mcpServers?: string[]; // 允许连接的 MCP 服务白名单
readonly?: boolean; // 是否只读模式
maxSteps?: number; // 最大执行步数
model?: string; // 使用的模型 ID
hooks?: UserHookConfig[]; // 专属钩子配置
disabled?: boolean; // 是否禁用
}
```

---

## 配置格式

子 Agent 使用 Markdown + frontmatter 格式配置,存放在 `.codingcode/agents/` 目录下:

| 级别 | 路径 | 说明 |
|------|------|------|
| 全局 | `~/.codingcode/agents/*.md` | 所有项目共享 |
| 项目 | `.codingcode/agents/*.md` | 仅当前项目生效 |

项目级同名 profile 覆盖全局级。

### 示例

```markdown
---
name: code-searcher
description: 专门搜索代码库的子 Agent,擅长定位函数定义和引用
tools: ["read_file", "search_code", "search_files"]
readonly: true
maxSteps: 100
model: deepseek-chat
disabled: false
---
You are a code search specialist. Your job is to find specific code patterns, function definitions, and usages in the codebase. Always provide the file path and line numbers in your results.
```

### 字段说明

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `name` | `string` | 必填 | profile 名称 |
| `description` | `string` | 必填 | 功能描述,LLM 据此决定是否委派任务 |
| `systemPrompt` | `string` | frontmatter 之后的正文 | 系统提示词 |
| `tools` | `string[]` | 所有内置工具 | 允许使用的工具白名单 |
| `mcpServers` | `string[]` | 无 | 允许连接的 MCP 服务名列表 |
| `readonly` | `boolean` | `false` | 只读模式下只允许只读工具 |
| `maxSteps` | `number` | 继承主 Agent | 最大执行步数 |
| `model` | `string` | 继承主 Agent | 使用的模型 ID |
| `hooks` | `UserHookConfig[]` | 无 | 专属钩子配置 |
| `disabled` | `boolean` | `false` | 禁用此 profile |

---

## 内置 Profile

系统内置两个子 Agent profile:

### explore

只读代码探索 Agent,用于快速浏览和理解代码库:

```yaml
name: explore
description: 只读代码探索
tools: [read_file, search_files, search_code, fetch_url]
readonly: true
maxSteps: 180
```

### plan

只读代码研究 + 规划 Agent。**只允许只读工具**和 `submit_plan`(用于提交实现计划等待用户审批),不允许执行命令或写文件。计划提交后 session 会自动切换到 `build` profile。

```yaml
name: plan
description: 只读代码研究和规划
tools: [read_file, search_files, search_code, fetch_url, submit_plan, dispatch_agent]
maxSteps: 180
```

> 注意:`plan` profile 自身不设置 `permissionMode`。在 plan 模式下,写工具会被 `plan/planModeGateHook`(注册在 `tool.approval.pre`,priority -1000)拒绝,仅 `submit_plan` 与 `dispatch_agent` 放行。`dispatch_agent` 由 `plan/planSubagentWhitelistHook` 进一步限制为只能派发 `explore` 子代理。

---

## 执行流程

主 Agent 通过 `dispatch_agent` 工具委派任务,完整执行流程如下:

1. **检查开关**:验证全局子智能体开关是否启用 (`resolveSubagentEnabled`)
2. **解析 profile**:查找对应的 AgentProfile (`runtime.resolveSubagentProfile`)
3. **检查禁用**:验证该 profile 是否被禁用 (`resolveAgentDisabled`)
4. **创建 LLM**:如果 profile 指定了 model,创建对应的 LLM 客户端
5. **钩子决策**:触发 `agent.subagent.spawn.before` 决策钩子(可 deny 阻止)
6. **创建子会话**:嵌套在父会话下,设置 `parentSessionId`
7. **Fork 审批**:如果非 readonly,fork 审批服务
8. **附加钩子**:附加 profile 中定义的 hooks
9. **连接 MCP**:连接 profile 中指定的 MCP 服务器(会话级 lease)
10. **构建工具策略**:根据 profile.tools 和 ToolVisibilityPolicy 过滤可用工具
11. **执行**:调用 `runner.runStream()` 执行子智能体
12. **钩子通知**:触发 `agent.subagent.spawn.after` 钩子
13. **收集输出**:提取事件流中的最终输出
14. **清理**:断开 MCP 连接,移除 hooks
15. **完成钩子**:触发 `agent.subagent.complete` 钩子

---

## 使用示例

### 通过 dispatch_agent 工具委派

```typescript
// Agent 自动调用
await agent.executeTool('dispatch_agent', {
agent: 'explore',
prompt: 'Find all usages of getUserById function'
});
```

### 自定义子 Agent

创建 `.codingcode/agents/security-auditor.md`:

```markdown
---
name: security-auditor
description: 安全审计 Agent,检查代码中的安全漏洞
tools: ["read_file", "search_code", "search_files"]
readonly: true
maxSteps: 50
model: deepseek-chat
---
You are a security audit specialist. Review code for common vulnerabilities:
- SQL injection
- XSS
- CSRF
- Path traversal
- Command injection
Report findings with severity level and remediation suggestions.
```

然后在对话中请求安全审计时,主 Agent 会自动委派给 `security-auditor`。
`plan` 模式由独立的 plan 工具策略限制可见和可执行工具,并通过 `submit_plan` 提交计划。普通子智能体工具名单机制已移除;后续子智能体配置由运行时机制另行提供。
8 changes: 4 additions & 4 deletions docs/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Coding Code 的工具系统是 Agent 与外部世界交互的核心机制。本

| 工具 | 功能 | 关键参数 |
|---|---|---|
| `dispatch_agent` | 将任务委派给子智能体 | `agent: string`(子智能体名称),`prompt: string`(任务描述,至少 1 字符) |
| `dispatch_agent` | 将任务委派给运行时注册的子智能体 | `agent: string`, `prompt: string` |

---

Expand All @@ -50,7 +50,7 @@ Coding Code 的工具系统是 Agent 与外部世界交互的核心机制。本
- **Core 工具**:始终可用,在启动时注册。包括上述所有内置工具。
- **MCP 工具**:从 MCP 服务自动导入和注册。名称空间化为 `serverName:toolName` 格式,避免不同服务间的工具名冲突。

Agent 在一次运行开始时将内置工具、项目 MCP 工具和 `dispatch_agent` 注册到 `ToolRegistry`。每轮通过注册表按 `AgentProfile.tools` 和 `ToolVisibilityPolicy` 过滤,并生成 LLM 工具描述与执行查找结果
Agent 在一次运行开始时注册内置工具、项目 MCP 工具和 `dispatch_agent`。plan 模式通过独立的 `PLAN_MODE_ALLOWED_TOOLS` 策略过滤工具

---

Expand Down Expand Up @@ -103,7 +103,7 @@ interface ToolVisibilityPolicy {
|------|------|------|
| 1 | **RuleEngine** | 规则引擎匹配,支持 glob 模式匹配工具名和参数,按优先级排序 |
| 2 | **ReadonlyWhitelist** | 只读工具自动放行(read_file, search_code, search_files, fetch_url, web_search, dispatch_agent, todo_write) |
| 3 | **PermissionMode** | 权限模式判断:`bypass`(全部放行)、`acceptEdits`(非破坏性工具放行)、`default`(继续下一层)。`plan` 模式由独立的 `plan/planModeGateHook` 在 Layer 4 强制,不在此层处理 |
| 3 | **PermissionMode** | 权限模式判断:`bypass`(全部放行)、`acceptEdits`(非破坏性工具放行)、`default`(继续下一层)。`plan` 模式由独立的 `agent/mode.ts` 中的 `planModeGateHook` 在 Layer 4 强制,不在此层处理 |
| 4 | **HookPreToolUse** | 钩子决策,可返回 allow/deny/ask/continue,支持 `modifiedInput` 修改参数 |
| 5 | **UserConfirmation** | 异步用户确认,支持 allow/deny/always/never 四种响应,always/never 会持久化为规则 |
| 6 | **AuditLog** | 每一层决策后记录审计日志,通过 `tool.approval.post` 钩子发出 |
Expand Down Expand Up @@ -134,7 +134,7 @@ type PermissionMode = 'default' | 'acceptEdits' | 'bypass';
- `acceptEdits`:非破坏性工具自动放行,减少确认弹窗
- `bypass`:全部放行,跳过所有审批(慎用)

> `plan` 不再是 `PermissionMode` 的成员。plan 模式通过 `AgentProfile.name === 'plan'` 结构化识别,由 `plan/planModeGateHook` 在 `tool.approval.pre` 阶段(priority -1000)强制拒绝非白名单工具。白名单见 `plan/policy.ts` 的 `PLAN_MODE_ALLOWED_TOOLS`。
> `plan` 不再是 `PermissionMode` 的成员。plan 模式通过 `AgentProfile.name === 'plan'` 结构化识别,由 `agent/mode.ts` 的 `planModeGateHook` 和 `PLAN_MODE_ALLOWED_TOOLS` 共同限制工具

### OS 级沙箱(预留)

Expand Down
2 changes: 0 additions & 2 deletions packages/codingcode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@
"./checkpoint/checkpoint-service": "./src/checkpoint/checkpoint-service.ts",
"./checkpoint/shadow-git": "./src/checkpoint/shadow-git.ts",
"./checkpoint/bootstrap": "./src/checkpoint/bootstrap.ts",
"./subagent/registry": "./src/subagent/registry.ts",
"./subagent/loader": "./src/subagent/loader.ts",
"./llm/factory": "./src/llm/factory.ts",
"./llm/client": "./src/llm/client.ts",
"./layer": "./src/layer.ts"
Expand Down
51 changes: 10 additions & 41 deletions packages/codingcode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@
import { ContextService } from '../context/service.js';
import { MemoryService } from '../memory/index.js';
import { createLogger } from '@codingcode/infra/logger';
import { resolveSubagentEnabled, resolveAgentDisabled } from '../subagent/registry.js';
import { ProjectRuntimeService, modeToProfile } from '../runtime/project-runtime.js';
import { createDispatchAgentTool } from '../tools/domains/subagent/dispatch.js';
import { LLMFactoryService } from '../llm/factory.js';
import { ProjectRuntimeService } from '../runtime/project-runtime.js';
import { registerBuiltinTools } from '../tools/builtin-tools.js';
import { ToolRegistry } from '../tools/registry.js';
import { submitPlanTool } from '../tools/domains/subagent/submit-plan.js';
import { createDispatchAgentTool } from '../tools/domains/subagent/dispatch.js';
import { normalizePath } from '../core/path.js';
import { isPlanProfile } from '../plan/index.js';
import { isPlanProfile } from './mode.js';
import type { SessionMode } from '../session/types.js';
import type { PermissionMode } from '../approval/types.js';

Expand Down Expand Up @@ -129,17 +127,16 @@
Effect.gen(function* () {
const session = yield* SessionService;
const agent = yield* AgentService;
const hooks = yield* HookService;

Check warning on line 130 in packages/codingcode/src/agent/agent.ts

View workflow job for this annotation

GitHub Actions / lint

'hooks' is assigned a value but never used. Allowed unused vars must match /^_/u
const mcp = yield* McpService;
const checkpoint = yield* CheckpointService;
const approval = yield* ApprovalService;

Check warning on line 133 in packages/codingcode/src/agent/agent.ts

View workflow job for this annotation

GitHub Actions / lint

'approval' is assigned a value but never used. Allowed unused vars must match /^_/u
const skills = yield* SkillService;
const runtime = yield* ProjectRuntimeService;
const todo = yield* TodoService;

Check warning on line 136 in packages/codingcode/src/agent/agent.ts

View workflow job for this annotation

GitHub Actions / lint

'todo' is assigned a value but never used. Allowed unused vars must match /^_/u
const rules = yield* RulesService;
const context = yield* ContextService;

Check warning on line 138 in packages/codingcode/src/agent/agent.ts

View workflow job for this annotation

GitHub Actions / lint

'context' is assigned a value but never used. Allowed unused vars must match /^_/u
const memory = yield* MemoryService;
const factory = yield* LLMFactoryService;

const normalizedCwd = normalizePath(cwd);
yield* runtime.prepareProject(normalizedCwd);
Expand All @@ -148,10 +145,7 @@
if (!sessionId) {
if (!options.mode || !options.permissionMode || !options.model) {
return yield* Effect.fail(
new AgentError(
'CONFIG_MISSING',
'new session requires mode, permissionMode, and model'
)
new AgentError('CONFIG_MISSING', 'new session requires mode, permissionMode, and model')
);
}
const created = yield* session.createSessionWithProfile(normalizedCwd, {
Expand All @@ -178,28 +172,14 @@

const dispatchTool = yield* createDispatchAgentTool();

let activeLlm = llm;
if (profile?.model) {
const entry = yield* factory.findModel(profile.model);
if (entry) {
activeLlm = yield* factory.createClient(entry);
}
}
const activeLlm = llm;
const effectiveMaxSteps = profile?.maxSteps;
const effectiveApproval: any = options?.approvalOverride;

if (profile?.hooks?.length) {
yield* hooks.attachSessionHooks(sid, profile.hooks);
}

if (profile?.mcpServers?.length) {
yield* mcp.connectServers(normalizedCwd, sid, profile.mcpServers);
}

const mcpTools = mcp.listProjectMcpTools(normalizedCwd);

const turnId = session.incrementTurn(state);
const [matchedSkill, actualInput] = yield* skills.extractSkill(state.cwd, input);
const [, actualInput] = yield* skills.extractSkill(state.cwd, input);

yield* session.recordUser(state, actualInput);

Expand All @@ -215,11 +195,10 @@
toolPolicy: policy,
maxStepsOverride: effectiveMaxSteps,
approvalOverride: effectiveApproval,
dispatchTool,
mcpTools,
skillInstruction: matchedSkill?.instruction,
abortSignal: options?.signal,
rulesText,
dispatchTool,
});

return { stream, sessionId: sid };
Expand Down Expand Up @@ -253,28 +232,18 @@
return Effect.gen(function* () {
const checkpoint = yield* CheckpointService;
const session = yield* SessionService;
const runtime = yield* ProjectRuntimeService;

Check warning on line 235 in packages/codingcode/src/agent/agent.ts

View workflow job for this annotation

GitHub Actions / lint

'runtime' is assigned a value but never used. Allowed unused vars must match /^_/u
const todo = yield* TodoService;
const context = yield* ContextService;
const memory = yield* MemoryService;
const { skillInstruction, systemPromptVariant, rulesText } = opts;

const allAgentProfiles = runtime.listAgentProfiles(projectPath);
const enabledAgentProfiles = resolveSubagentEnabled(projectPath)
? allAgentProfiles.filter((p) => !resolveAgentDisabled(projectPath, p.name))
: [];
const visibleAgentProfiles = isPlanProfile(profile)
? enabledAgentProfiles.filter((p) => p.name === 'explore')
: enabledAgentProfiles;
const { rulesText } = opts;

const basePrompt =
opts.systemOverride ??
buildSystemPrompt({
cwd: projectPath,
platform: process.platform,
shell: process.env.SHELL || process.env.ComSpec || 'bash',
variant: systemPromptVariant ?? 'default',
skillInstruction,
agentProfiles: visibleAgentProfiles,
rules: rulesText,
profileSystemPrompt: profile?.systemPrompt,
});
Expand All @@ -292,7 +261,7 @@
const registry = new ToolRegistry();
yield* registerBuiltinTools(registry);
registry.register(...(opts.mcpTools ?? []));
if (opts.dispatchTool && resolveSubagentEnabled(projectPath)) registry.register(opts.dispatchTool);
if (opts.dispatchTool) registry.register(opts.dispatchTool);
if (isPlanProfile(profile)) registry.register(submitPlanTool);

let messages: Message[] = [];
Expand Down Expand Up @@ -483,7 +452,7 @@
}
}

const record = yield* session.recordAssistant(state, resp.content, toolCalls!, resp.usage);

Check warning on line 455 in packages/codingcode/src/agent/agent.ts

View workflow job for this annotation

GitHub Actions / lint

'record' is assigned a value but never used. Allowed unused vars must match /^_/u
const allResults = yield* executor.executeBatch(toolCalls, state.sessionId, {
turnId: state.currentTurnId,
projectPath,
Expand Down
Loading
Loading