---
name: foliko-dev
description: Foliko Framework 插件开发指南。当用户说"创建插件"、"开发插件"时立即调用此 skill。
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
---

# Foliko Plugin Development Guide

## 概述

Foliko 是一个基于插件的 Agent 框架，核心简单，通过插件扩展功能。

---

## 插件存放位置

### `.agent/plugins/` 目录（用户插件，自动加载）

用户自定义插件放在项目根目录的 `.agent/plugins/` 下，**自动加载**，无需手动注册。

**推荐使用文件夹结构**（适合复杂插件）：

```
项目目录/
└── .agent/
    └── plugins/
        ├── my-plugin/              ✅ 文件夹结构（推荐）
        │   ├── package.json        # 可选，main 字段指定入口
        │   ├── index.js            # 默认入口
        │   └── node_modules/       # 可选，插件私有依赖
        ├── another-plugin/         ✅ 另一个文件夹插件
        │   └── index.js
        └── legacy.js               ✅ 单文件仍支持
```

**文件夹结构的优势：**

- 支持 `package.json` 的 `main` 字段自定义入口
- 支持 `node_modules` 存放插件私有依赖
- 更适合复杂插件的代码组织

### `plugins/` 目录（内置插件）

框架内置插件位于 `plugins/` 目录。

---

## 用户插件写法（`.agent/plugins/`）

**必须使用免引入写法**：

### 文件夹结构（推荐）

```
.agent/plugins/my-plugin/
├── package.json      # 可选
└── index.js          # 入口
```

```json
// package.json 示例
{
  "name": "my-plugin",
  "main": "index.js"
}
```

```javascript
// .agent/plugins/my-plugin/index.js
const { z } = require('zod');

module.exports = function (Plugin) {
  return class MyPlugin extends Plugin {
    constructor(config = {}) {
      super();
      this.name = 'my-plugin';
      this.version = '1.0.0';
      this.description = '我的工具插件';
      this.priority = 10;
    }

    // 工具定义在 this.tools 中
    tools = {
      my_tool: {
        description: '我的工具',
        inputSchema: z.object({
          param: z.string().describe('参数描述'),
        }),
        execute: async (args) => {
          return { success: true, result: args.param };
        },
      },
    };

    install(framework) {
      // 也可以用 this.registerTool() 注册额外工具
      this.registerTool('another_tool', {
        description: '另一个工具',
        inputSchema: z.object({}),
        execute: async (args) => ({ success: true }),
      });
      return this;
    }

    uninstall(framework) {
      // 清理资源
    }
  };
};
```

### 单文件结构（仍支持）

```javascript
// .agent/plugins/my-plugin.js
const { z } = require('zod');

module.exports = function (Plugin) {
  return class MyPlugin extends Plugin {
    constructor(config = {}) {
      super();
      this.name = 'my-plugin';
      this.version = '1.0.0';
      this.description = '我的工具插件';
      this.priority = 10;
    }

    // 工具定义在 this.tools 中
    tools = {
      my_tool: {
        description: '我的工具',
        inputSchema: z.object({
          param: z.string().describe('参数描述'),
        }),
        execute: async (args) => {
          return { success: true, result: args.param };
        },
      },
    };

    install(framework) {
      return this;
    }

    uninstall(framework) {
      // 清理资源
    }
  };
};
```

**注意**：如果文件夹和同名 `.js` 文件同时存在，**文件夹优先**。

**插件开发流程：**

1. **创建插件**（优先使用文件夹结构）
2. **安装依赖**：
   - 文件夹插件：`install { package: "包名", path: ".agent/plugins/插件名" }`
   - 单文件插件：`install { package: "包名" }`
3. **热重载**：`reload_plugins`
4. **检查加载状态**

**关键点：**

- `Plugin` 基类由系统自动传入（无需 require）
- 函数直接返回插件类
- 在 `install(framework)` 中通过 `framework` 注册工具

---

## 依赖管理

当插件需要使用第三方 npm 包（如 `zod`、`axios` 等）时，根据插件类型选择安装位置：

| 插件类型       | 安装位置                              | 说明                   |
| -------------- | ------------------------------------- | ---------------------- |
| **文件夹插件** | `.agent/plugins/插件名/node_modules/` | 插件自包含，可独立迁移 |
| **单文件插件** | `.agent/node_modules/`                | 共享依赖               |

### 自动安装工具

框架提供了 `install` 工具，支持以下用法：

```javascript
// 安装到默认位置 .agent/node_modules
install({ package: 'zod' });

// 安装到指定目录（用于文件夹插件）
install({ package: 'axios', path: '.agent/plugins/my-plugin' });

// 从 package.json 安装所有依赖到默认位置
install({ file: './package.json' });

// 从 package.json 安装所有依赖到指定目录
install({ file: './my-plugin/package.json', path: '.agent/plugins/my-plugin' });

// 仅指定路径，安装该目录下的 package.json 依赖
install({ path: '.agent/plugins/my-plugin' });
```

### 文件夹插件的依赖安装示例

```javascript
// .agent/plugins/my-plugin/index.js
const { z } = require('zod');

module.exports = function (Plugin) {
  return class MyPlugin extends Plugin {
    constructor(config = {}) {
      super();
      this.name = 'my-plugin';
    }

    tools = {
      fetch_data: {
        description: '获取远程数据',
        inputSchema: z.object({
          url: z.string().describe('API URL'),
        }),
        execute: async (args) => {
          // 安装到插件自己的目录
          const pluginDir = '.agent/plugins/my-plugin';
          await framework.callTool('install', {
            package: 'axios',
            path: pluginDir,
          });

          // 引入插件目录下的 node_modules
          const axios = require(`${pluginDir}/node_modules/axios`);
          const response = await axios.get(args.url);
          return { success: true, data: response.data };
        },
      },
    };

    install(framework) {
      return this;
    }
  };
};
```

### 安装步骤

当 LLM 检测到 `Cannot find module 'xxx'` 错误时，必须：

1. **确定插件目录**：如果插件是文件夹结构，安装到插件目录
2. **调用 `install` 工具** 安装缺失的包
3. **安装完成后** 重新加载插件
4. **返回状态** 给用户

示例流程（文件夹插件）：

```
错误: Cannot find module 'axios'
↓
确定插件目录: .agent/plugins/my-plugin
↓
自动安装: install { package: "axios", path: ".agent/plugins/my-plugin" }
↓
重新加载插件
↓
成功
```

### 重要提醒

- **文件夹插件优先安装到插件目录** - 使用 `path` 参数指定插件目录
- **不要尝试手动 require 全局安装的包** - 必须在指定目录中安装
- **使用 `install` 工具自动处理** - 让 LLM 知道如何处理缺失依赖
- **插件加载失败时检查依赖** - 优先安装缺失的包再重试

---

## 内置插件写法（`plugins/` 目录）

内置插件需要 `require` Plugin 基类：

```javascript
// plugins/my-plugin.js
const { Plugin } = require('../src/core/plugin-base');
const { z } = require('zod');

class MyPlugin extends Plugin {
  constructor(config = {}) {
    super();
    this.name = 'my-plugin';
    this.version = '1.0.0';
    this.description = '我的工具插件';
    this.priority = 10;
  }

  // 工具定义在 this.tools 中，ExtensionExecutor 自动扫描注册
  tools = {
    my_tool: {
      description: '我的工具',
      inputSchema: z.object({
        param: z.string().describe('参数描述'),
      }),
      execute: async (args) => {
        return { success: true, result: args.param };
      },
    },
  };

  install(framework) {
    // 可以用 this.registerTool() 注册额外工具
    this.registerTool('another_tool', {
      description: '另一个工具',
      inputSchema: z.object({}),
      execute: async (args) => ({ success: true }),
    });
    return this;
  }

  uninstall(framework) {}
}

module.exports = MyPlugin;
```

---

## 插件属性

| 属性          | 类型   | 必须 | 说明                                                         |
| ------------- | ------ | ---- | ------------------------------------------------------------ |
| `name`        | string | ✅   | 唯一名称                                                     |
| `version`     | string | ❌   | 版本号，默认 '1.0.0'                                         |
| `description` | string | ❌   | 插件描述                                                     |
| `priority`    | number | ❌   | 加载优先级，默认 10                                          |
| `tools`       | object | ❌   | 工具定义 `{ name: toolDef }`，ExtensionExecutor 自动扫描注册 |

---

## 生命周期

| 方法                   | 调用时机   | 必须实现 |
| ---------------------- | ---------- | -------- |
| `install(framework)`   | 插件安装时 | ✅       |
| `start(framework)`     | 插件启动时 | 推荐     |
| `reload(framework)`    | 热重载时   | 可选     |
| `uninstall(framework)` | 卸载时     | 推荐     |

---

## 注册工具

### 最新方式：定义在 `this.tools = {}`

插件将工具定义在 `this.tools = {}` 中，ExtensionExecutor 会自动扫描并注册到系统提示词：

```javascript
class MyPlugin extends Plugin {
  constructor(config = {}) {
    super();
    this.name = 'my-plugin';
    this.description = '我的工具插件';
  }

  tools = {
    my_tool: {
      description: '我的工具',
      inputSchema: z.object({
        param: z.string().describe('参数描述'),
      }),
      execute: async (args) => {
        return { success: true, result: args.param };
      },
    },
  };
}
```

### 方式2：使用 `this.registerTool()`

支持两种调用格式：

```javascript
install(framework) {
  const { z } = require('zod');

  // 方式1：分开传 name 和 def
  this.registerTool('my_tool', {
    description: '我的工具',
    inputSchema: z.object({
      param: z.string().describe('参数描述')
    }),
    execute: async (args) => {
      return { success: true, result: args.param };
    }
  });

  // 方式2：直接传对象（name 在对象中）
  this.registerTool({
    name: 'another_tool',
    description: '另一个工具',
    inputSchema: z.object({}),
    execute: async (args) => ({ success: true })
  });

  return this;
}
```

---

## 核心规则

| 规则                         | 说明                                            |
| ---------------------------- | ----------------------------------------------- |
| **优先使用 this.tools = {}** | ExtensionExecutor 自动扫描并注册到系统提示词    |
| **优先使用文件夹结构**       | 复杂插件推荐用文件夹，便于管理依赖和代码        |
| **必须用 inputSchema**       | `inputSchema: z.object({})`（不是 parameters!） |
| **必须用 zod 定义参数**      | `param: z.string().describe('描述')`            |
| **install 必须返回 this**    | 确保链式调用                                    |
| **文件夹与文件重名时**       | 文件夹优先，同名 `.js` 文件会被忽略             |
| **工具自动出现在系统提示词** | 定义在 `this.tools` 中会自动注册                |

## 开发完成后注意事项

### 引入第三方库时

**必须先安装依赖，再热重载！**

如果插件需要使用第三方 npm 包（如 `zod`、`axios`），按以下顺序操作：

1. **创建插件文件**（暂不加载）
2. **安装依赖**：`install { package: "包名" }`
3. **热重载**：`reload_plugins`
4. **检查加载状态**

示例：

```

创建插件 my-plugin.js（需要 zod）
↓
install { package: "zod" }
↓
reload_plugins
↓
成功加载

```

### 其他注意事项

--**开发完成重载成功后检测有没有加载新的插件，不需要进行过多测试步骤，直接告知用户状态即可**

---

## 内置占位符

Agent 支持 `sharedPrompt` 中的占位符替换：

| 占位符          | 说明     |
| --------------- | -------- |
| `{{WORK_DIR}}`  | 工作目录 |
| `{{HOME_DIR}}`  | 主目录   |
| `{{HOST_NAME}}` | 主机名   |
| `{{PLATFORM}}`  | 平台     |
| `{{TIME}}`      | 当前时间 |
| `{{DATE}}`      | 当前日期 |

---

## 创建 Agent

```javascript
const agent = framework.createAgent({
  name: 'MyAgent',
  systemPrompt: '你是一个助手',
  sharedPrompt: '工作目录: {{WORK_DIR}}\n项目: {{projectName}}',
  metadata: {
    projectName: 'MyProject',
    version: '1.0.0',
  },
});
```

---

## 热重载

```javascript
// 重载单个插件
await framework.reloadPlugin('my-plugin');

// 重载所有插件
await framework.reloadAllPlugins();
```

---

## 子 Agent 注册

插件可以通过 `this.agents = []` 配置或 `this.registerSubAgent()` 方法注册子Agent。

### 方式1：配置 `agents` 数组

```javascript
// plugins/my-plugin.js
const { Plugin } = require('../src/core/plugin-base');
const { tool } = require('ai');
const { z } = require('zod');

class MyPlugin extends Plugin {
  constructor(config = {}) {
    super();
    this.name = 'my-plugin';
  }

  // 配置式注册子Agent，插件启动时自动创建
  agents = [
    {
      name: 'code-agent',
      role: '代码专家',
      description: '处理代码开发任务',
      tools: {
        compile: tool({
          description: '编译代码',
          parameters: z.object({
            language: z.string(),
            code: z.string(),
          }),
          execute: async (args) => ({ success: true }),
        }),
      },
      parentTools: ['read_file', 'write_file'],
    },
  ];
}
```

### 方式2：手动调用 `registerSubAgent()`

```javascript
start(framework) {
  this.registerSubAgent({
    name: 'code-agent',
    role: '代码专家',
    tools: {
      compile: tool({
        description: '编译代码',
        parameters: z.object({ language: z.string(), code: z.string() }),
        execute: async (args) => ({ success: true })
      })
    },
    parentTools: ['read_file', 'write_file']
  })
}
```

### 子Agent配置说明

| 字段          | 类型   | 说明                                            |
| ------------- | ------ | ----------------------------------------------- |
| `name`        | string | 子Agent名称（唯一标识）                         |
| `role`        | string | 角色描述                                        |
| `description` | string | 详细描述                                        |
| `tools`       | object | 自定义工具 `{ name: toolDef }`，只属于此子Agent |
| `parentTools` | array  | 从父Agent继承的工具名称列表                     |

---

## 访问其他插件

### 获取插件实例

在插件中通过 `pluginManager.get()` 访问其他插件：

```javascript
install(framework) {
  // 访问 storage 插件
  const storage = this._framework.pluginManager.get('storage')

  // 访问 session 插件
  const session = this._framework.pluginManager.get('session')

  // 访问 audit 插件
  const audit = this._framework.pluginManager.get('audit')
}
```

### 可访问的系统插件

| 插件名        | 说明       | 可用方法                                     |
| ------------- | ---------- | -------------------------------------------- |
| `storage`     | 键值对存储 | `get(key)`, `set(key, value)`, `delete(key)` |
| `session`     | 会话管理   | `getSession(id)`, `getHistory(id)`           |
| `audit`       | 审计日志   | `log(type, data)`                            |
| `scheduler`   | 定时任务   | `addTask()`, `removeTask()`                  |
| `file-system` | 文件操作   | 工具已注册: `read_file`, `write_file`        |
| `tools`       | 工具管理   | `list()`, `reload()`                         |
| `ai`          | AI 配置    | `getAIClient()`                              |

### Storage 插件 API

```javascript
// 在 install/start 中获取 storage
const storage = this._framework.pluginManager.get('storage');

// 方式1：通过工具调用（推荐）
// storage_get({ key: 'xxx', namespace: 'yyy' })
// storage_set({ key: 'xxx', value: {...}, namespace: 'yyy' })
// storage_delete({ key: 'xxx', namespace: 'yyy' })
// storage_list({ namespace: 'yyy' })
// storage_clear({ namespace: 'yyy' })

// 方式2：直接调用内部方法
const value = storage.getStore().get('key'); // 获取 Map
storage.setDirect('key', value); // 设置并持久化
storage.deleteDirect('key'); // 删除并持久化
```

### Session 插件 API

```javascript
const session = this._framework.pluginManager.get('session');

// 获取会话
const sess = session.getSession(sessionId);

// 获取历史消息
const messages = session.getHistory(sessionId);

// 添加消息
session.addMessage(sessionId, { role: 'user', content: 'hello' });
```
