让 Agent 按需读取角色
读取小索引和单角色资料,核对版本、语言和 SHA-256。
从小索引开始
团队读者重点理解按需发现角色、提供上下文和确认权限即可;不必先克隆仓库或安装 Node,也不要求每位协作者手动计算 SHA-256。继续阅读“读取与信任的边界”;“本地快照校验与失败练习”可留给开发者。
- 读取 llms.txt,选择 zh 或 en 索引。
- 按当前任务选择一个 ID,再读取其 JSON 或 Markdown。
- 说明输入、输出、前提和需求,在 Agent 自身规则下处理任务。
四种机器文件
| 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 导出和宿主确认中的实际设置为准。
{
"schema": "awesome.bot.agent/1",
"id": "awesome/status-report-writer",
"templateVersion": "1.0.0",
"locale": "en"
}读取并核对 SHA-256
以下 Node.js 22+ 示例只读取和校验,不导入或执行配置。摘要不一致时停止,重新获取一致版本或带校验文件的发布快照。
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 匹配说明与元数据一致,不证明发布者身份;当前目录未签名。
理解上述边界后,团队读者可继续排错;实际接入、下载或升级仍须执行对应契约要求的完整性与信任核验,由负责接入的人或工具完成。
本地快照校验与失败练习
准备 Node.js 22+,先从 GitHub clone 本仓库并进入仓库根目录,运行下方准备命令。clone、依赖安装和构建可能需要网络;只有后面的校验阶段完全读取本地文件。它不调用模型,也不表示模型可以离线运行。 命令使用macOS/Linux或Windows WSL的shell;其他终端可将JS标记之间的代码另存为.mjs,再用Node运行。
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。
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 文件摘要、中文下载或英文下载摘要。
参考来源
来源核对日期: