mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-06-23 05:33:37 +08:00
refactor: organize agent harness courses
This commit is contained in:
260
learn-pi-agent/s12_package_distribution/README.md
Normal file
260
learn-pi-agent/s12_package_distribution/README.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# s12: Package Distribution — 能力整理成一个包
|
||||
|
||||
> *一组能力,一张清单,整体带走。*
|
||||
> **Pi 边界**:能力分发边界 —— manifest 是入口,决定哪些内容可见。
|
||||
|
||||
[上一节:s11](../s11_trust_and_execution_boundary/) → `s12`
|
||||
|
||||
---
|
||||
|
||||
## 问题
|
||||
|
||||
到现在,工具、命令、项目资料都是零散定义的。想复用一整套能力(某个项目的全部工具 + 命令 + 资料),没有个地方说明"这包里到底有什么"。
|
||||
|
||||
零散定义没法整体分发,也没法整体加载——拿到一堆内容,不知道哪些该用、哪些是多余的。
|
||||
|
||||
s12 要把它们整理成一个**带清单的包**。
|
||||
|
||||
---
|
||||
|
||||
## 解决方案
|
||||
|
||||
一个包由两部分组成:
|
||||
|
||||
```text
|
||||
manifest 清单:声明包里有哪些 tools / commands / resources(按名字)
|
||||
contents 实际内容:名字 → 内容
|
||||
```
|
||||
|
||||
`loadPackage` 按 manifest 从 contents 里挑出对应内容。**清单就是入口**:清单上列了才加载,没列的(哪怕 contents 里有)一律不进结果。
|
||||
|
||||
加载完还要接回主线:`installLoadedPackage` 把 loaded 结果装回已有的 ToolRegistry、commands 和 resources。这样 package 才不只是一个清单解析 demo,而是能真的把能力分发回 mini Pi。
|
||||
|
||||
---
|
||||
|
||||
## 工作原理
|
||||
|
||||
**先定义清单和包。**
|
||||
|
||||
```ts
|
||||
export type PackageManifest = {
|
||||
name: string;
|
||||
tools: string[];
|
||||
commands: string[];
|
||||
resources: string[];
|
||||
};
|
||||
|
||||
export type Package = {
|
||||
manifest: PackageManifest;
|
||||
contents: Record<string, string>;
|
||||
};
|
||||
```
|
||||
|
||||
**按名字挑内容。** `pick` 从 contents 里取清单上列出的名字;清单列了但 contents 里没有的,跳过(不会因为缺一项就崩)。
|
||||
|
||||
```ts
|
||||
function pick(contents: Record<string, string>, names: string[]): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
for (const name of names) {
|
||||
const value = contents[name];
|
||||
if (value !== undefined) {
|
||||
result[name] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**按清单加载。** `loadPackage` 对三类资源分别 pick,产出 LoadedPackage。
|
||||
|
||||
```ts
|
||||
export function loadPackage(pkg: Package): LoadedPackage {
|
||||
return {
|
||||
name: pkg.manifest.name,
|
||||
tools: pick(pkg.contents, pkg.manifest.tools),
|
||||
commands: pick(pkg.contents, pkg.manifest.commands),
|
||||
resources: pick(pkg.contents, pkg.manifest.resources),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**再安装回主线。** `loadPackage` 只负责挑内容,`installLoadedPackage` 才负责把内容接回前面已经有的零件。
|
||||
|
||||
```ts
|
||||
export function installLoadedPackage(
|
||||
loaded: LoadedPackage,
|
||||
registry: ToolRegistry,
|
||||
commands: Map<string, Command>,
|
||||
resources: ContextResource[],
|
||||
): void {
|
||||
// loaded.tools -> registry.register(...)
|
||||
// loaded.commands -> commands.set(...)
|
||||
// loaded.resources -> resources.push(...)
|
||||
}
|
||||
```
|
||||
|
||||
加载后的 tools / commands / resources,分别注入 s02 的 ToolRegistry、s09 的 commands、s08 的 ResourceLoader。这一节把前面散落的能力收拢成一个可分发、可安装的整体。
|
||||
|
||||
> 这一节真正建立的是**能力分发边界**:manifest 是唯一入口,决定一个包对外暴露什么。contents 里再多东西,只要 manifest 没列,就不会被加载——分发方靠清单精确控制可见能力。
|
||||
|
||||
---
|
||||
|
||||
## 试一下
|
||||
|
||||
运行:
|
||||
|
||||
```sh
|
||||
npm run s12
|
||||
```
|
||||
|
||||
输出类似:
|
||||
|
||||
```text
|
||||
s12: Package Distribution
|
||||
|
||||
[manifest]
|
||||
name: demo-package
|
||||
tools: note
|
||||
commands: status
|
||||
resources: AGENTS.md
|
||||
|
||||
[loaded]
|
||||
tools: 1
|
||||
commands: 1
|
||||
resources: 1
|
||||
|
||||
[installed]
|
||||
registry tools: 1
|
||||
commands: 1
|
||||
resources: 1
|
||||
note -> package tool note: tool: 保存一条笔记
|
||||
/status -> command: 打印包状态
|
||||
```
|
||||
|
||||
观察重点:contents 里其实有 4 项(含一个 `ignored`),但 loaded 只挑出 manifest 列出的 3 类各 1 项——`ignored` 因为不在清单里,没有被加载。随后 installed 证明这 3 项已经接回 mini Pi 的 registry、commands 和 resources。
|
||||
|
||||
---
|
||||
|
||||
## 接入主线
|
||||
|
||||
s12 在 s11 上累积,是 mini Pi 的最后一版。相对 s11 的变更:
|
||||
|
||||
| 组件 | s11 | s12 |
|
||||
| --- | --- | --- |
|
||||
| 新增类型 | — | `PackageManifest` / `Package` / `LoadedPackage` |
|
||||
| 新增函数 | — | `loadPackage` / `installLoadedPackage` / `pick` |
|
||||
| 主循环 / `ProviderInput` | — | **不变**(纯新增) |
|
||||
|
||||
**焊接点**:`loadPackage(pkg)` 按 manifest 从 contents 挑出 tools / commands / resources;`installLoadedPackage(loaded, registry, commands, resources)` 把它们注入既有 `ToolRegistry` / commands / `ResourceLoader`。s01–s11 的全部能力至此收拢成一个完整 mini Pi。
|
||||
|
||||
---
|
||||
|
||||
## 课程结束
|
||||
|
||||
12 节走完,mini Pi 覆盖了这条主线:
|
||||
|
||||
```text
|
||||
s01 Agent Core 接住一轮消息
|
||||
s02 Tool Contract 工具拆成说明和执行
|
||||
s03 Provider Event Stream provider 分段返回事件
|
||||
s04 Evented Tool Loop 工具请求 → 执行 → 结果回写,循环
|
||||
s05 Tool Hook Boundary 执行前后留插口
|
||||
s06 Turn Snapshot 一轮开始先拍快照
|
||||
s07 Session Tree 历史能分叉
|
||||
s08 Context Resources 项目资料进入输入
|
||||
s09 Extension Runtime 外部代码通过 API 接入
|
||||
s10 Runtime Modes core 产事件,外层决定展示
|
||||
s11 Trust and Execution 加载靠 trust,执行靠容器
|
||||
s12 Package Distribution 能力整理成包分发
|
||||
```
|
||||
|
||||
每一节只加一个机制,机制之间首尾相接。完整的 turn 执行链和总览,见[项目根 README](../README.md)。
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary>Pi 源码溯源:PiManifest 和三种包源</summary>
|
||||
|
||||
教学版用 PackageManifest(名字列表)+ contents(内容字典)+ loadPackage。Pi 的 `packages/coding-agent` 有完整的包管理,支持 npm/git/local 三种来源。
|
||||
|
||||
### 源码在哪
|
||||
|
||||
- `packages/coding-agent/docs/packages.md` — 包机制官方文档
|
||||
- `packages/coding-agent/src/core/package-manager.ts:92` — `PackageManager` 接口
|
||||
- `packages/coding-agent/src/core/package-manager.ts:147` — `PiManifest`
|
||||
- `packages/coding-agent/src/core/resource-loader.ts:22` — `ResourceLoader`
|
||||
|
||||
### PiManifest 的真实形状
|
||||
|
||||
教学版的 manifest 是 `{ tools, commands, resources }` 三个名字列表。Pi 的 manifest(`package-manager.ts:147`)声明四类资源的**路径**:
|
||||
|
||||
```ts
|
||||
interface PiManifest {
|
||||
extensions?: string[]; // 扩展路径
|
||||
skills?: string[]; // skill 路径
|
||||
prompts?: string[]; // 提示模板路径
|
||||
themes?: string[]; // 主题路径
|
||||
}
|
||||
```
|
||||
|
||||
放在 `package.json` 的 `pi` 字段里:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-package",
|
||||
"keywords": ["pi-package"],
|
||||
"pi": {
|
||||
"extensions": ["./extensions"],
|
||||
"skills": ["./skills"],
|
||||
"prompts": ["./prompts"],
|
||||
"themes": ["./themes"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
教学版的 tools/commands/resources 对应 Pi 的 extensions/skills/prompts/themes——Pi 没有单独的 "tools" 和 "commands",它们都由 extension 注册(s09)。
|
||||
|
||||
### 三种包来源
|
||||
|
||||
教学版的包是内存对象。Pi 的 `PackageManager`(`package-manager.ts:92`)支持三种来源:
|
||||
|
||||
```ts
|
||||
interface PackageManager {
|
||||
resolve(onMissing?): Promise<ResolvedPaths>;
|
||||
install(source: string, options?): Promise<void>;
|
||||
remove(source: string, options?): Promise<void>;
|
||||
update(source?): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
| 来源 | 格式 | 例子 |
|
||||
| --- | --- | --- |
|
||||
| npm | `npm:@scope/pkg@1.2.3` | 从 npm 安装 |
|
||||
| git | `git:github.com/user/repo@v1` | 从 git 仓库 |
|
||||
| local | `/absolute/path` | 本地路径 |
|
||||
|
||||
教学版的 `pick(contents, names)` 是 Pi `resolve` 的极简版——Pi 的 resolve 要解析三种来源、处理依赖、去重,复杂得多。
|
||||
|
||||
### glob + 排除 + 强制包含
|
||||
|
||||
manifest 的路径支持 glob,还能排除和强制包含:
|
||||
|
||||
```json
|
||||
"extensions": [
|
||||
"./extensions/**/*",
|
||||
"!extensions/legacy.ts", // 排除
|
||||
"+themes/legacy.json" // 强制包含(即使被排除规则匹配)
|
||||
]
|
||||
```
|
||||
|
||||
教学版没有这层路径模式。
|
||||
|
||||
### 安全警告
|
||||
|
||||
`packages.md` 明确:第三方包拿到的是完全系统访问权限(呼应 s11——Pi 不内置 permission)。装一个 pi 包等于让它跑任意代码,信任靠包来源和 s11 的 trust 机制。
|
||||
|
||||
### 一句话
|
||||
|
||||
教学版的 PackageManifest 立的是"清单驱动的按需加载"。Pi 把它坐实成 `pi` 字段声明四类资源路径 + npm/git/local 三种来源 + glob 排除规则 + 完整的 install/remove/update 生命周期。教学版用内存对象保留最小路径,但"manifest 是入口、决定哪些内容可见"这个心智一致。
|
||||
|
||||
</details>
|
||||
Reference in New Issue
Block a user