工作台能力插件

工作台能力

插件

插件是装进 CogineWork 的程序扩展:可以提供页面、工具,或注册一种新的消息渠道。它和 Skill 不同——Skill 是给智能体看的说明和脚本;插件会改客户端实际能调用的能力。

从哪里装

统一入口是 Work → 扩展。

  • 发现 → 插件:浏览已配置的官方精选。只有实例配置了外部插件目录时,才会提供相应来源;尚无条目时会显示空状态。
  • 已添加 → 插件:从本地文件夹、ZIP 或已知安装包 URL 安装,查看版本、启用与加载状态,并管理已添加的插件。

官方精选中的插件添加后会自动尝试启用并加载。若缺少必要配置或初始化失败,界面会提示具体原因;补齐配置或处理错误后,按提示启用或重试。界面区分「已启用」与「已加载」,仅显示已启用不代表插件已加载或可以完成任务。停用插件仍保留在已添加列表中,可继续管理或卸载。

安装、启用和停用后的插件页面会单独刷新,保留正在进行的技能安装队列。部分修改后端运行时行为的插件仍可能需要重启应用才完全生效。目录不可用时,本地管理和官方添加记录仍可查看;中断的官方操作会显示待确认状态及可用的恢复操作。

确认卸载后,已添加列表和详情会自动同步,无需刷新页面。卸载成功后的关闭过程不会显示“本地内容不可用”;取消或卸载失败时保留当前选择及相应提示。官方添加记录仍会保留,失效的本地详情链接仍会提示不可用。

装之前看什么

  • 确认来源、版本,以及它要改哪些页面或工具。
  • 核对插件声明的 CogineWork 版本范围;超出范围时界面会警告,安装后可能出错。
  • 不要把 API Token、账号密码写进插件文件或截图。
  • manifest.trust 不能授权自定义 endpoint、远程媒体写入或其他高风险配置。高风险信任要靠签名校验,或本地操作者明确确认。

装上之后去哪用

  • 插件提供的页面,出现在它声明的产品位置;功能与设置入口也会分别出现在 Work → 扩展 → 已添加 → 插件。需要智能体的功能页会要求明确选择目标。
  • 频道插件装好后,到 Work → 连接 或该智能体详情的 连接 分区配置凭据。开发合同见开发频道插件。
  • 技能通常从技能池明确分配,并在智能体详情的 技能 分区管理。插件也可能通过自己的初始化流程安装包内技能;具体目标和保留规则见下方开发者说明。

和 MCP、技能怎么选

你想做的事用什么
告诉智能体一套可复用步骤技能
给这个智能体接一组外部工具MCP
给这台客户端加功能包或新渠道本页的插件

官方前端插件的配套更新

新的前端插件可以使用客户端提供的表单、列表、弹窗和状态组件,跟随客户端的亮暗主题。 本轮 UI SDK 配套开发版本包括光合运营 0.5.0 和包含 UI SDK 1 的 CogineWork 客户端; 实际发布和桌面验收以配套交付记录为准,不能仅凭 2.4.0b1 版本号判断已包含 SDK。

更新时先安装配套客户端,再从扩展库更新官方插件,按现有提示刷新或重启。 若提示“请配套更新客户端与官方插件”,核对两份安装包的交付说明。 不要删除智能体工作区、配置、凭据或业务数据来修复界面加载。 回退也应使用保存的客户端与插件配套包,并保留这些数据。

插件可在设置扩展列表提供常规入口;Work 侧栏最多显示两个插件快捷入口, 没有显示快捷入口时仍可从常规入口打开。

开发者可从仓库的 console/examples/frontend-plugin-template/ 开始编写 React/TSX, 使用 console/plugin-sdk/ 的构建工具生成单文件入口。SDK 类型、默认样式、共享依赖、 错误重试和注册清理的完整说明见这两个目录的 README。后端 plugin_host_api 的含义不变。

开发和发布插件

开发或上架新插件时,先读插件发布 V1:作者与智能体指南,按其中的包、元数据和验收清单交付。

开发者:向已有智能体安装技能(Host API 1.3)

插件可以给已有的公告助手安装标讯技能,再给标书专家安装技术标技能;其他智能体和之后新建的智能体不会因这些调用获得技能。宿主不会自动创建、启动或重载目标智能体。

发布状态:本节对应本任务分支的未发布改动,Host API 为 1.3.0。 分支已同步应用版本为 3.1.1 的主线,但已发布的 3.1.1 安装包不包含此能力。正式发布时须以实际包含该实现的首个客户端版本填写应用版本下限;目前尚无已确认的正式最低应用版本。

新插件必须同时声明应用版本和 Host API 区间。下面仅为包含本次实现的分支源码构建提供开发验证片段,3.1.1 是分支继承的应用版本,不能直接当作正式上架的最低版本承诺:

json
{
  "coginework_version": { "min": "3.1.1", "max": "4.0.0" },
  "plugin_host_api": { "min": "1.3.0", "max": "2.0.0" }
}

两个区间在提供 max 时按 >=min, <max 检查;省略 max 时不设上界,仅要求 >=min。声明 Host 区间的插件按真实应用预发布版本比较,不把 beta 当作正式版。普通 ZIP 的替换前检查与 loader 使用同一个公共检查;不满足任一区间时拒绝。未声明 Host 区间的旧插件保持既有应用版本检查行为。新插件不能在旧宿主上退回全局分发来模拟目标安装。

调用与参数

python
async def install_skills_to_agent(
    self,
    skills_dir: str | Path,
    *,
    target_agent_id: str,
    skill_names: Sequence[str] | None = None,
    enabled_by_default: bool = True,
    channels: Sequence[str] | None = None,
    overwrite: bool = False,
) -> dict[str, Any]:
    ...

Path 来自 pathlib,Sequence 来自 collections.abc,Any 来自 typing。

  • skills_dir 是当前插件包内的技能容器。相对路径以宿主绑定的实际插件目录为基准;绝对路径使用宿主绑定的安装路径或其规范化实际路径,且须解析到包内。宿主安装位置上层的系统或用户目录链接可以作为路径别名;包内技能容器和内部路径中的符号链接、包外资源及特殊文件不被接受。
  • target_agent_id 是已登记智能体的精确 ID,不是显示名称或工作区路径。缺少必填 Python 参数会抛出 TypeError;None、空串、纯空白、错误类型、非法或不存在的目标会返回错误,不能解释为全局安装。缺失、损坏、身份不符或被多个智能体共用的工作区也失败且不写入。已通过参数校验的普通目标 ID 在未登记、工作区不可用或共享时仍原样返回,便于关联请求;不会替换为其他智能体。已完整存在但停用的普通智能体可安装,停用状态保持;现有 default 智能体沿用工作区技能管理规则。系统管理智能体与技能仍受保护;本机 Policy 已退役,新接口不增加角色或 capability 检查。
  • skill_names=None 枚举容器中的技能;显式列表只选这些技能。[] 在校验目标后返回无写入的 noop,不解析或读取源目录,也不要求资源存在,不代表全部;None 枚举时空容器报资源错误。名称必须是唯一的规范目录标识,与 SKILL.md 中的名称一致;路径、重复项、保留系统技能名和把字符串当列表均无效。明确选中但缺失的资源按项失败。
  • enabled_by_default=True、channels=None(新项使用 ["all"])只影响首次登记。两个开关参数 enabled_by_default、overwrite 只接受 bool;channels=[]、空渠道名或字符串序列均报参数错误。渠道选择消息渠道,不选择智能体。重复调用和覆盖均保留已有 enabled/channels/config。
  • overwrite=False 默认保留不同内容;True 只允许显式选择的非空 skill_names 中、归属本插件且目录与清单完整的同名技能,详见覆盖规则。

下面是后端入口 plugin.py。先用现有创建流程准备两个智能体,并把对应技能目录放入插件的 skills/;不要在模块顶层导入时执行安装。

python
from pathlib import Path


class ExpertPlugin:
    def register(self, api):
        async def prepare_skills():
            first = await api.install_skills_to_agent(
                Path(__file__).parent / "skills",
                target_agent_id="procurement-notice-assistant",
                skill_names=[
                    "procurement-opportunity-automation",
                    "bid-spreadsheet-delivery",
                ],
            )
            second = await api.install_skills_to_agent(
                Path(__file__).parent / "skills",
                target_agent_id="bid-writing-expert",
                skill_names=["technical-bid-authoring"],
            )
            for result in (first, second):
                if not result["success"]:
                    raise RuntimeError(
                        f"{result['target_agent_id']}: {result['message']}"
                    )

        api.register_startup_hook(
            "prepare_expert_skills", prepare_skills, priority=80
        )


plugin = ExpertPlugin()

示例由插件主动选择已有 startup hook 作为调用时机;新方法本身不注册启动、新工作区或卸载钩子,也不排队等待未来目标。缺失目标应先通过现有流程准备,再明确重试。重复初始化时,同源、完整目录内容相同的技能返回 already_installed,不复制、不重复登记、不重置用户配置。

冲突、显式覆盖与恢复

不同来源、UI 编辑后成为 customized、来源不明、目录或清单残留均不接管;即使文件字节相同或传入 overwrite=True,仍返回冲突或资源错误。同源内容不同默认返回 conflict,包括用户在磁盘上修改或新增的文件。损坏清单、不可读目录和比较失败会明确失败,不按空清单或相同内容继续安装。

插件可以在自己的现有流程中让用户选择覆盖,也可以由作者在有意替换同源技能的调用点显式传参。下面只选择一个技能;参数表示调用方的写入策略,不代表宿主已核实最终用户确认。不要无条件重试覆盖全部冲突。

python
result = await api.install_skills_to_agent(
    Path(__file__).parent / "skills",
    target_agent_id="bid-writing-expert",
    skill_names=["technical-bid-authoring"],
    overwrite=True,
)

覆盖会完整替换该技能目录,旧目录内的用户新增文件不合并进新版。宿主先校验新内容,并在目标技能根目录的唯一隐藏 backup_dir 中保全、验证完整旧目录和原清单条目,再原子替换。备份失败不覆盖。返回 overwritten 时保留原有 enabled/channels/config/installed_from;结果不返回可能含凭据的配置。

已验证的恢复副本在成功、失败、外层回滚及卸载后均保留,路径不在插件包内,也不由事务临时清理删除。用户之后可显式处理备份。重复调用遇到同源同内容,即使 overwrite=True 也返回 already_installed,不创建新备份。发生并发用户修改或无法安全恢复时返回 recovery_required 和恢复位置,保留当前内容及恢复材料;不要删除工作区或重建清单来强行重试。外层恢复重放也不能用覆盖参数绕过这个保护。

backup_dir 只标识完整目录与原清单条目均验证通过的副本。复制、序列化或验证失败时保留部分材料,但不返回该字段,不能将部分目录当作可恢复副本。

返回值与提交边界

json
{
  "success": false,
  "target_agent_id": "bid-writing-expert",
  "outcome": "partial",
  "commit_state": "committed",
  "message": "一个技能已安装,另一个技能因内容冲突未安装",
  "results": [
    {
      "name": "technical-bid-authoring",
      "status": "installed",
      "code": null,
      "message": "已安装"
    },
    {
      "name": "bid-review",
      "status": "conflict",
      "code": "existing_content_differs",
      "message": "已有内容不同,已保留"
    }
  ],
  "error": null
}
  • outcome 为 complete/partial/failed/noop;只有全部请求完成或合法空列表时 success=true。逐项 status 为 installed/overwritten/already_installed/conflict/failed/not_attempted,均含 name/status/code/message;有已验证恢复副本的覆盖成功或失败项另含 backup_dir。
  • 目标级失败在 error 返回 {code, message},合法请求项为 not_attempted。稳定错误代码包括 invalid_argument、invalid_target_agent_id、target_not_found、target_workspace_unavailable、target_workspace_shared、forbidden、source_outside_plugin、invalid_skill_names、skill_not_found、invalid_skill_resource、scan_rejected、manifest_corrupt、source_conflict、existing_content_differs、backup_failed、concurrent_modification、write_failed、recovery_required。
  • 普通显式调用无外层包事务时逐技能提交,返回 committed。后项失败不撤销前项,不同目标两次调用也没有共同事务;重试已完成项返回 already_installed。
  • 普通 ZIP 安装/替换的激活调用加入其真实外层事务,成功写入先返回 pending_plugin_activation,最终插件安装响应才代表外层提交。结构化冲突、部分失败或写入失败会锁存该轮失败,插件忽略结果或同轮第二次覆盖都不能将整轮改成成功。先结束并恢复该轮,再在新一轮明确选择覆盖。
  • 官方资源激活若没有这个外层事务,仍返回 committed;其后插件激活失败不表示技能已回滚。仅“正在激活”不能推出 pending。plugin.loaded、日志成功行和钩子注册成功也不代替本次安装结果;插件要阻断激活时须检查 success 并通过现有异常通道报错。

更新、卸载与旧全局副本

新安装项保留 source="plugin:<plugin_id>",首次登记 installed_from="plugin-api",界面显示普通“插件 / Plugin”来源。它们不随插件更新、停用、卸载或新版省略技能而自动删除;删除使用现有技能管理流程,更新通过再次显式覆盖。副本保留不代表插件工具继续可用,工具仍遵循插件生命周期,用户技能开关不会被联动改写。

旧 register_skill_provider() 的签名和全局广播语义保持。新接口即时安装,不替旧 provider 改变分发策略;作者不应同时用全局 provider 投递同一组目标技能。即使混用,带 plugin-api 标记的副本也不参与旧 provider 的差集和卸载删除;每次成功的目标调用(包括历史副本 no-op)都保留本轮请求项。仅使用新调用的替换轮次保留未提及的旧项。

历史 global 副本同源同内容可直接返回 already_installed;符合条件时也可显式覆盖。两者均不补 plugin-api 标记,覆盖继续保留原出处。若之后某版本既不调用新方法,也没有可识别的 direct 标记,仍可能按旧差集规则删除这些历史副本;旧 provider 卸载也仍可清理未标记项,包括曾被显式覆盖的项。需要继续保留时应继续明确调用;本接口不迁移或清理历史副本,独立恢复备份始终保留。

旧宿主不理解新增方法及 direct 清理边界;降级不保证继续保留这些语义。保留工作区、配置和备份,不在旧宿主上重跑自动安装或清理来“修复”副本。发布前应在临时实例验证多目标隔离、空目标、空选择、重试、配置保留、显式覆盖备份、ZIP 激活失败恢复及旧 provider 共存;不能以静态合同或插件 loaded 状态代替这些运行验收。

开发者:旧 Prompt API 迁移

删除 PluginApi.register_prompt_section、PluginRegistry.register_prompt_section、 get_prompt_sections 和 coginework.agents.prompt_builder 后,仍调用这些入口的插件会加载失败。 只删除注册调用可以消除加载错误,但不会恢复插件提示词。需要保留提示词功能时,应迁移到下面的实际执行路径。

旧 registry 曾接入早期 Agent;AgentScope 2 迁移后,当前运行时已不再消费它。 现在基础提示词由 AgentBuilder.build_prompt() 通过 PromptManager.build_sync() 组装, 插件可用已存在的 api.register_middleware() 和 AgentScope 的 MiddlewareBase.on_system_prompt() 追加说明。无需为此创建一个空 Mode。

旧功能如何对应

旧用法当前改法行为差异
provider(agent) 返回提示词片段在 on_system_prompt(agent, current_prompt) 中调用 provider,返回原提示词加片段hook 收到实际 Agent;核对 provider 使用的 Agent 字段是否仍存在。
condition(agent) 控制注入在 hook 中判断,不满足时返回 current_prompt若条件只依赖请求或 Agent 配置,也可在 factory 中判断,不满足时返回 None。
只对某个 Agent 生效factory 检查 ctx.agent_idfactory 收到请求上下文 ctx 和 agent_config,它们与 hook 的 agent 不是同一对象。
priority 控制多个插件的顺序api.register_middleware(factory, priority=100)数值越小越先执行,仅排序插件 middleware;不会改变宿主 middleware 或 contributor 的位置。
after="workspace"、"multimodal"、"env_context"没有等价的稳定锚点接口middleware 处理已经组合的提示词。依赖精确插入位置的插件不能宣称无损迁移;不要通过搜索、替换宿主文本伪造锚点。
get_prompt_sections() 查询注册项插件自行保存自身的配置或元数据没有等价的新提示词注册表查询 API。

可运行的追加示例

下面是完整的后端入口 plugin.py。已有插件可保留自己的 manifest 和其他注册逻辑, 将旧 register_prompt_section 调用替换成这里的 middleware 注册。 新建示例时,将后面的 plugin.json 与它放在同一文件夹,再通过「设置 → 扩展库 → 插件库」安装文件夹。

python
import logging

from agentscope.middleware import MiddlewareBase

logger = logging.getLogger(__name__)


class ExtraGuidance(MiddlewareBase):
    def __init__(self, config):
        self.config = config

    async def on_system_prompt(self, agent, current_prompt):
        try:
            # Replace this check with the former condition(agent), if any.
            if not self.config.get("enabled", True):
                return current_prompt
            # Call the former provider(agent) here when migrating a plugin.
            fragment = self.config.get(
                "text", "Include units with quantities."
            )
            if not isinstance(fragment, str):
                raise TypeError("Plugin prompt text must be a string")
            fragment = fragment.strip()
        except Exception:
            logger.exception("Could not build plugin prompt")
            return current_prompt

        if not fragment:
            return current_prompt
        if not current_prompt:
            return fragment
        return f"{current_prompt}\n\n{fragment}"


class GuidancePlugin:
    def register(self, api):
        def factory(ctx, agent_config):
            target = api.config.get("agent_id")
            if target is not None and ctx.agent_id != target:
                return None
            return ExtraGuidance(dict(api.config))

        api.register_middleware(
            factory, priority=api.config.get("priority", 100)
        )


plugin = GuidancePlugin()
json
{
  "id": "prompt-guidance-example",
  "name": "Prompt Guidance Example",
  "version": "1.0.0",
  "type": "general",
  "entry": { "backend": "plugin.py" },
  "coginework_version": { "min": "2.4.0", "max": "2.4.1" }
}

示例默认对所有 Agent 追加 Include units with quantities.。宿主传给插件的 api.config 可包含 agent_id(目标 Agent)、enabled(是否追加)、text(提示词文本)、 priority(执行顺序);未提供配置时使用代码中的默认值。迁移自己的插件时,沿用原配置来源和条件。

示例验证基于 CogineWork 2.4.0b1 / AgentScope 2.0.7。当前兼容性检查会把宿主预发布版本 按基础版本比较,因此示例的 >=2.4.0, <2.4.1 范围可加载在 2.4.0b1 上。 这只是示例的窄范围声明,不代表已经验证所有预发布版本;发布自己的插件前,应逐一验证实际支持的版本。 register_middleware 目前属于内部扩展入口,不在稳定 Plugin Host v1 方法清单中, 不能把当前可用实现或 plugin_host_api 声明当作它的长期兼容保证。

运行时需要保留的语义

  • 保留宿主提示词。 在收到的 current_prompt 后追加,不修改 agent._system_prompt, 不把上次渲染结果存起来再追加。此时宿主、技能和 offloader 的提示词已经组装完成。
  • 保留失败回退。 旧 provider/condition 出错时会被捕获;middleware hook 不会自动提供同样的回退。 示例把条件、provider 和返回值检查放在 try 中,失败时记录日志并返回原提示词。
  • 区分构建与渲染。 factory 在每次构建 Agent 时执行;on_system_prompt 在准备模型输入时执行, 同一回合可能调用多次。不要在每次渲染时无条件执行昂贵 I/O 或有副作用的操作。
  • 卸载作用于后续构建。 PluginLoader 卸载插件后会移除它的 middleware factory; 新构建的 Agent 不再注入,其他插件仍保留。已构建或正在执行的 Agent 仍持有原 middleware, 不承诺卸载会即时改变正在运行的回合。示例也会在构建时复制配置,配置修改需在后续构建中生效。

如果插件本来就提供完整的 Mode,可使用 AgentMode.prompt_contributors() 配合 SyncPromptContributor。但它的 prompt context 不是完整请求上下文,不能直接照搬 provider(agent);build_sync() 也不会等待异步 contributor。 当前已有工作区的 Mode/contributor 卸载清理还不完整,因此不推荐把它作为单纯追加提示词的迁移方案。

其他被移除入口

  • StopHandler.set_continuation():把继续执行文本放进自有 StopGate.build_continuation(), 由原来的 check(ctx) 决定何时返回 StopAction.INTERRUPT_AND_CONTINUE。 如果原插件通过 api.register_agent_stop_handler() 注册独立 handler,则由该 handler 返回带 continuation_message 的 StopHandlerResult。继续文本作为下一条 user 消息注入, 不是每回合的 system prompt;保留原停止条件、作用域和执行次数上限,不要改成无条件继续。
  • ApprovalService.set_channel_manager():删除调用即可。该方法原来就是 no-op, 不负责审批通知;迁移时仍应验证插件实际使用的审批发送路径。
  • coginework app --workers N:删除参数及其值,见命令行迁移说明。

插件作者的验收步骤

  1. 在目标版本加载迁移后的插件,确认加载成功且原来的工具等其他功能仍可用。
  2. 检查实际准备发送给模型的 system 消息:宿主文本完整、插件片段存在,重复组装不累加。
  3. 覆盖目标与非目标 Agent、条件开关、空片段、provider 抛错和返回错误类型。
  4. 同时加载两个插件检查顺序;卸载一个后,新构建的 Agent 不再注入它,另一个仍有效。
  5. 重新加载后确认片段不重复,并验证已开始的回合允许保留旧 middleware。

仓库的 tests/unit/plugins/test_prompt_middleware_migration.py 会直接执行本页代码块, 通过实际插件加载、Agent 构建和模型输入准备路径验证上述提示词行为,不调用真实模型。

相关页面:工作台 · 智能体 · 频道 · 开发频道插件

工具作用和目标任务权限(Host API 1.1)

插件可通过 from coginework.plugins import ToolGovernance, ToolEffect, ToolInvocationContext 声明工具作用:

python
api.register_tool(
    "read_report", read_report,
    governance=ToolGovernance(effects=(
        ToolEffect(kind="file", operation="read", target_param="path"),
    )),
)

文件支持 read/write/delete,网络支持 read/write,进程支持 execute。每个作用选择 target_param 或同步纯函数 target_resolver(context, arguments);后者返回非空目标字符串元组。context 包含宿主绑定的 agent_id、workspace_dir、可选 chat_id 和 task_directory。不要在 resolver 中创建目录、访问数据库/网络或调用模型。递归读取目录需声明 recursive_search=True。混合作用必须完整声明,业务确认仍由工具实现执行。

声明随宿主统一登记和卸载。名称冲突会报错;省略声明仍可加载,但托管权限下保持未分类审批。工作区/任务目录内的非递归纯本地读取可确定性允许;递归读取保留现有规则和审批,不进入该默认放行。敏感目标、明确禁止规则及 strict 继续生效;作用声明不提供恶意代码隔离。

插件页面发送任务前,读取明确目标的原始选择:

javascript
const { approvalLevel } = await window.CogineWork.host.getTaskPermission({
  agentId,
  chatId,
});

approvalLevel 为用户当前选择或 null,不是计算后的 Agent 默认。getter 在异步校验前捕获目标选择,目标不存在或存储读取失败会报错。插件经 HTTP/CLI 发起任务时,将非空值传给 agents chat --approval-level;null 时省略。只有 chat_id/session_id 的独立 CLI 不会读取浏览器存储。不要硬编码 full_access。