PKU harness

架构

PKU harness 的框架层由 Agent 循环、设计系统和宿主服务组成,具体功能都由插件通过同一套接口提供。

PKU harness 的设计借鉴了 DeepSeek Harness 的两个思路。第一,App 的功能都由插件提供。第二,App 以 AI 助手为核心,但你和助手能做的事情完全相同。

为了做到第二点,每项功能只实现一次,形式是一个有类型的工具(tool)。你在界面上点按钮时,界面调用这个工具;你在对话里提出需求时,助手调用同一个工具。

分层结构

App 的代码分为四层,从上到下依次是界面、核心运行时、插件和原生模块。

界面          路由 · 页面 · 设计系统(设计令牌、组件、动效)· 生成式界面组件目录
                   │
核心运行时    插件注册表与权限 · Agent 循环 · 看板 · 定时任务 · 存储
              校园会话管理(IAAA、各服务的会话)
                   │
插件          PLUGIN.md 插件文档(解释执行)· 内置 TypeScript 插件 · 远程 MCP 插件 · 模型服务商
                   │
原生模块      campus-http 模块(控制重定向、每个会话独立的 cookie、HTTP/1.1、不走代理)

依赖关系只能向下。插件从不导入 App 的代码。插件需要做自身以外的事情时,要通过宿主服务(ctx.services);需要显示界面时,要通过 HostUI。

框架层的组成

我们把插件以下的这一层叫作框架(harness)。它由四个部分组成。

部分插件使用的接口
Agent 循环tools、instructions、context、contributes.commands
设计系统HostUI(host.components、host.useTheme() 等)
宿主服务HostServices(ctx.services、props.services)
生成式界面ui.render 工具、HostUI.components.GenUI

Agent 循环负责和模型对话。它基于 AI SDK,模型由任意一个模型服务商插件提供。每次请求时,它把所有已启用的工具交给模型,并以流式方式接收回答。模型可以按任务需要连续调用工具,单次回复没有步骤上限,直到模型给出回答或你按停止才结束。回复进行中,如果历史记录超过上下文预算,Agent 循环会把较早的步骤压缩成摘要,然后继续。它还为每个会话保存模型可见的历史记录,并记录 token 用量。无人值守的定时 Agent 任务仍有一个较小的步骤上限。

设计系统包括设计令牌、原生控件和动效。插件的页面用它搭建,所以看起来和 App 的原生页面一致。App 更新后,这些页面也能继续正常工作。

宿主服务是 App 提供给插件的一组服务,包括校园 HTTP 请求、定时任务、小组件、待办、课表、通知权限状态、文件、浏览器、加密、插件注册表和设置注册表。

生成式界面是一个组件目录。助手写出一段 JSON 描述,App 就按描述在对话或看板里显示对应的组件。

助手能操作一切

你在 App 里能做的每一件事,助手都能通过一个工具来做。各个页面只是进入同一份数据的另一条途径,而不是另一套逻辑。整个 App 分为三个部分。

部分操作的对象通过什么
校园服务和数据校园服务、校园卡、日程、待办、看板、记忆、附件各个插件的工具
App 本身所有设置:外观、语言、时区、触感反馈、AI 服务和语音服务、每个插件的开关和权限、每个插件自己的设置项设置注册表和核心插件 settings
手机手机上的其他 AppCopilot(copilot.* 工具)

设置注册表是一份由带类型的条目组成的列表。每个条目有键名、分组、标题、类型(choice、boolean、text 或 list)、可选值和当前值。它包括:

  • 宿主自己的设置:app.theme、app.language、app.timezone、app.haptics、ai.provider、ai.model 和 voice.provider;
  • 每个插件的 plugin.<id>.enabled(核心插件没有这一项)和 plugin.<id>.permission.<effect>,取值为 auto、ask 或 off;
  • 每个插件自己的设置项,在清单的 PluginDef.preferences 中声明,键名为 <plugin>.<name>,例如 copilot.enabled、copilot.apps 和 copilot.screenshots。

插件只要声明设置项,就能加入注册表,宿主不需要任何改动。标记为 uiOnly 的条目(例如 API Key、校园账号和系统通知权限)会和它所在的页面一起列出。助手不能修改它们,只能为用户打开那个页面。

核心插件 settings 给助手提供三个工具:list(读取)、change(修改)和 open(打开页面)。修改遵循通常的权限模式:在改动任何内容之前拒绝未知的键和 uiOnly 的键,按条目校验每个值,并返回每个键修改前后的值。插件通过 HostServices.settings 使用同一个注册表。

四个标签页由谁提供

标签页提供者
对话框架
看板dashboard 插件
日程schedule 插件
设置框架,其中部分设置项由插件提供

看板和日程这两个标签页在框架里只是两个空的位置,我们叫它插槽。插件通过 contributes.tabs 填充插槽。

插件

插件有三种形式:

  • 插件文档:一个 PLUGIN.md 文档,由 App 解释执行;
  • 内置插件:用 definePlugin 写成的 TypeScript 对象,随 App 发布;
  • 远程插件:一个 MCP 服务器。MCP 是 Model Context Protocol(模型上下文协议)的缩写。

无论哪种形式,框架都会把它转换成同样的结构:一组工具,加上可选的界面。每个工具的 id 形如 <plugin>.<tool>,并带有效果(effect)和输入 schema。

下面是一个最简单的内置插件。它只有一个工具 greet,用来打招呼。

import { definePlugin, defineTool } from '@pku-harness/sdk';
import { z } from 'zod';

export const helloPlugin = definePlugin({
  id: 'hello',
  name: '你好',
  description: '演示插件。',
  version: '0.1.0',
  scopes: [],
  tools: {
    greet: defineTool({
      title: '打招呼',
      description: 'Greet someone by name.',
      input: z.object({ name: z.string() }),
      effect: 'read',
      execute: async ({ name }) => ({ greeting: `你好,${name}` }),
    }),
  },
});

同一个插件也可以写成插件文档:一个带有 tools: { greet: … } 的 PLUGIN.md,工具的逻辑用 YAML 步骤来写,见声明式插件。三种插件的对比见插件。

数据存在哪里

数据位置
校园账号密码、模型和语音输入的 API 密钥、远程插件的令牌手机的密钥库(SecureStore)
聊天会话、用量、小组件、定时任务、待办、课表、插件的键值存储手机上的 SQLite 数据库
你主动开启的插件云端数据PKU harness Cloud 服务,端到端加密

默认情况下,数据不经过本项目的任何服务器。你配置的模型服务商会收到对话内容。校园服务收到的请求和浏览器发出的请求相同。

更新如何送达用户

不同的代码有不同的更新方式。

改动的内容送达方式
插件文档(PLUGIN.md),包括随 App 内置的官方插件App 重新拉取它的链接
远程插件服务器自己更新,App 每次实时调用
插件市场的条目(registry.json)App 实时拉取
App 的 JavaScript 代码、SDK、内置 TypeScript 插件签名的空中更新(OTA),或者发布新版 App
原生代码(原生模块、系统权限)发布新版 App

插件文档的链接每天最多重新拉取一次。如果新版本的声明没有变化,它会直接生效。如果新版本要求更宽的权限,要等你确认后才生效。插件文档的更新完全不经过 OTA。

空中更新(over-the-air update,简称 OTA)从我们自己的更新服务器下发,下次启动 App 时生效。它基于 expo-updates,服务端是一个静态的、经过代码签名的服务器(https://pkuharness.com/updates/)。每次更新都由持续集成(CI)用只有维护者持有的密钥签名。App 内置了对应的证书,会拒绝其他来源的更新。

runtimeVersion 采用 fingerprint 策略。也就是说,只要原生代码有改动,运行时版本就会变化,旧的 APK 不会接收新的 OTA 更新,用户需要安装新的 APK。

继续阅读

  • 插件:插件文档、内置插件和远程插件的区别,以及如何选择。
  • 工具与效果:插件对外提供功能的唯一方式。
  • 安全模型:账号凭据、提示词注入和用户授权。

本页目录