# 让 Agent 按需读取角色

读取小索引和单角色资料，核对版本、语言和 SHA-256。

## 从小索引开始

团队读者重点理解按需发现角色、提供上下文和确认权限即可；不必先克隆仓库或安装 Node，也不要求每位协作者手动计算 SHA-256。继续阅读“读取与信任的边界”；“本地快照校验与失败练习”可留给开发者。

1. 读取 llms.txt，选择 zh 或 en 索引。
2. 按当前任务选择一个 ID，再读取其 JSON 或 Markdown。
3. 说明输入、输出、前提和需求，在 Agent 自身规则下处理任务。

- [读取指南](https://harzva.github.io/awesome-bot/llms.txt)
- [中文索引](https://harzva.github.io/awesome-bot/agents/zh/index.json)
- [英文索引](https://harzva.github.io/awesome-bot/agents/en/index.json)
- [读取与信任的边界](https://harzva.github.io/awesome-bot/handbook-agents.html#trust)

## 四种机器文件

| schema | 实际用途 |
| --- | --- |
| openbot.market.registry/1 | 发现版本、清单 URL 和完整性摘要 |
| openbot.market/1 | 规范角色清单：persona、技能和声明需求 |
| openbot.fleet/1 | 供宿主预览与确认的配置 |
| awesome.bot.agent/1 | 语言化角色上下文、来源和 configuration 字段 |

id、templateVersion、locale、contentRevision 定位上下文；configuration.bytes 和 configuration.sha256 定位下载字节。语言投影与规范清单可能字节不同，不可用中文清单摘要核对英文 Fleet。

下面是现有进度报告角色 Agent 卡的字段节选，不是可直接导入的完整文件。清单中的声明需求也不是已授予权限；以 Fleet 导出和宿主确认中的实际设置为准。

```json
{
  "schema": "awesome.bot.agent/1",
  "id": "awesome/status-report-writer",
  "templateVersion": "1.0.0",
  "locale": "en"
}
```

## 读取并核对 SHA-256

以下 Node.js 22+ 示例只读取和校验，不导入或执行配置。摘要不一致时停止，重新获取一致版本或带校验文件的发布快照。

```js
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
const base = 'https://harzva.github.io/awesome-bot/';
async function read(url) {
  const r = await fetch(url, { signal: AbortSignal.timeout(10000) });
  assert.equal(r.status, 200);
  return r;
}
const card = await (await read(base + 'agents/en/status-report-writer.json')).json();
assert.equal(card.schema, 'awesome.bot.agent/1');
assert.equal(card.id, 'awesome/status-report-writer');
assert.equal(card.locale, 'en');
const bytes = Buffer.from(await (await read(card.configuration.url)).arrayBuffer());
assert.equal(bytes.length, card.configuration.bytes);
assert.equal(createHash('sha256').update(bytes).digest('hex'), card.configuration.sha256);
console.log(card.id, card.templateVersion, card.locale, card.contentRevision);
```

## 读取与信任的边界

Copy for Agent 只复制提示，网站不会联系 Agent。无法读链接时，经 Agent 正常粘贴或附件入口提供对应 Markdown，并说明限制。SHA-256 匹配说明与元数据一致，不证明发布者身份；当前目录未签名。

理解上述边界后，团队读者可继续排错；实际接入、下载或升级仍须执行对应契约要求的完整性与信任核验，由负责接入的人或工具完成。

- [团队路径：继续排错](https://harzva.github.io/awesome-bot/handbook-troubleshooting.html?path=team)

## 本地快照校验与失败练习

准备 Node.js 22+，先从 GitHub clone 本仓库并进入仓库根目录，运行下方准备命令。clone、依赖安装和构建可能需要网络；只有后面的校验阶段完全读取本地文件。它不调用模型，也不表示模型可以离线运行。 命令使用macOS/Linux或Windows WSL的shell；其他终端可将JS标记之间的代码另存为.mjs，再用Node运行。

```sh
git clone https://github.com/Harzva/awesome-bot.git
cd awesome-bot
npm ci --ignore-scripts
npm run check
npm run build:site
```

已 clone 并完成 npm ci --ignore-scripts 时，从 npm run check 开始。检查失败先修复或重新取得同一份完整源快照，不跳过检查。随后在仓库根目录整体复制执行下面的 sh 块：它按 discovery → 英文索引 → 进度报告角色卡 → Fleet 字节读取，URL 必须匹配预期站点与路径，本地路径与符号链接也不能逃出 dist/site。

```sh
node --input-type=module <<'JS'
import assert from 'node:assert/strict';
import { readFile, realpath } from 'node:fs/promises';
import { resolve, sep } from 'node:path';
import { createHash } from 'node:crypto';
const root = await realpath(resolve('dist/site'));
const base = new URL('https://harzva.github.io/awesome-bot/');
async function bytesAt(url, expected) {
  assert.equal(url, new URL(expected, base).href, 'unexpected URL');
  const u = new URL(url);
  assert.equal(u.origin, base.origin);
  assert.equal(u.search + u.hash, '');
  assert.ok(u.pathname.startsWith(base.pathname));
  const relative = decodeURIComponent(u.pathname.slice(base.pathname.length));
  assert.ok(!relative.includes('\\') && !relative.includes('\0'));
  const path = resolve(root, relative);
  assert.ok(path.startsWith(root + sep), 'path escaped site root');
  const actual = await realpath(path);
  assert.ok(actual.startsWith(root + sep), 'symlink escaped site root');
  return readFile(actual);
}
async function jsonAt(url, expected) {
  return JSON.parse((await bytesAt(url, expected)).toString('utf8'));
}
const discovery = await jsonAt(new URL('agents/index.json', base).href, 'agents/index.json');
assert.equal(discovery.schema, 'awesome.bot.agent-discovery/1');
assert.equal(discovery.configurationFormat, 'openbot.fleet/1');
assert.ok(discovery.contentRevision);
const languages = discovery.languages.filter(x => x.locale === 'en');
assert.equal(languages.length, 1);
const index = await jsonAt(languages[0].url, 'agents/en/index.json');
assert.equal(index.schema, 'awesome.bot.agent-index/1');
assert.equal(index.locale, 'en');
assert.equal(index.contentRevision, discovery.contentRevision);
assert.equal(index.count, index.entries.length);
assert.equal(languages[0].count, index.count);
const matches = index.entries.filter(x => x.id === 'awesome/status-report-writer');
assert.equal(matches.length, 1);
const entry = matches[0];
const card = await jsonAt(entry.json, 'agents/en/status-report-writer.json');
assert.equal(card.schema, 'awesome.bot.agent/1');
assert.equal(card.id, entry.id);
assert.equal(card.locale, index.locale);
assert.equal(card.contentRevision, index.contentRevision);
assert.ok(entry.templateVersion);
assert.equal(card.templateVersion, entry.templateVersion);
assert.equal(card.configuration.format, 'openbot.fleet/1');
assert.equal(card.configuration.locale, card.locale);
assert.equal(card.configuration.grantsTools, false);
assert.equal(card.configuration.grantsContainer, false);
assert.equal(card.configuration.includesAccountsOrMemory, false);
assert.equal(card.configuration.importRequiresHostConfirmation, true);
const original = await bytesAt(card.configuration.url, 'exports/en/status-report-writer.fleet.json');
function verify(bytes) {
  assert.equal(bytes.length, card.configuration.bytes, 'byte length mismatch');
  assert.match(card.configuration.sha256, /^[0-9a-f]{64}$/);
  assert.equal(createHash('sha256').update(bytes).digest('hex'), card.configuration.sha256, 'SHA-256 mismatch');
}
verify(original);
const fleet = JSON.parse(original.toString('utf8'));
assert.equal(fleet.schema, card.configuration.format);
assert.equal(fleet.mode, 'config');
assert.equal(fleet.bots.length, 1);
const bot = fleet.bots[0];
assert.equal(bot.id, card.id.split('/')[1]);
assert.equal(bot.toolsEnabled, false);
assert.equal(bot.containerEnabled, false);
assert.deepEqual(bot.messages, []);
assert.deepEqual(bot.files, []);
assert.deepEqual(bot.channels, {});
assert.deepEqual(bot.memory, { profile: '', journal: [] });
assert.equal(bot.summary, null);
assert.deepEqual(fleet.shared, { skills: [], files: [] });
console.log('PASS original snapshot');
const altered = Buffer.from(original);
assert.ok(altered.length > 0);
altered[0] ^= 1;
assert.throws(() => verify(altered), { code: 'ERR_ASSERTION', message: /SHA-256 mismatch/ });
verify(original);
console.log('PASS altered copy rejected');
JS
```

完成判据是依次出现 PASS original snapshot 和 PASS altered copy rejected 两行，进程退出码为 0。版本和摘要取自当前快照，不需要改成某个写死的版本或 SHA。负例只翻转 Buffer 副本的一字节，以 assert.throws 证明摘要校验拒绝；源码和原始产物没有被写入。

schema 按每层的实际类型检查；id 与 templateVersion 在索引和角色卡间核对，locale 在索引、角色卡与 configuration 间核对，contentRevision 在 discovery、索引和角色卡间核对。Fleet 本身没有 locale、templateVersion 或 contentRevision 字段；这里核对其 schema、单 Bot 的 slug ID 与实际空结构，不臆造字段。

- ENOENT：确认当前目录是仓库根、Node.js 为 22+，并确认 npm run check 和 npm run build:site 成功；不要从其他版本手工拼入缺失文件。
- 版本、语言或 URL 断言失败：停止使用这份混合快照，从同一 Git 快照重新生成整个 dist/site 后重试，不放宽断言。
- 字节长度或 SHA-256 失败：保留失败记录，重新生成或重新取得完整一致快照；不要修改角色卡摘要让错误文件通过。

## 分清两种摘要

| 字段 | 计算对象与格式 | 比较边界 |
| --- | --- | --- |
| registry integrity | 规范清单的 canonicalJson：键递归排序、无格式空白；SHA-256 后使用 sha256-\<base64\>。 | 由 engine/src/manifest.mjs 的 integrityOf 定义；不是下载文件的原始字节摘要。 |
| Agent configuration.sha256 / bytes | configuration.url 指向的具体 Fleet 导出字节；SHA-256 为 64 位十六进制，bytes 为字节数。 | 由 scripts/agent-surface.mjs 读取产物计算；只与同一 URL、语言和快照的字节比较。 |

CLI 经 scripts/market.mjs 复用引擎导出，默认时间取当前 Date.now()；站点构建使用 metadata.updatedAt 的固定时间。exportedAt 和 Bot createdAt 因此可能不同；中文规范投影和英文站点投影的人设等文本也不同。即使角色 ID 和模板版本相同，这些导出的字节与摘要仍可能不同，不能混用 registry integrity、CLI 文件摘要、中文下载或英文下载摘要。

## 参考来源

来源核对日期: 2026-10-25

- [Agent 读取与校验](https://github.com/Harzva/awesome-bot/blob/main/docs/agent-integration.md)
- [格式契约](https://github.com/Harzva/awesome-bot/blob/main/INTEGRATION.md)
