cybertron-agents 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cybertron_agents-0.1.0/PKG-INFO +90 -0
- cybertron_agents-0.1.0/README.md +79 -0
- cybertron_agents-0.1.0/cybertron/__init__.py +11 -0
- cybertron_agents-0.1.0/cybertron/compaction.py +107 -0
- cybertron_agents-0.1.0/cybertron/deployment.py +141 -0
- cybertron_agents-0.1.0/cybertron/dispatch.py +92 -0
- cybertron_agents-0.1.0/cybertron/execution.py +58 -0
- cybertron_agents-0.1.0/cybertron/exporters.py +150 -0
- cybertron_agents-0.1.0/cybertron/feishu.py +264 -0
- cybertron_agents-0.1.0/cybertron/installer.py +310 -0
- cybertron_agents-0.1.0/cybertron/isolated_runner.py +109 -0
- cybertron_agents-0.1.0/cybertron/ledger.py +219 -0
- cybertron_agents-0.1.0/cybertron/opencode_executor.py +140 -0
- cybertron_agents-0.1.0/cybertron/opencode_l2_probe.py +42 -0
- cybertron_agents-0.1.0/cybertron/opencode_readonly.py +47 -0
- cybertron_agents-0.1.0/cybertron/opencode_run.py +27 -0
- cybertron_agents-0.1.0/cybertron/opencode_v2.py +98 -0
- cybertron_agents-0.1.0/cybertron/policy.py +112 -0
- cybertron_agents-0.1.0/cybertron/registry.py +518 -0
- cybertron_agents-0.1.0/cybertron/schema.py +55 -0
- cybertron_agents-0.1.0/cybertron/selfcheck.py +47 -0
- cybertron_agents-0.1.0/cybertron/simulation.py +64 -0
- cybertron_agents-0.1.0/cybertron/stage5_verify.py +67 -0
- cybertron_agents-0.1.0/cybertron/stage6_mcp_verify.py +111 -0
- cybertron_agents-0.1.0/cybertron/stage6_verify.py +67 -0
- cybertron_agents-0.1.0/cybertron/stage7_probe.py +68 -0
- cybertron_agents-0.1.0/cybertron_agents.egg-info/PKG-INFO +90 -0
- cybertron_agents-0.1.0/cybertron_agents.egg-info/SOURCES.txt +40 -0
- cybertron_agents-0.1.0/cybertron_agents.egg-info/dependency_links.txt +1 -0
- cybertron_agents-0.1.0/cybertron_agents.egg-info/entry_points.txt +2 -0
- cybertron_agents-0.1.0/cybertron_agents.egg-info/top_level.txt +2 -0
- cybertron_agents-0.1.0/pyproject.toml +23 -0
- cybertron_agents-0.1.0/setup.cfg +4 -0
- cybertron_agents-0.1.0/tests/test_deployment_stage7.py +84 -0
- cybertron_agents-0.1.0/tests/test_dispatch.py +117 -0
- cybertron_agents-0.1.0/tests/test_engine.py +148 -0
- cybertron_agents-0.1.0/tests/test_feishu_stage6.py +267 -0
- cybertron_agents-0.1.0/tests/test_installer.py +58 -0
- cybertron_agents-0.1.0/tests/test_isolated_runner.py +27 -0
- cybertron_agents-0.1.0/tests/test_opencode_executor.py +143 -0
- cybertron_agents-0.1.0/tests/test_opencode_v2.py +53 -0
- cybertron_agents-0.1.0/tests/test_stage7_probe.py +31 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cybertron-agents
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Install the 13 Cybertron Primes agents and the /cybertron command into OpenCode
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/zhiwenC125/Cybertron-agents
|
|
7
|
+
Project-URL: Repository, https://github.com/zhiwenC125/Cybertron-agents.git
|
|
8
|
+
Keywords: opencode,agents,cybertron,multi-agent
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Cybertron Thirteen Primes
|
|
13
|
+
|
|
14
|
+
当前开发阶段和每阶段验收条件见 [开发计划](DEVELOPMENT_PLAN.md)。
|
|
15
|
+
|
|
16
|
+
当前项目协作与权限基线见 [AGENTS.md](AGENTS.md)。普通角色的测试、构建和静态检查 Shell 权限暂时关闭;阶段 5 的本地隔离运行器尚未接入 OpenCode 角色工具调用。
|
|
17
|
+
|
|
18
|
+
**当前状态(2026-11-14)**:七阶段的最小闭环基线已达到。阶段 7 的 STM32 最小 App 已经人工批准真机烧录、读回、观察 SRAM 心跳并恢复原固件;完整 OTA App、ESP32-S3、九步自动接力和宿主审批接口仍在开发。证据见[阶段 7 文档](docs/stages/07-deployment.md)。在其他电脑使用或规划一条命令安装,请先看[安装与发布方案](docs/packaging-and-installation.md)。
|
|
19
|
+
|
|
20
|
+
各阶段的代码摘录、验收记录与后续实现建议见 [阶段文档目录](docs/stages/01-foundation.md):[1](docs/stages/01-foundation.md) · [2](docs/stages/02-local-execution.md) · [3](docs/stages/03-opencode-config.md) · [4](docs/stages/04-opencode-executor.md) · [5](docs/stages/05-quality-gates.md) · [6](docs/stages/06-feishu.md) · [7](docs/stages/07-deployment.md)。参考资料新增 [Datawhale Hello Agents](https://datawhalechina.github.io/hello-agents/#/)。
|
|
21
|
+
|
|
22
|
+
本仓库是 [v5 架构白皮书](design/cybertron_13_primes_architecture.md)的 Python 原型。当前实现包含 13 个角色的静态定义、权限规则、9 步任务账本、飞书 Wiki OpenAPI 网关,以及 OpenCode V2 / DeepSeek Harness 配置导出。已有一个只读角色的 OpenCode V2 命令行适配器;完整多智能体流程仍在开发中。角色模型 ID 以 `cybertron/registry.py` 为准;白皮书中的旧 DeepSeek 模型名保留为当时的设计记录。
|
|
23
|
+
|
|
24
|
+
## 运行
|
|
25
|
+
|
|
26
|
+
需要 Python 3.9 或更新版本,无第三方 Python 依赖。
|
|
27
|
+
|
|
28
|
+
项目根目录已包含可供 OpenCode V2 加载的 `opencode.jsonc` 与 `.opencode/agents/`。本机 `opencode2` v2.3.0 的 `debug config` 与 `debug agents` 已确认加载 13 个 Cybertron 角色;首次在新电脑使用时请自行连接 DeepSeek,并用 `opencode2 models` 核对 `deepseek/deepseek-flash` 与 `deepseek/deepseek-v4-pro`。以下单元测试、导出和配置检查均不会调用模型;交互提示和第 1 步真实执行会调用模型。
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
python3 -m unittest discover -s tests -v
|
|
32
|
+
python3 cybertron_primes_engine.py
|
|
33
|
+
python3 -m cybertron.simulation "开发示例功能"
|
|
34
|
+
python3 -m cybertron.opencode_v2 --output opencode_v2_smoke
|
|
35
|
+
python3 -m cybertron.opencode_run "请澄清 STM32F4 电机控制任务还缺少哪些关键参数"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
第二条命令会在当前目录生成 `cybertron_workspace_dist/`,其中有 13 份 OpenCode 智能体 Markdown、`opencode.jsonc` 和一份 DeepSeek Harness 预设 JSON。原脚本路径继续作为兼容入口;实现已拆分到 `cybertron/`。
|
|
39
|
+
|
|
40
|
+
第三条命令使用统一的 `AgentTask` / `AgentResult` 执行接口运行本地模拟接力。第 3 步依次派发锐天骁、维克多、昆图斯;第 5 步派发微米天、谎言天尊;第 9 步派发钛师傅、第十三天尊。默认在第 8 步硬件审批前停下,不会调用模型、飞书或硬件工具。若只想检验九步状态转换,可显式运行 `python3 -m cybertron.simulation --simulate-hardware-approval`;该选项仅改变模拟结果,不代表真实人工授权。
|
|
41
|
+
|
|
42
|
+
第四条命令生成供 OpenCode V2 配置加载测试的独立目录,默认不启用飞书 MCP。进入 `opencode_v2_smoke/` 后运行 `opencode2 debug config` 检查宿主加载;此步骤不会调用模型。飞书 MCP 可在有凭证时通过导出命令的 `--include-feishu` 显式启用。
|
|
43
|
+
|
|
44
|
+
第五条命令在项目根目录运行,会真实调用 OpenCode V2 的 `thirteenth` 角色。若终端仍在 `opencode_v2_smoke/`,重新生成导出包后可直接运行 `python3 run_first_agent.py "请澄清 STM32F4 电机控制任务还缺少哪些关键参数"`。此测试需要该角色配置的 DeepSeek 模型可用。它只执行账本第 1 步,不继续派发其他角色;模型回复不符合结构化格式、超时或命令失败时,账本会阻塞。
|
|
45
|
+
|
|
46
|
+
阶段 5 的只读接力探针在项目根目录运行:`python3 -m cybertron.opencode_readonly "查看本项目源码与当前 Git 基线"`。项目根目录已生成 `opencode.jsonc` 和 `.opencode/agents/`,让角色能读取真正的项目源码。探针用模拟前置条件跳过步骤 1、2,只真实调用 `onyx-prime` 和 `vector-prime`,不会执行后续写入角色。
|
|
47
|
+
|
|
48
|
+
阶段 5 保留 `cybertron.opencode_l2_probe` 作为真实宿主审查探针;前置步骤和 L1 是模拟结果。用户已决定不向外部模型发送项目文件,因此它不属于当前离线验收命令。
|
|
49
|
+
|
|
50
|
+
阶段 5 的离线终端验收运行 `python3 -m unittest discover -s tests -q`、`python3 -m cybertron.stage5_verify` 和 `opencode2 debug config`。第二条命令通过 macOS `sandbox-exec` 核查项目写入、设备读取及网络连接均被拒绝,再将实际单元测试报告作为 `test` 证据写入账本;不会调用模型或飞书。
|
|
51
|
+
|
|
52
|
+
阶段 6 的离线测试包含在单元测试中。真实飞书测试使用自建应用身份:在本机设置 `FEISHU_APP_ID`、`FEISHU_APP_SECRET` 和测试空间的 `FEISHU_WIKI_SPACE_ID`,依次运行 `python3 -m cybertron.stage6_verify --check-env`、`--check-auth` 和 `--live`;最后一项会在该空间创建并保留一篇测试文档、追加正文并回读验证。完整步骤见[阶段 6 文档](docs/stages/06-feishu.md)。不要把 App Secret 或租户令牌提交到仓库或贴进聊天。
|
|
53
|
+
|
|
54
|
+
## 安装到 OpenCode
|
|
55
|
+
|
|
56
|
+
三条安装路径共享同一份 payload,默认 dry-run,`--apply` 才写入:
|
|
57
|
+
|
|
58
|
+
- npm:`npx cybertron-agents@0.1.0 init --dry-run`(发布后)
|
|
59
|
+
- shell:`sh install.sh --dry-run --project .`
|
|
60
|
+
- pipx:`pipx install .` 后 `cybertron init --dry-run`
|
|
61
|
+
|
|
62
|
+
详见[安装与发布方案](docs/packaging-and-installation.md)。
|
|
63
|
+
|
|
64
|
+
## 模块
|
|
65
|
+
|
|
66
|
+
| 模块 | 职责 |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `schema.py` | 消息和工具调用数据结构 |
|
|
69
|
+
| `compaction.py` | 上下文首尾压缩 |
|
|
70
|
+
| `policy.py` | 本地权限与循环熔断检查 |
|
|
71
|
+
| `feishu.py` | 自建应用凭证读取、租户令牌缓存、Wiki 节点及 Docx 正文读写网关 |
|
|
72
|
+
| `registry.py` | 13 个角色配置 |
|
|
73
|
+
| `ledger.py` | SDLC 计划和顺序状态转换 |
|
|
74
|
+
| `execution.py` | 宿主无关的任务、结果和执行器协议 |
|
|
75
|
+
| `dispatch.py` | 派单、结果校验和账本推进 |
|
|
76
|
+
| `simulation.py` | 不产生外部副作用的本地模拟执行器 |
|
|
77
|
+
| `isolated_runner.py` | macOS 沙箱内运行单元测试并生成 QA 证据 |
|
|
78
|
+
| `deployment.py` | 第七阶段固件、设备、擦写与回滚契约及无硬件仿真 |
|
|
79
|
+
| `exporters.py` | 双宿主静态配置导出 |
|
|
80
|
+
| `installer.py` | npm / shell / pipx 三条安装路径的 Python 入口 |
|
|
81
|
+
| `selfcheck.py` | 命令行自检 |
|
|
82
|
+
|
|
83
|
+
## 当前边界与下一步
|
|
84
|
+
|
|
85
|
+
1. `ledger.py` 与 `dispatch.py` 已实现 L1/L2 拒绝后返回编码、重审及超限阻塞;本地隔离测试能生成 QA 回执。真实 L1/L2 宿主审查和人工烧录授权仍需后续阶段接入。
|
|
86
|
+
2. `policy.py` 是本地策略原型,不能代替宿主层权限和命令解析。生成配置后的真实权限仍需在目标宿主中验证。
|
|
87
|
+
3. DeepSeek Harness 目前只导出预设 JSON,白皮书中的 `.dsh/cordis-cybertron-plugin.mjs` 尚未实现。
|
|
88
|
+
4. 阶段 6 的 Wiki OpenAPI 读写已由开发者在专用测试空间验收;阶段 7 的 STM32 最小真机烧录及回滚已通过。完整 OTA App、ESP32-S3、外部 Flash 备份和隔离构建仍待开发。
|
|
89
|
+
|
|
90
|
+
缺少飞书 App ID 或 App Secret 时,网关默认报错。模拟租户令牌仅能通过显式传入 `mock_if_unset=True` 用于局部测试,不会进入真实 Wiki 请求。
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Cybertron Thirteen Primes
|
|
2
|
+
|
|
3
|
+
当前开发阶段和每阶段验收条件见 [开发计划](DEVELOPMENT_PLAN.md)。
|
|
4
|
+
|
|
5
|
+
当前项目协作与权限基线见 [AGENTS.md](AGENTS.md)。普通角色的测试、构建和静态检查 Shell 权限暂时关闭;阶段 5 的本地隔离运行器尚未接入 OpenCode 角色工具调用。
|
|
6
|
+
|
|
7
|
+
**当前状态(2026-11-14)**:七阶段的最小闭环基线已达到。阶段 7 的 STM32 最小 App 已经人工批准真机烧录、读回、观察 SRAM 心跳并恢复原固件;完整 OTA App、ESP32-S3、九步自动接力和宿主审批接口仍在开发。证据见[阶段 7 文档](docs/stages/07-deployment.md)。在其他电脑使用或规划一条命令安装,请先看[安装与发布方案](docs/packaging-and-installation.md)。
|
|
8
|
+
|
|
9
|
+
各阶段的代码摘录、验收记录与后续实现建议见 [阶段文档目录](docs/stages/01-foundation.md):[1](docs/stages/01-foundation.md) · [2](docs/stages/02-local-execution.md) · [3](docs/stages/03-opencode-config.md) · [4](docs/stages/04-opencode-executor.md) · [5](docs/stages/05-quality-gates.md) · [6](docs/stages/06-feishu.md) · [7](docs/stages/07-deployment.md)。参考资料新增 [Datawhale Hello Agents](https://datawhalechina.github.io/hello-agents/#/)。
|
|
10
|
+
|
|
11
|
+
本仓库是 [v5 架构白皮书](design/cybertron_13_primes_architecture.md)的 Python 原型。当前实现包含 13 个角色的静态定义、权限规则、9 步任务账本、飞书 Wiki OpenAPI 网关,以及 OpenCode V2 / DeepSeek Harness 配置导出。已有一个只读角色的 OpenCode V2 命令行适配器;完整多智能体流程仍在开发中。角色模型 ID 以 `cybertron/registry.py` 为准;白皮书中的旧 DeepSeek 模型名保留为当时的设计记录。
|
|
12
|
+
|
|
13
|
+
## 运行
|
|
14
|
+
|
|
15
|
+
需要 Python 3.9 或更新版本,无第三方 Python 依赖。
|
|
16
|
+
|
|
17
|
+
项目根目录已包含可供 OpenCode V2 加载的 `opencode.jsonc` 与 `.opencode/agents/`。本机 `opencode2` v2.3.0 的 `debug config` 与 `debug agents` 已确认加载 13 个 Cybertron 角色;首次在新电脑使用时请自行连接 DeepSeek,并用 `opencode2 models` 核对 `deepseek/deepseek-flash` 与 `deepseek/deepseek-v4-pro`。以下单元测试、导出和配置检查均不会调用模型;交互提示和第 1 步真实执行会调用模型。
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
python3 -m unittest discover -s tests -v
|
|
21
|
+
python3 cybertron_primes_engine.py
|
|
22
|
+
python3 -m cybertron.simulation "开发示例功能"
|
|
23
|
+
python3 -m cybertron.opencode_v2 --output opencode_v2_smoke
|
|
24
|
+
python3 -m cybertron.opencode_run "请澄清 STM32F4 电机控制任务还缺少哪些关键参数"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
第二条命令会在当前目录生成 `cybertron_workspace_dist/`,其中有 13 份 OpenCode 智能体 Markdown、`opencode.jsonc` 和一份 DeepSeek Harness 预设 JSON。原脚本路径继续作为兼容入口;实现已拆分到 `cybertron/`。
|
|
28
|
+
|
|
29
|
+
第三条命令使用统一的 `AgentTask` / `AgentResult` 执行接口运行本地模拟接力。第 3 步依次派发锐天骁、维克多、昆图斯;第 5 步派发微米天、谎言天尊;第 9 步派发钛师傅、第十三天尊。默认在第 8 步硬件审批前停下,不会调用模型、飞书或硬件工具。若只想检验九步状态转换,可显式运行 `python3 -m cybertron.simulation --simulate-hardware-approval`;该选项仅改变模拟结果,不代表真实人工授权。
|
|
30
|
+
|
|
31
|
+
第四条命令生成供 OpenCode V2 配置加载测试的独立目录,默认不启用飞书 MCP。进入 `opencode_v2_smoke/` 后运行 `opencode2 debug config` 检查宿主加载;此步骤不会调用模型。飞书 MCP 可在有凭证时通过导出命令的 `--include-feishu` 显式启用。
|
|
32
|
+
|
|
33
|
+
第五条命令在项目根目录运行,会真实调用 OpenCode V2 的 `thirteenth` 角色。若终端仍在 `opencode_v2_smoke/`,重新生成导出包后可直接运行 `python3 run_first_agent.py "请澄清 STM32F4 电机控制任务还缺少哪些关键参数"`。此测试需要该角色配置的 DeepSeek 模型可用。它只执行账本第 1 步,不继续派发其他角色;模型回复不符合结构化格式、超时或命令失败时,账本会阻塞。
|
|
34
|
+
|
|
35
|
+
阶段 5 的只读接力探针在项目根目录运行:`python3 -m cybertron.opencode_readonly "查看本项目源码与当前 Git 基线"`。项目根目录已生成 `opencode.jsonc` 和 `.opencode/agents/`,让角色能读取真正的项目源码。探针用模拟前置条件跳过步骤 1、2,只真实调用 `onyx-prime` 和 `vector-prime`,不会执行后续写入角色。
|
|
36
|
+
|
|
37
|
+
阶段 5 保留 `cybertron.opencode_l2_probe` 作为真实宿主审查探针;前置步骤和 L1 是模拟结果。用户已决定不向外部模型发送项目文件,因此它不属于当前离线验收命令。
|
|
38
|
+
|
|
39
|
+
阶段 5 的离线终端验收运行 `python3 -m unittest discover -s tests -q`、`python3 -m cybertron.stage5_verify` 和 `opencode2 debug config`。第二条命令通过 macOS `sandbox-exec` 核查项目写入、设备读取及网络连接均被拒绝,再将实际单元测试报告作为 `test` 证据写入账本;不会调用模型或飞书。
|
|
40
|
+
|
|
41
|
+
阶段 6 的离线测试包含在单元测试中。真实飞书测试使用自建应用身份:在本机设置 `FEISHU_APP_ID`、`FEISHU_APP_SECRET` 和测试空间的 `FEISHU_WIKI_SPACE_ID`,依次运行 `python3 -m cybertron.stage6_verify --check-env`、`--check-auth` 和 `--live`;最后一项会在该空间创建并保留一篇测试文档、追加正文并回读验证。完整步骤见[阶段 6 文档](docs/stages/06-feishu.md)。不要把 App Secret 或租户令牌提交到仓库或贴进聊天。
|
|
42
|
+
|
|
43
|
+
## 安装到 OpenCode
|
|
44
|
+
|
|
45
|
+
三条安装路径共享同一份 payload,默认 dry-run,`--apply` 才写入:
|
|
46
|
+
|
|
47
|
+
- npm:`npx cybertron-agents@0.1.0 init --dry-run`(发布后)
|
|
48
|
+
- shell:`sh install.sh --dry-run --project .`
|
|
49
|
+
- pipx:`pipx install .` 后 `cybertron init --dry-run`
|
|
50
|
+
|
|
51
|
+
详见[安装与发布方案](docs/packaging-and-installation.md)。
|
|
52
|
+
|
|
53
|
+
## 模块
|
|
54
|
+
|
|
55
|
+
| 模块 | 职责 |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `schema.py` | 消息和工具调用数据结构 |
|
|
58
|
+
| `compaction.py` | 上下文首尾压缩 |
|
|
59
|
+
| `policy.py` | 本地权限与循环熔断检查 |
|
|
60
|
+
| `feishu.py` | 自建应用凭证读取、租户令牌缓存、Wiki 节点及 Docx 正文读写网关 |
|
|
61
|
+
| `registry.py` | 13 个角色配置 |
|
|
62
|
+
| `ledger.py` | SDLC 计划和顺序状态转换 |
|
|
63
|
+
| `execution.py` | 宿主无关的任务、结果和执行器协议 |
|
|
64
|
+
| `dispatch.py` | 派单、结果校验和账本推进 |
|
|
65
|
+
| `simulation.py` | 不产生外部副作用的本地模拟执行器 |
|
|
66
|
+
| `isolated_runner.py` | macOS 沙箱内运行单元测试并生成 QA 证据 |
|
|
67
|
+
| `deployment.py` | 第七阶段固件、设备、擦写与回滚契约及无硬件仿真 |
|
|
68
|
+
| `exporters.py` | 双宿主静态配置导出 |
|
|
69
|
+
| `installer.py` | npm / shell / pipx 三条安装路径的 Python 入口 |
|
|
70
|
+
| `selfcheck.py` | 命令行自检 |
|
|
71
|
+
|
|
72
|
+
## 当前边界与下一步
|
|
73
|
+
|
|
74
|
+
1. `ledger.py` 与 `dispatch.py` 已实现 L1/L2 拒绝后返回编码、重审及超限阻塞;本地隔离测试能生成 QA 回执。真实 L1/L2 宿主审查和人工烧录授权仍需后续阶段接入。
|
|
75
|
+
2. `policy.py` 是本地策略原型,不能代替宿主层权限和命令解析。生成配置后的真实权限仍需在目标宿主中验证。
|
|
76
|
+
3. DeepSeek Harness 目前只导出预设 JSON,白皮书中的 `.dsh/cordis-cybertron-plugin.mjs` 尚未实现。
|
|
77
|
+
4. 阶段 6 的 Wiki OpenAPI 读写已由开发者在专用测试空间验收;阶段 7 的 STM32 最小真机烧录及回滚已通过。完整 OTA App、ESP32-S3、外部 Flash 备份和隔离构建仍待开发。
|
|
78
|
+
|
|
79
|
+
缺少飞书 App ID 或 App Secret 时,网关默认报错。模拟租户令牌仅能通过显式传入 `mock_if_unset=True` 用于局部测试,不会进入真实 Wiki 请求。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Cybertron multi-agent configuration and orchestration primitives."""
|
|
2
|
+
from .schema import Message, ToolCallRequest, function_to_json_schema
|
|
3
|
+
from .compaction import AlphaTrionHeadTailCompaction, CompactionStrategy
|
|
4
|
+
from .policy import MegatronusCircuitBreaker, PermissionEffect, PermissionRule
|
|
5
|
+
from .feishu import FeishuWikiGovernanceConfig, FeishuWikiKnowledgeClient
|
|
6
|
+
from .registry import AgentMode, PrimeAgentSpec, THIRTEEN_PRIMES_REGISTRY
|
|
7
|
+
from .ledger import CybertronTaskLedger, LedgerStep, PrimaPlanOrchestrator
|
|
8
|
+
from .exporters import compile_deepseek_harness_assets, compile_opencode_v2_assets
|
|
9
|
+
from .selfcheck import run_self_verification
|
|
10
|
+
from .execution import AgentExecutor, AgentResult, AgentTask, QualityEvidence, QualityKind, ResultStatus
|
|
11
|
+
from .dispatch import PrimaDispatcher
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""在模型上下文超预算时保留早期约束与近期消息,并保持工具调用和结果成组。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import json
|
|
5
|
+
from abc import abstractmethod
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import List, Protocol, Set, Tuple, runtime_checkable
|
|
8
|
+
from .schema import Message
|
|
9
|
+
|
|
10
|
+
# MODULE 2: Alpha Trion Context Compaction Engine (Ref: picoagents/compaction.py)
|
|
11
|
+
# ============================================================================
|
|
12
|
+
|
|
13
|
+
@runtime_checkable
|
|
14
|
+
class CompactionStrategy(Protocol):
|
|
15
|
+
"""Protocol for context compaction executed INSIDE the agent tool loop."""
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def compact(self, messages: List[Message]) -> List[Message]:
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class AlphaTrionHeadTailCompaction:
|
|
24
|
+
"""
|
|
25
|
+
Token-aware Head+Tail Compaction (Alpha Trion's Quill).
|
|
26
|
+
Preserves:
|
|
27
|
+
- Head (25% budget): System prompt, initial user requirement, and Feishu Wiki architectural/hardware constraints.
|
|
28
|
+
- Tail (75% budget): Most recent tool calls and results (working memory).
|
|
29
|
+
Crucially preserves 'atomic groups': an assistant message containing `tool_calls`
|
|
30
|
+
is NEVER separated from its corresponding `ToolMessage` results.
|
|
31
|
+
"""
|
|
32
|
+
token_budget: int = 64_000
|
|
33
|
+
head_ratio: float = 0.25
|
|
34
|
+
compaction_count: int = field(default=0, repr=False)
|
|
35
|
+
total_tokens_saved: int = field(default=0, repr=False)
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def estimate_tokens(messages: List[Message]) -> int:
|
|
39
|
+
total = 0
|
|
40
|
+
for msg in messages:
|
|
41
|
+
total += 4 + len(msg.content or "") // 4
|
|
42
|
+
for tc in msg.tool_calls:
|
|
43
|
+
total += 8 + len(tc.tool_name) + len(json.dumps(tc.parameters, ensure_ascii=False)) // 4
|
|
44
|
+
return total
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def find_atomic_groups(messages: List[Message]) -> List[Tuple[int, ...]]:
|
|
48
|
+
groups: List[Tuple[int, ...]] = []
|
|
49
|
+
i = 0
|
|
50
|
+
while i < len(messages):
|
|
51
|
+
msg = messages[i]
|
|
52
|
+
if msg.role == "assistant" and msg.tool_calls:
|
|
53
|
+
pending_ids: Set[str] = {tc.call_id for tc in msg.tool_calls}
|
|
54
|
+
group_indices = [i]
|
|
55
|
+
j = i + 1
|
|
56
|
+
while j < len(messages) and pending_ids:
|
|
57
|
+
if messages[j].role == "tool" and messages[j].tool_call_id in pending_ids:
|
|
58
|
+
group_indices.append(j)
|
|
59
|
+
pending_ids.remove(messages[j].tool_call_id) # type: ignore[arg-type]
|
|
60
|
+
j += 1
|
|
61
|
+
groups.append(tuple(group_indices))
|
|
62
|
+
i = max(group_indices) + 1
|
|
63
|
+
else:
|
|
64
|
+
groups.append((i,))
|
|
65
|
+
i += 1
|
|
66
|
+
return groups
|
|
67
|
+
|
|
68
|
+
def compact(self, messages: List[Message]) -> List[Message]:
|
|
69
|
+
if not messages:
|
|
70
|
+
return messages
|
|
71
|
+
current_tokens = self.estimate_tokens(messages)
|
|
72
|
+
if current_tokens <= self.token_budget:
|
|
73
|
+
return messages
|
|
74
|
+
|
|
75
|
+
self.compaction_count += 1
|
|
76
|
+
groups = self.find_atomic_groups(messages)
|
|
77
|
+
head_budget = int(self.token_budget * self.head_ratio)
|
|
78
|
+
tail_budget = self.token_budget - head_budget
|
|
79
|
+
|
|
80
|
+
head_groups: List[Tuple[int, ...]] = []
|
|
81
|
+
head_tokens = 0
|
|
82
|
+
for grp in groups:
|
|
83
|
+
grp_tokens = self.estimate_tokens([messages[idx] for idx in grp])
|
|
84
|
+
if head_tokens + grp_tokens <= head_budget:
|
|
85
|
+
head_groups.append(grp)
|
|
86
|
+
head_tokens += grp_tokens
|
|
87
|
+
else:
|
|
88
|
+
break
|
|
89
|
+
|
|
90
|
+
remaining = groups[len(head_groups):]
|
|
91
|
+
tail_groups: List[Tuple[int, ...]] = []
|
|
92
|
+
tail_tokens = 0
|
|
93
|
+
for grp in reversed(remaining):
|
|
94
|
+
grp_tokens = self.estimate_tokens([messages[idx] for idx in grp])
|
|
95
|
+
if tail_tokens + grp_tokens <= tail_budget:
|
|
96
|
+
tail_groups.insert(0, grp)
|
|
97
|
+
tail_tokens += grp_tokens
|
|
98
|
+
else:
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
kept_indices: Set[int] = set()
|
|
102
|
+
for grp in head_groups + tail_groups:
|
|
103
|
+
kept_indices.update(grp)
|
|
104
|
+
|
|
105
|
+
compacted = [messages[idx] for idx in sorted(kept_indices)]
|
|
106
|
+
self.total_tokens_saved += current_tokens - self.estimate_tokens(compacted)
|
|
107
|
+
return compacted
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Offline stage 7 deployment contracts; no device or shell operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from dataclasses import asdict, dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_FIRMWARE_SUFFIXES = {".bin", ".elf", ".hex", ".uf2"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _required(value: str, label: str) -> str:
|
|
15
|
+
if not isinstance(value, str) or not value.strip():
|
|
16
|
+
raise ValueError(f"{label} is required")
|
|
17
|
+
return value.strip()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class FirmwareArtifact:
|
|
22
|
+
path: str
|
|
23
|
+
sha256: str
|
|
24
|
+
size_bytes: int
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def inspect(cls, path: Path) -> FirmwareArtifact:
|
|
28
|
+
if path.is_symlink() or path.suffix.lower() not in _FIRMWARE_SUFFIXES:
|
|
29
|
+
raise ValueError("Firmware must be a regular .bin/.elf/.hex/.uf2 file")
|
|
30
|
+
if not path.is_file():
|
|
31
|
+
raise FileNotFoundError("Firmware file is missing")
|
|
32
|
+
digest = hashlib.sha256()
|
|
33
|
+
size = 0
|
|
34
|
+
with path.open("rb") as stream:
|
|
35
|
+
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
|
36
|
+
digest.update(chunk)
|
|
37
|
+
size += len(chunk)
|
|
38
|
+
if size == 0:
|
|
39
|
+
raise ValueError("Firmware file is empty")
|
|
40
|
+
return cls(str(path.resolve()), digest.hexdigest(), size)
|
|
41
|
+
|
|
42
|
+
def verify(self) -> None:
|
|
43
|
+
current = type(self).inspect(Path(self.path))
|
|
44
|
+
if current.sha256 != self.sha256 or current.size_bytes != self.size_bytes:
|
|
45
|
+
raise ValueError("Firmware changed after inspection")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class DeviceTarget:
|
|
50
|
+
device_id: str
|
|
51
|
+
board: str
|
|
52
|
+
connection: str
|
|
53
|
+
flash_start: int
|
|
54
|
+
flash_end: int # exclusive
|
|
55
|
+
|
|
56
|
+
def validate(self) -> None:
|
|
57
|
+
for label in ("device_id", "board", "connection"):
|
|
58
|
+
_required(getattr(self, label), label)
|
|
59
|
+
if not (isinstance(self.flash_start, int) and isinstance(self.flash_end, int)
|
|
60
|
+
and 0 <= self.flash_start < self.flash_end):
|
|
61
|
+
raise ValueError("Target flash range must be a nonempty address interval")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class FlashPlan:
|
|
66
|
+
target: DeviceTarget
|
|
67
|
+
firmware: FirmwareArtifact
|
|
68
|
+
rollback: FirmwareArtifact
|
|
69
|
+
erase_start: int
|
|
70
|
+
erase_end: int # exclusive
|
|
71
|
+
rollback_procedure: str
|
|
72
|
+
heartbeat_marker: str
|
|
73
|
+
plan_digest: str
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def prepare(cls, target: DeviceTarget, firmware: FirmwareArtifact,
|
|
77
|
+
rollback: FirmwareArtifact, *, erase_start: int, erase_end: int,
|
|
78
|
+
rollback_procedure: str, heartbeat_marker: str) -> FlashPlan:
|
|
79
|
+
target.validate()
|
|
80
|
+
firmware.verify()
|
|
81
|
+
rollback.verify()
|
|
82
|
+
if not (isinstance(erase_start, int) and isinstance(erase_end, int)
|
|
83
|
+
and target.flash_start <= erase_start < erase_end <= target.flash_end):
|
|
84
|
+
raise ValueError("Erase range is outside the target flash region")
|
|
85
|
+
if Path(firmware.path).suffix.lower() == ".bin" and firmware.size_bytes > erase_end - erase_start:
|
|
86
|
+
raise ValueError("Raw firmware does not fit the erase range")
|
|
87
|
+
if Path(rollback.path).suffix.lower() == ".bin" and rollback.size_bytes > target.flash_end - target.flash_start:
|
|
88
|
+
raise ValueError("Rollback firmware does not fit target flash")
|
|
89
|
+
procedure = _required(rollback_procedure, "rollback_procedure")
|
|
90
|
+
marker = _required(heartbeat_marker, "heartbeat_marker")
|
|
91
|
+
if firmware.path == rollback.path or firmware.sha256 == rollback.sha256:
|
|
92
|
+
raise ValueError("Rollback firmware must be a distinct verified image")
|
|
93
|
+
values = {
|
|
94
|
+
"target": asdict(target), "firmware": asdict(firmware),
|
|
95
|
+
"rollback": asdict(rollback), "erase_start": erase_start,
|
|
96
|
+
"erase_end": erase_end, "rollback_procedure": procedure,
|
|
97
|
+
"heartbeat_marker": marker,
|
|
98
|
+
}
|
|
99
|
+
digest = hashlib.sha256(json.dumps(values, sort_keys=True).encode()).hexdigest()
|
|
100
|
+
return cls(target, firmware, rollback, erase_start, erase_end,
|
|
101
|
+
procedure, marker, digest)
|
|
102
|
+
|
|
103
|
+
def verify(self) -> None:
|
|
104
|
+
rebuilt = type(self).prepare(
|
|
105
|
+
self.target, self.firmware, self.rollback,
|
|
106
|
+
erase_start=self.erase_start, erase_end=self.erase_end,
|
|
107
|
+
rollback_procedure=self.rollback_procedure,
|
|
108
|
+
heartbeat_marker=self.heartbeat_marker,
|
|
109
|
+
)
|
|
110
|
+
if rebuilt.plan_digest != self.plan_digest:
|
|
111
|
+
raise ValueError("Flash plan changed after preparation")
|
|
112
|
+
|
|
113
|
+
def assessment(self) -> dict:
|
|
114
|
+
"""Reviewable blast-radius data; contains no command or approval claim."""
|
|
115
|
+
self.verify()
|
|
116
|
+
return {
|
|
117
|
+
"plan_digest": self.plan_digest,
|
|
118
|
+
"target": asdict(self.target),
|
|
119
|
+
"firmware": asdict(self.firmware),
|
|
120
|
+
"erase_range": {"start": self.erase_start, "end_exclusive": self.erase_end},
|
|
121
|
+
"rollback": asdict(self.rollback),
|
|
122
|
+
"rollback_procedure": self.rollback_procedure,
|
|
123
|
+
"heartbeat_marker": self.heartbeat_marker,
|
|
124
|
+
"requires_human_approval": True,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dataclass(frozen=True)
|
|
129
|
+
class SimulationReceipt:
|
|
130
|
+
plan_digest: str
|
|
131
|
+
device_id: str
|
|
132
|
+
heartbeat_observed: bool
|
|
133
|
+
simulated: bool = True
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def simulate_flash(plan: FlashPlan, boot_log: str) -> SimulationReceipt:
|
|
137
|
+
"""Check a supplied fake boot log without opening ports or writing hardware."""
|
|
138
|
+
plan.verify()
|
|
139
|
+
if not isinstance(boot_log, str) or plan.heartbeat_marker not in boot_log:
|
|
140
|
+
raise ValueError("Simulated boot log does not contain the required heartbeat")
|
|
141
|
+
return SimulationReceipt(plan.plan_digest, plan.target.device_id, True)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Move the ledger one step after validating an executor result."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .execution import AgentExecutor, AgentResult, AgentTask, QualityKind, ResultStatus
|
|
6
|
+
from .ledger import CybertronTaskLedger
|
|
7
|
+
from .registry import THIRTEEN_PRIMES_REGISTRY
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PrimaDispatcher:
|
|
11
|
+
def __init__(self, executor: AgentExecutor) -> None:
|
|
12
|
+
self.executor = executor
|
|
13
|
+
self.agent_ids = {agent.agent_id for agent in THIRTEEN_PRIMES_REGISTRY}
|
|
14
|
+
|
|
15
|
+
def run_next(self, ledger: CybertronTaskLedger) -> AgentResult | None:
|
|
16
|
+
step = ledger.next_step()
|
|
17
|
+
if step is None:
|
|
18
|
+
return None
|
|
19
|
+
agent_id = step.next_agent()
|
|
20
|
+
if agent_id is None:
|
|
21
|
+
raise ValueError(f"Step {step.step_id} has no remaining agent")
|
|
22
|
+
if agent_id not in self.agent_ids:
|
|
23
|
+
raise ValueError(f"Unknown agent: {agent_id}")
|
|
24
|
+
if ledger.turn_count >= ledger.max_turns:
|
|
25
|
+
raise RuntimeError("Ledger turn limit reached")
|
|
26
|
+
if step.status == "blocked":
|
|
27
|
+
raise ValueError(f"Step {step.step_id} is blocked; resume it first")
|
|
28
|
+
if step.status == "pending":
|
|
29
|
+
ledger.start_step(step.step_id)
|
|
30
|
+
|
|
31
|
+
task = AgentTask(
|
|
32
|
+
step_id=step.step_id,
|
|
33
|
+
agent_id=agent_id,
|
|
34
|
+
user_request=ledger.user_request,
|
|
35
|
+
objective=step.participant_objectives.get(agent_id, step.objective),
|
|
36
|
+
input_artifact=step.input_artifact,
|
|
37
|
+
expected_output=step.expected_output,
|
|
38
|
+
feedback=step.feedback,
|
|
39
|
+
prior_evidence=tuple(
|
|
40
|
+
item for completed in ledger.plan if completed.status == "completed"
|
|
41
|
+
for item in completed.evidence
|
|
42
|
+
) + tuple(step.evidence),
|
|
43
|
+
)
|
|
44
|
+
ledger.turn_count += 1
|
|
45
|
+
try:
|
|
46
|
+
result = self.executor.execute(task)
|
|
47
|
+
except Exception as exc:
|
|
48
|
+
ledger.block_step(step.step_id, f"Executor error: {exc}")
|
|
49
|
+
raise
|
|
50
|
+
|
|
51
|
+
if result.step_id != task.step_id or result.agent_id != task.agent_id:
|
|
52
|
+
ledger.block_step(step.step_id, "Executor returned a result for a different task")
|
|
53
|
+
raise ValueError("Executor returned a result for a different task")
|
|
54
|
+
if not result.summary.strip():
|
|
55
|
+
ledger.block_step(step.step_id, "Executor returned an empty summary")
|
|
56
|
+
raise ValueError("Executor returned an empty summary")
|
|
57
|
+
if result.status == ResultStatus.COMPLETED:
|
|
58
|
+
if not result.evidence or not all(item.strip() for item in result.evidence):
|
|
59
|
+
ledger.block_step(step.step_id, "Completion has no evidence")
|
|
60
|
+
raise ValueError("Completion requires nonempty evidence")
|
|
61
|
+
gate_kind = {
|
|
62
|
+
"micronus-prime": QualityKind.REVIEW_L1,
|
|
63
|
+
"liege-maximo": QualityKind.REVIEW_L2,
|
|
64
|
+
"alchemist": QualityKind.TEST,
|
|
65
|
+
}.get(agent_id)
|
|
66
|
+
if gate_kind is not None and not any(
|
|
67
|
+
item.kind == gate_kind and item.passed and item.source.strip()
|
|
68
|
+
for item in result.quality_evidence
|
|
69
|
+
):
|
|
70
|
+
ledger.block_step(step.step_id, f"Missing passing {gate_kind.value} evidence")
|
|
71
|
+
raise ValueError(f"Missing passing {gate_kind.value} evidence")
|
|
72
|
+
ledger.record_agent_completion(step.step_id, agent_id, result.summary,
|
|
73
|
+
list(result.evidence), list(result.artifacts),
|
|
74
|
+
list(result.quality_evidence))
|
|
75
|
+
elif result.status in (ResultStatus.BLOCKED, ResultStatus.NEEDS_APPROVAL):
|
|
76
|
+
ledger.block_step(step.step_id, result.summary)
|
|
77
|
+
elif result.status == ResultStatus.REJECTED:
|
|
78
|
+
ledger.reject_review(agent_id, result.summary)
|
|
79
|
+
else:
|
|
80
|
+
ledger.block_step(step.step_id, "Unknown executor status")
|
|
81
|
+
raise ValueError("Unknown executor status")
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
def run_until_blocked(self, ledger: CybertronTaskLedger) -> list[AgentResult]:
|
|
85
|
+
results: list[AgentResult] = []
|
|
86
|
+
while ledger.next_step() is not None:
|
|
87
|
+
result = self.run_next(ledger)
|
|
88
|
+
assert result is not None
|
|
89
|
+
results.append(result)
|
|
90
|
+
if result.status != ResultStatus.COMPLETED:
|
|
91
|
+
break
|
|
92
|
+
return results
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Host-neutral task and result contracts for one agent dispatch."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Protocol, Tuple
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ResultStatus(str, Enum):
|
|
11
|
+
COMPLETED = "completed"
|
|
12
|
+
BLOCKED = "blocked"
|
|
13
|
+
NEEDS_APPROVAL = "needs_approval"
|
|
14
|
+
REJECTED = "rejected"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class QualityKind(str, Enum):
|
|
18
|
+
REVIEW_L1 = "review_l1"
|
|
19
|
+
REVIEW_L2 = "review_l2"
|
|
20
|
+
TEST = "test"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class QualityEvidence:
|
|
25
|
+
"""A typed gate result; source identifies the report or run that produced it."""
|
|
26
|
+
kind: QualityKind
|
|
27
|
+
passed: bool
|
|
28
|
+
source: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class AgentTask:
|
|
33
|
+
step_id: int
|
|
34
|
+
agent_id: str
|
|
35
|
+
user_request: str
|
|
36
|
+
objective: str
|
|
37
|
+
input_artifact: str
|
|
38
|
+
expected_output: str
|
|
39
|
+
prior_evidence: Tuple[str, ...] = ()
|
|
40
|
+
feedback: str = ""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class AgentResult:
|
|
45
|
+
step_id: int
|
|
46
|
+
agent_id: str
|
|
47
|
+
status: ResultStatus
|
|
48
|
+
summary: str
|
|
49
|
+
evidence: Tuple[str, ...] = ()
|
|
50
|
+
artifacts: Tuple[str, ...] = ()
|
|
51
|
+
quality_evidence: Tuple[QualityEvidence, ...] = ()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AgentExecutor(Protocol):
|
|
55
|
+
"""An adapter for a model host or deterministic local simulation."""
|
|
56
|
+
|
|
57
|
+
def execute(self, task: AgentTask) -> AgentResult:
|
|
58
|
+
...
|