track: 新增 OpenWork / Orca / Maka 三项目,样本达到 4 个
样本 - openwork (different-ai/openwork) 23.2k★ 能力分发层,DCO + evals + worlds - orca (stablyai/orca) 58.8k★ 并行编排外壳,5000 issue 实为团队自开任务板 - maka (apache/maka) 4.4k★ 执行记录层,Apache 孵化但无正式 release 档案 - 三份五维档案填完,risk.claim_vs_reality 均已核实到证据 - ipollowork 象限坐标从占位 9/9/9/9 校准为 8/7/7/7(与新样本同一标尺) 文章 - 新增横评文《四个 Agent 工作台的路线分野:把「多引擎中立」拆开看》(emlog id=99) - 总纲文回填四项目矩阵 + 双视图象限图 (emlog id=98) 镜像 - gitinbox/maka 已推送(public,source-only 快照 12 MB) - openwork(291MB) / orca(671MB) 受 50KB/s 带宽约束,档案标 mode: pending 规则 - license-tiers.yaml 新增「目录切分许可」降级流程(OpenWork 判例: 排除 ee/ 后为纯 MIT → public,完整镜像 → private) - registry.yaml openwork 可见性改 public + mirror_excludes: ["ee/"] 脚本修复 - _common.py 新增 upsert_block:手工整块覆盖 ecosystem 删掉 metrics 时能自愈 - mirror_sync.sh:SOURCE_ONLY 未初始化(set -u 下偶发炸); 浅仓推送被服务端拒绝 → 自动 --unshallow,失败降级快照; 量体积前先 repack(此前新 commit 全是 loose object,恒打印 0 MB) - build_matrix.py:成熟度长文本移出表格(新增 maturity_level 短标签); 象限图标签靠右时翻转到左侧防溢出;SVG 加 max-width 防窄屏溢出; 文章索引改为自动生成(原先手抄,横评文发了索引里却没有) - publish_emlog.py:ID 回写按「有无同名档案」分流, 00-* 跨项目文章落到 articles/<id>.meta.yaml(此前 ID 无处可写)
This commit is contained in:
@@ -86,9 +86,15 @@ python3 scripts/new_project.py --repo owner/name
|
||||
|
||||
# 2. 手工填档案五维度 —— 重点填 risk.claim_vs_reality
|
||||
$EDITOR data/projects/<id>.yaml
|
||||
# 并把它加进 data/registry.yaml
|
||||
# ⚠️ 补完时**不要整块覆盖**下面这三个块,它们由脚本回写:
|
||||
# ecosystem.metrics(fetch_stats 写)
|
||||
# mirror (mirror_sync 写)
|
||||
# article (publish_emlog 写)
|
||||
# 整块替换 ecosystem 会把 metrics 一起删掉,表现为矩阵里指标静默变空。
|
||||
# 真踩了也别慌:fetch_stats 用 upsert_block 会自动把 metrics 长回来
|
||||
# (python3 scripts/fetch_stats.py --id <id> --force)。
|
||||
|
||||
# 3. 刷新指标
|
||||
# 3. 刷新指标(放在填档之后,别反过来)
|
||||
python3 scripts/fetch_stats.py --id <id>
|
||||
|
||||
# 4. 镜像源码(按许可证等级自动决定 private / public)
|
||||
@@ -111,8 +117,35 @@ git add -A && git commit -m "track: <name>" && git push
|
||||
### ⚠️ 更新总纲文时必读
|
||||
|
||||
emlog 的 update 接口**不传 `--content` 就会清空正文**。
|
||||
`publish_emlog.py` 已内置保护(update 前先 `detail` 取回全文),
|
||||
但如果你手工调 emlog 脚本更新总纲,**必须先 detail 再 update**。
|
||||
|
||||
保护点应该是「**本地正文非空**」,而不是「update 前先 detail 取回线上全文」——
|
||||
本脚本的 content 永远来自本地 Markdown 源文件,不存在"忘了传"的情况;
|
||||
反过来,emlog 的 detail 接口**查不到草稿**,如果把保护建立在 detail 上,
|
||||
草稿就永远无法转正。所以 `publish_emlog.py` 的判据是:本地正文非空即放行,
|
||||
detail 只作为可选的健全性检查。
|
||||
|
||||
如果你手工调 emlog 脚本更新总纲,记住同一件事:**确保传了 `--content`**。
|
||||
|
||||
### 两类文章,ID 落点不同
|
||||
|
||||
`publish_emlog.py` 按「有没有同名项目档案」自动分流,**不需要手工指定**:
|
||||
|
||||
| 文章类型 | 文件名 | ID 落点 |
|
||||
| --- | --- | --- |
|
||||
| 项目深度文 | `articles/<id>.md`(有 `data/projects/<id>.yaml`) | 档案的 `article` 块 |
|
||||
| 跨项目文章 | `articles/00-*.md`(总纲、横评、专题) | `articles/<id>.meta.yaml` |
|
||||
|
||||
新增一篇 `00-` 开头的文章不用做任何额外配置,发完就有 meta。
|
||||
反过来,如果某天需要给跨项目文章单独建档,只要 YAML 一出现,脚本会改走档案路径——
|
||||
先按「是不是项目文」分流,再决定写哪儿。
|
||||
|
||||
### 文章索引不需要手工维护
|
||||
|
||||
总纲里 `<!-- INDEX:BEGIN/END -->` 之间的内容由 `build_matrix.py` 生成,
|
||||
事实源是两处:项目档案的 `article` 块 + `articles/*.meta.yaml`。
|
||||
|
||||
这段原本是手抄的,结果横评文发了、索引里却没有它——**手写的索引每发一篇文章就漂移一次**。
|
||||
现在只需要在发完文章后重跑 `build_matrix.py`,没深度文的项目会自动标注「待发布」并指向现有横评。
|
||||
|
||||
---
|
||||
|
||||
@@ -122,9 +155,9 @@ emlog 的 update 接口**不传 `--content` 就会清空正文**。
|
||||
| --- | --- | --- |
|
||||
| `new_project.py` | 建档 + 自动判许可证等级 | API 失败时生成未定级骨架,要求人工填 |
|
||||
| `fetch_stats.py` | 抓指标 + 累积时间序列快照 | 无 `GITHUB_TOKEN` 则匿名降级;当日缓存;失败沿用上次快照并标 `stale` |
|
||||
| `build_matrix.py` | 生成对比矩阵 + 赛道图 / 象限图 | 项目 <4 个出分层图,≥4 个才出象限图 |
|
||||
| `build_matrix.py` | 生成对比矩阵 + 赛道图 / 象限图 + 文章索引 | 项目 <4 个出分层图,≥4 个才出象限图;索引缺标记则跳过并警告 |
|
||||
| `mirror_sync.sh` | 源码镜像同步 | 大仓(>300MB)提示并支持 treeless clone 降级 |
|
||||
| `publish_emlog.py` | emlog 发布 / 更新 | update 前强制取回全文防空内容 |
|
||||
| `publish_emlog.py` | emlog 发布 / 更新 | 本地正文为空时拒绝 update(防清空线上) |
|
||||
|
||||
---
|
||||
|
||||
@@ -132,6 +165,17 @@ emlog 的 update 接口**不传 `--content` 就会清空正文**。
|
||||
|
||||
- **GitHub API 限额**:无 `GITHUB_TOKEN` 时匿名限额 60 次/小时。
|
||||
项目数超过约 20 个后批量刷新会受限,建议配置 token。
|
||||
- **磁盘占用**:源码镜像是全量裸仓。10 个量级约需数 GB,
|
||||
- **GitHub 下载带宽**:本机到 GitHub 实测约 **50 KB/s**(走沙箱代理 56 KB/s,
|
||||
直连 27 KB/s 且 github.com 常被阻断)。591 MB 的仓全量镜像需要 3 小时以上。
|
||||
大仓一律先 `--depth 1`,后续再用脚本的增量加深补齐。
|
||||
- **Gitea 推送上限**:前置 nginx 的 `client_max_body_size` 约 **50 MB**
|
||||
(实测 48 MB 放行、64 MB 拒绝)。超限会在传完所有对象后才收到 HTTP 413。
|
||||
脚本内置体积闸门,超限自动降级为「源码快照」(剔除非源码素材,通常能减一半以上)。
|
||||
彻底解法是在服务器 nginx 配置里放开该值。
|
||||
- **浅仓不能直接推送**:Git 服务端默认 `receive.shallowUpdate = deny`,
|
||||
推浅仓会被拒。脚本在推送前自动 `--unshallow` 补齐历史;
|
||||
若补齐失败则降级为快照(丢历史但保住代码)。
|
||||
- **磁盘占用**:源码镜像是全量裸仓。4 个项目已占约 800 MB。
|
||||
大仓可用 `--filter=blob:none` 做 treeless 镜像。
|
||||
- **象限图阈值**:样本 <4 个时象限图没有统计意义,此阶段只出赛道分层图。
|
||||
4 个也只是示意,不构成统计结论。
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
# 四个 Agent 工作台的路线分野:把「多引擎中立」拆开看
|
||||
|
||||
> **速览**
|
||||
> | 项 | iPolloWork | OpenWork | Orca | Maka |
|
||||
> | --- | --- | --- | --- | --- |
|
||||
> | 仓库 | Devin-AXIS/iPolloWork | different-ai/openwork | stablyai/orca | apache/maka |
|
||||
> | Star | 5,201 | 23,253 | 58,771 | 4,373 |
|
||||
> | Fork / Issues | 976 / 80 | 2,307 / 414 | 3,987 / 5,000 | 406 / 398 |
|
||||
> | 主语言 | TypeScript 49.5% | TypeScript 89.9% | TypeScript 97.0% | TypeScript 91.7% |
|
||||
> | 仓库体积 | 591 MB | 291 MB | 671 MB | 100 MB |
|
||||
> | 最新版本 | v0.50.12 | v0.18.40 | v1.4.194 | v0.2.0-dev.9 |
|
||||
> | 许可证 | Source Available(L3) | MIT + EE 切分(L3) | MIT(L1) | Apache-2.0(L1) |
|
||||
> | 建仓 | 2025-08-25 | 2026-01-14 | 2026-03-17 | 2026-05-27 |
|
||||
>
|
||||
> **一句话结论**:四个项目都在「Agent 工作台」这个筐里,但争的完全不是同一件事——
|
||||
> iPolloWork 争入口,OpenWork 争能力分发,Orca 争并行规模,Maka 争可审计性。
|
||||
> 把它们的「多引擎中立」拆开看,会发现这根本是四个不同的词。
|
||||
|
||||
---
|
||||
|
||||
## 0. 先给结论
|
||||
|
||||
这个追踪系列的第一个样本是 iPolloWork。只有它的时候,我判断它是「桌面工作台层的一个多引擎整合尝试」。
|
||||
样本增加到四个之后,这个判断需要修正:**「桌面工作台」不是一个赛道,是四条不同的路。**
|
||||
|
||||
- **Orca(58.8k★)** 是人气第一,但它的 5,000 个 open issue 里绝大多数是开发者自己开的任务卡——
|
||||
最近 100 次提交中 `nwparker` 独占 79 次。广度做到极致,治理还是草稿。
|
||||
- **OpenWork(23.3k★)** 是工程治理最好的一个,但它和自称要替代的 Claude Cowork 不是一个物种——
|
||||
它的主体是一个只暴露两个工具的 MCP 服务,桌面 app 是可选的。
|
||||
- **Maka(4.4k★)** 是唯一自带评测框架、唯一把「执行记录」当地基的项目,
|
||||
但也是唯一没有任何正式 release、只支持 Apple Silicon Mac 的项目。
|
||||
- **iPolloWork(5.2k★)** 是唯一把交付物从代码延伸到 PPT / 网页 / 设计 / 视频的,
|
||||
但它的三种引擎接入深度并不齐整。
|
||||
|
||||
最值得记住的一点是:**许可证的开放程度,完全不能预测项目的治理质量。**
|
||||
Orca 用最开放的 MIT,治理最弱;OpenWork 用最受限制的混合许可,工程规范最强。
|
||||
|
||||
---
|
||||
|
||||
## 1. 从 1 个样本到 4 个:为什么现在能出象限图了
|
||||
|
||||
只有一个样本的时候,任何对比都是过度抽象——你不知道一个坐标点代表的是什么水平。
|
||||
四个样本足够看出**分布**,但还不足以看出**规律**。所以这一篇是横向对比,不是最终结论。
|
||||
|
||||
四个项目都满足同一个筛选条件:**面向真实工作的、可在本地运行的 Agent 工作台**,
|
||||
而不是某个 agent 的 demo 或 SDK。这也是本系列的收录标准。
|
||||
|
||||
先看它们的硬指标分布(数据日期 2026-09-01,全部来自 GitHub API 实测):
|
||||
|
||||
| 项目 | Star | 建仓 | 距今天数 | 日均 Star |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| iPolloWork | 5,201 | 2025-08-25 | 372 天 | 14.0 |
|
||||
| OpenWork | 23,253 | 2026-01-14 | 230 天 | 101.1 |
|
||||
| Orca | 58,771 | 2026-03-17 | 168 天 | 349.8 |
|
||||
| Maka | 4,373 | 2026-05-27 | 97 天 | 45.1 |
|
||||
|
||||
Orca 的增速是第二名的三倍多。但 Star 增速和成熟度从来不是一回事——
|
||||
Orca 的版本号 `v1.4.194` 泄露了另一面:一个 1.x 版本号跑到第 194 个补丁版本,
|
||||
说明日均不止一次发版。
|
||||
|
||||
---
|
||||
|
||||
## 2. 赛道分层:它们在争什么
|
||||
|
||||
按 Agent 工具的三层栈来定位:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 第 3 层 · 桌面工作台层 │
|
||||
│ │
|
||||
│ iPolloWork ──────► 争「入口」:把多引擎收进同一界面 │
|
||||
│ Orca ────────────► 争「规模」:并行 N 个 agent 各自 worktree │
|
||||
│ Maka ────────────► 争「可信」:执行记录 + 可复现评测 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
▲ 消费
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 第 2 层 · 能力分发层 │
|
||||
│ │
|
||||
│ OpenWork ────────► 争「复用」:一次创建的能力处处可用 │
|
||||
│ (不接管执行,只提供 capabilities) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
▲ 依赖
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 第 1 层 · 执行引擎层 │
|
||||
│ Codex · Claude Code · OpenCode · DSH · Pi · Kimi · ... │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**关键观察**:OpenWork 不在这条纵轴上跟其他三个竞争,它横插在中间做「能力层」。
|
||||
Maka 也不完全在第 3 层——它自带 Runtime,实际上从第 1 层一路做到第 3 层。
|
||||
|
||||
所以严格说,**只有 iPolloWork 和 Orca 是正面竞品**,
|
||||
OpenWork 和 Maka 是从不同方向切进来的。这也解释了为什么功能对比表对它们意义不大。
|
||||
|
||||
---
|
||||
|
||||
## 3. 业务维度:四条完全不同的变现路径
|
||||
|
||||
| 项目 | 变现方式 | 免费边界 | 商业意图 |
|
||||
| --- | --- | --- | --- |
|
||||
| **iPolloWork** | 双轨:个人与 <3 人免费;3 人以上及任何商用需书面授权 | 人数 + 用途双重限制 | 用社区版铺量,商业授权变现,iPolloCloud 承载身份与权益 |
|
||||
| **OpenWork** | Den 订阅(组织控制面) | ≤5 人组织免费、30 天评估、开发测试永久免费 | GitLab 式开源核心 + 付费管控面 |
|
||||
| **Orca** | **未明示** | 全部免费 | 无公开营收路径 |
|
||||
| **Maka** | 无 | 全部免费 | ASF 孵化项目,无商业实体 |
|
||||
|
||||
### 3.1 OpenWork 的许可设计值得单独说
|
||||
|
||||
它的根目录 `LICENSE`(36 行)声明 `ee/` 之外是 MIT,`ee/LICENSE`(72 行)是 OpenWork EE License。
|
||||
这种「目录切分许可」是 GitLab 带火的做法,但 OpenWork 加了一个少见的条款:
|
||||
|
||||
> Grant of Future License: Different AI, Inc. hereby irrevocably grants you
|
||||
> an additional license to use each version of the Software under the MIT license,
|
||||
> **effective on the second anniversary** of the date that version of the Software
|
||||
> is first made publicly available.
|
||||
|
||||
**每个 ee/ 版本发布满两年后自动转 MIT。** 这是一个很有说服力的承诺——
|
||||
它把「开源核心会不会被收回」这个疑虑,用一个不可逆的、带时间锁的授权消解掉了。
|
||||
对比那些纯粹靠「我们承诺不会作恶」的项目,这个设计硬得多。
|
||||
|
||||
顺带说一个方法论上的收获:这个仓库的 SPDX 标识是 `NOASSERTION`,
|
||||
自动化脚本会把它判成 L3 / 私有镜像。但人工复核后发现,
|
||||
**排除 `ee/` 目录之后,剩下的内容是纯 MIT,完全可以公开镜像。**
|
||||
同一个仓库,两种正确答案,取决于镜像是否包含受限目录。
|
||||
我据此给追踪体系的许可证分级规则补了一条「混合许可降级流程」——
|
||||
否则这类项目会被系统性地过度保守处理。
|
||||
|
||||
### 3.2 Orca 的空白才是最值得警惕的
|
||||
|
||||
58.8k Star、MIT 全开源、无企业版、无订阅层,README 还明确写着
|
||||
「不提供模型,也不替你购买订阅」。
|
||||
|
||||
**一个没有营收路径的 58k Star 项目,是一个不稳定态。**
|
||||
它要么在准备融资,要么在准备改条款。参考 Redis、Terraform、Elasticsearch 的先例,
|
||||
「先用 MIT 铺到不可替代,再换成限制性许可」是已经被验证过的路径。
|
||||
这不代表 Orca 一定会这么做,但选型时应该把这个可能性算进去。
|
||||
|
||||
---
|
||||
|
||||
## 4. 场景维度:交付物决定了用户群
|
||||
|
||||
这一维度的差异比想象中大得多:
|
||||
|
||||
| 项目 | 最终产出 | 产出后还能改吗 | 由此决定的用户群 |
|
||||
| --- | --- | --- | --- |
|
||||
| **Orca** | code、diff、review comment | 能(就是普通代码) | 专业开发者 |
|
||||
| **OpenWork** | workflow、skill 包、capability | 能,且可跨工具复用 | 团队 / 组织 IT |
|
||||
| **Maka** | code、artifact、**eval 报告**、**执行记录** | 能,且执行记录可回溯 | 研究者 / 受监管团队 |
|
||||
| **iPolloWork** | code、doc、**PPT**、**网页**、**设计**、**视频** | 能,且定位就是「继续手工改」 | 内容团队 |
|
||||
|
||||
**Maka 的「执行记录」是本轮唯一的新物种。** 它的 README 有一句话很能说明设计取向:
|
||||
|
||||
> Shorter context is not deleted history.
|
||||
|
||||
上下文压缩只省略下次 prompt 里的旧 tool 输出,不会删掉已经落盘的证据。
|
||||
model 消息、tool call、tool result、turn 的结束方式全部写进 append-only 日志。
|
||||
对受监管行业的团队来说,这是「能不能用」的前提条件,不是锦上添花。
|
||||
|
||||
**Orca 的交付物最窄(只有代码),但它的并行规模最大**——
|
||||
一条 prompt 扇出到 5 个 agent,各自在独立 git worktree 里实现,人工比对后合并优胜解。
|
||||
这是把「多方案比选」做成了产品,而不是让用户自己开 5 个终端。
|
||||
|
||||
---
|
||||
|
||||
## 5. 技术维度:四种「多引擎中立」
|
||||
|
||||
这是我认为全篇最值得看的一节。四个项目都声称或暗示支持多个 agent 引擎,
|
||||
但实现方式完全不同,深度也完全不对等。
|
||||
|
||||
### 5.1 iPolloWork:协议级 adapter,但深度不对等
|
||||
|
||||
```
|
||||
桌面 UI
|
||||
│
|
||||
▼
|
||||
Engine Protocol(local API)
|
||||
├──► OpenCode adapter ← 原生,默认本地 runtime,独立 sidecar
|
||||
├──► DSH adapter ← 原生,但仍在开发中
|
||||
└──► [MCP 控制面] ─────► Codex ← 浅
|
||||
```
|
||||
|
||||
README 自己承认了这种不对等:Codex「currently uses the MCP control surface
|
||||
rather than claiming a native Codex engine adapter」。
|
||||
快是真的快(8 小时打通),但**快是因为走了控制面,深度也就止于控制面**。
|
||||
|
||||
### 5.2 Orca:PTY 级平等,但平等在低处
|
||||
|
||||
```
|
||||
桌面 UI
|
||||
│
|
||||
▼
|
||||
PTY(伪终端)
|
||||
├──► Codex
|
||||
├──► Claude Code
|
||||
├──► OpenCode
|
||||
├──► Kimi / Qwen Code / MiMo Code ...
|
||||
└──► 共 30+ 个 CLI agent
|
||||
```
|
||||
|
||||
兼容判据就一条:**「能在终端里跑」**。
|
||||
代价是没有任何一家能拿到原生能力——拿不到结构化 tool call、
|
||||
拿不到统一的权限回调、拿不到一致口径的 token 统计。
|
||||
|
||||
**广度最大,深度最浅。** 这是 Orca 一切优点和缺点的共同根源。
|
||||
|
||||
### 5.3 OpenWork:MCP 级反注入,桌面是可选
|
||||
|
||||
```
|
||||
OpenWork MCP(只暴露 2 个工具)
|
||||
├── search_capabilities
|
||||
└── execute_capability
|
||||
▲
|
||||
│ 作为 MCP 客户端连入
|
||||
├──► Codex codex mcp add openwork --url ...
|
||||
├──► Claude Code claude mcp add --transport http openwork ...
|
||||
├──► Cursor
|
||||
└──► OpenCode ← 真正深度绑定的(仓库描述即 powered by opencode)
|
||||
```
|
||||
|
||||
注意方向反了:它不是去接引擎,而是**让引擎来接它**。
|
||||
所以它不接管 agent 的执行循环、不负责上下文、不做权限回调。
|
||||
README 那句「桌面 app 是可选的」不是谦虚,是架构的直接结果。
|
||||
|
||||
### 5.4 Maka:不做接入,自己就是引擎
|
||||
|
||||
```
|
||||
Desktop ┐
|
||||
TUI/CLI ┼──► Runtime Host ──► AgentRun(自带)
|
||||
Eval ┘ └─► [external subject adapter] ← 仅评测用
|
||||
```
|
||||
|
||||
Maka 对**自己的**引擎深度是满分——一个 Runtime Host 统管所有入口。
|
||||
对**外部**引擎深度是零,只在 eval 里当被测对象。
|
||||
|
||||
它压根不参与「多引擎中立」这条赛道的竞争。这是定位选择,不是能力缺陷,
|
||||
但读者必须知道它和另外三个不是同一类选手。
|
||||
|
||||
### 5.5 四种模式对照
|
||||
|
||||
| 项目 | 抽象层 | 引擎数 | 深度 | 本质 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| iPolloWork | Engine Protocol | 3 | **不对等** | 想做深,但只做成了一家半 |
|
||||
| Orca | 无(PTY) | 30+ | **都浅** | 用广度换深度,很诚实 |
|
||||
| OpenWork | OpenWork MCP | 4+ | 能力面 | 不接管执行,只分发能力 |
|
||||
| Maka | Runtime Host | 1(自己) | 满 | 不做中立,做纵深 |
|
||||
|
||||
**如果只能记住一句话**:下次看到「支持多引擎」,先问是哪种中立。
|
||||
协议级、PTY 级、能力级、还是根本不中立——这四种的成本和价值差了一个数量级。
|
||||
|
||||
---
|
||||
|
||||
## 6. 宣称 vs 实测
|
||||
|
||||
四个项目合计核出 14 条落差。这里挑最有代表性的 8 条,完整清单见各项目档案。
|
||||
|
||||
### 6.1 Orca:5,000 个 issue 的真相
|
||||
|
||||
> **宣称**:5,000 个 open issue —— 看起来是严重的社区问题积压。
|
||||
> **实际**:抽样最近 100 条,**全部创建于 2026-09-01 同一天,94% 没有任何标签**,
|
||||
> 标题是 `fix(ssh): ...` / `refactor: name split modules` / `fix(task-page): ...`
|
||||
> 这种内部任务口吻。提交者 TOP3 是 `nwparker`(51)、`OrcaWin`(13)、`klaidliadon`(12)。
|
||||
>
|
||||
> 这是**团队自开的任务待办板**,不是社区投诉,也不构成 triage 压力。
|
||||
> 而且带明显的 AI 批量生成痕迹——比如 `OrcaWin` 提交的这条:
|
||||
> 「pty.spawn can leak a running agent process unboundedly: host holds the door open」,
|
||||
> 这种文学化的措辞不像人手写的。
|
||||
>
|
||||
> **但要说清楚另一面**:这同时意味着这 5,000 条从来没有被当作对外承诺管理过。
|
||||
> **来源**:GitHub API `/repos/stablyai/orca/issues?state=open`,2026-09-01 抽样 100 条。
|
||||
|
||||
### 6.2 Orca:巴士系数为 1
|
||||
|
||||
> **宣称**:「The AI Orchestrator for 100x builders」,面向多人协作的成熟开发环境。
|
||||
> **实际**:最近 100 次提交中 `nwparker` 独占 **79 次**,第二名 `AmethystLiang` 仅 9 次。
|
||||
> **来源**:GitHub API `/repos/stablyai/orca/commits?per_page=100`,2026-09-01。
|
||||
|
||||
### 6.3 OpenWork:它要替代的东西,和它不是一个物种
|
||||
|
||||
> **宣称**:「The open-source alternative to Claude Cowork」。
|
||||
> **实际**:Cowork 是**桌面操控型** agent(直接操作你的电脑完成工作);
|
||||
> OpenWork 是**能力分发型** MCP 层。README 原文写得很直白:
|
||||
> 「The desktop app is there when you want a dedicated workspace, but it is not required」。
|
||||
>
|
||||
> 它替代的是 Cowork 唤起的「能力复用」诉求,不是 Cowork 本身。
|
||||
> **来源**:README 开篇与「Use OpenWork from any agent」段。
|
||||
|
||||
### 6.4 OpenWork:NOASSERTION 反而是治理最好的
|
||||
|
||||
> **宣称**:(无明确宣称,但 SPDX 为 `NOASSERTION`,容易被读成「不够开源」)
|
||||
> **实际**:恰恰相反,它是四个里工程治理最规范的——
|
||||
> 强制 DCO sign-off(每个 commit 都要 `Signed-off-by`)、
|
||||
> `ee/` 目录改动还需额外 CLA、`evals/` 放可执行测试规格并要求 PR 附测试证据、
|
||||
> `worlds/` 放声明式的开发与测试环境。
|
||||
>
|
||||
> `NOASSERTION` 只因为根目录 LICENSE 是个混合声明文件,不代表开放度低。
|
||||
> **来源**:README「Getting started (contributors)」与「Sending a pull request」段。
|
||||
|
||||
### 6.5 Maka:Apache 光环不等于生产可用
|
||||
|
||||
> **宣称**:Apache 品牌 = 成熟稳定。
|
||||
> **实际**:孵化状态恰恰是「尚未被 ASF 完全背书」的标记,README 自带官方免责声明。
|
||||
> 截至 2026-09-01 **没有任何 Apache 正式 release**,对外分发的只有 Desktop Nightly,
|
||||
> README 明确标注「not an ASF release, not intended for production use」。
|
||||
>
|
||||
> 平台支持也最窄:macOS arm64 是唯一正式档,Windows 为未签名预览,Linux 标注 `soon`。
|
||||
> **来源**:README 的 NOTE / IMPORTANT 段、Releases and downloads 段、平台 badge。
|
||||
|
||||
### 6.6 Maka:本地优先,但 API key 是明文
|
||||
|
||||
> **宣称**:「Your machine, your data」本地优先、数据主权。
|
||||
> **实际**:数据确实不出机器,但 README 自己承认 `credential-vault.json` 是
|
||||
> 「local plaintext file, readable only by your OS account」。
|
||||
>
|
||||
> 防护依赖操作系统账户权限,不是加密也不是系统钥匙串。
|
||||
> renderer 进程拿不到,但同机任一以你身份运行的进程都能读到。
|
||||
> **来源**:README「Local data and recovery」段。
|
||||
|
||||
### 6.7 Maka:崩溃恢复的两个附加条件
|
||||
|
||||
> **宣称**:崩溃恢复与可选续跑。
|
||||
> **实际**:续跑**默认关闭**,需手动设 `MAKA_RUNTIME_SAFE_BOUNDARY_RESUME=1`,
|
||||
> 且 README 警告该调用会打模型、消耗 token。
|
||||
> 另有**升级断代**:旧版 JSONL transcripts 与 Electron safeStorage 凭据不会被导入,
|
||||
> 升级后的工作区可能出现空线程,凭据必须重新录入。
|
||||
> **来源**:README「Local data and recovery」段。
|
||||
|
||||
### 6.8 Orca:三端覆盖,成熟度不对等
|
||||
|
||||
> **宣称**:桌面 / 移动 / VPS 全覆盖。
|
||||
> **实际**:Android 仍是手工分发 APK(0.0.47),iOS 走 App Store 与 TestFlight,
|
||||
> VPS 场景需另查 headless Linux 指南。
|
||||
> 移动端实际是「监控与追加指令」的伴随形态,不是完整工作环境。
|
||||
> **来源**:README Install 段与 Mobile Companion 段。
|
||||
|
||||
---
|
||||
|
||||
## 7. 象限图:四个坐标
|
||||
|
||||
按四个维度打分(0–10,人工标定,见各项目档案的 `chart` 块):
|
||||
|
||||
| 项目 | 形态纵深 | 引擎中立 | 本地优先 | 交付物纵深 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| iPolloWork | 8 | 7 | 7 | 7 |
|
||||
| OpenWork | 5 | 6 | 6 | 5 |
|
||||
| Orca | 8 | 9 | 6 | 3 |
|
||||
| Maka | 6 | 1 | 10 | 6 |
|
||||
|
||||
把「引擎中立」和「本地优先」拉成两个轴:
|
||||
|
||||
```
|
||||
本地优先 10 │
|
||||
│ Maka (1,10)
|
||||
9 │
|
||||
│ iPolloWork (7,7)
|
||||
7 │
|
||||
│ OpenWork (6,6) Orca (9,6)
|
||||
6 │
|
||||
│
|
||||
5 │
|
||||
└────┬────┬────┬────┬────┬────┬────
|
||||
1 3 5 7 9
|
||||
引擎中立 →
|
||||
```
|
||||
|
||||
**读图**:
|
||||
- **右上(高本地 + 高中立)目前是空的**。这四个项目里没有一个同时做到
|
||||
「完全本地」和「多引擎中立」——这大概就是下一个值得关注的空白位。
|
||||
- Maka 在左上角孤零零一个点:它用「放弃中立」换来了「完全本地」。
|
||||
- Orca 在右下:中立度最高,但本地优先只有 6 分(强依赖各家云端订阅)。
|
||||
|
||||
样本只有四个,象限图只是示意,不构成统计结论。
|
||||
|
||||
---
|
||||
|
||||
## 8. 选型建议
|
||||
|
||||
| 如果你的情况是 | 选 | 理由 |
|
||||
| --- | --- | --- |
|
||||
| 已重度使用 CLI agent,想并行跑多方案比对 | **Orca** | 兼容面最广,并行 worktree 是独一份 |
|
||||
| 团队已固定在某款 agent,只想让能力跨工具复用 | **OpenWork** | MCP 反注入是唯一解,不逼你换工具 |
|
||||
| 有审计 / 数据主权 / 可复现评测的硬要求 | **Maka** | 执行记录 + eval 框架,别家都没有 |
|
||||
| 希望 agent 产出的 PPT / 网页 / 设计之后还能手工改 | **iPolloWork** | 交付物纵深最广 |
|
||||
|
||||
**共同的不建议**:
|
||||
- 都还是 0.x 或 1.x 早期版本,没有一家适合放进关键生产路径。
|
||||
- 都要求 BYOM(自带模型),模型成本与额度需要自己算。
|
||||
|
||||
**再看一段时间的信号**:
|
||||
- Orca 的营收路径是否公开(决定它会不会改许可)
|
||||
- OpenWork 是否发布 1.0(当前 v0.18.40)
|
||||
- Maka 是否产出第一个 Apache 正式 release(当前只有 Nightly)
|
||||
- iPolloWork 的 Codex 是否从 MCP 控制面升级为原生 adapter
|
||||
|
||||
---
|
||||
|
||||
## 9. 追踪备注
|
||||
|
||||
- **数据日期**:2026-09-01
|
||||
- **指标来源**:GitHub API 自动化快照(无 token,匿名配额)
|
||||
- **下次跟进点**:
|
||||
1. Orca 的 issue 是否会开始做社区 triage(当前 94% 无标签)
|
||||
2. Maka 的首个 Apache 正式 release
|
||||
3. OpenWork 1.0 与 ee/ 两年转 MIT 的首批到期版本
|
||||
- **相关档案**:`data/projects/{ipollowork,openwork,orca,maka}.yaml`
|
||||
- **源码镜像**:
|
||||
- `gitinbox/iPolloWork`(私有 · L3 source-available)
|
||||
- `gitinbox/maka`(公开 · L1 Apache-2.0)
|
||||
- OpenWork / Orca 因体积超限(291 MB / 671 MB)暂未镜像,档案中已标注
|
||||
- **方法论更新**:本轮给许可证分级补了「混合许可降级流程」,
|
||||
并给指标回写加了自愈能力(块被误删时自动重建)
|
||||
|
||||
---
|
||||
|
||||
<sub>本文为「全球智能体开源应用追踪」系列之一 · 总纲见 [系列索引](https://blog.apescale.com/?post=98) · 数据与脚本开源于 `gitinbox/agent-radar`</sub>
|
||||
@@ -0,0 +1,5 @@
|
||||
# 发布元数据(由 scripts/publish_emlog.py 维护)
|
||||
# 本文不属于单个项目,ID 不落在 data/projects/ 下
|
||||
emlog_id: 99
|
||||
url: "https://blog.apescale.com/?post=99"
|
||||
last_published: "2026-09-01"
|
||||
+104
-18
@@ -1,6 +1,7 @@
|
||||
# 全球智能体开源应用追踪 · 总纲
|
||||
|
||||
> 这是一个**持续更新**的追踪索引。每收录一个新项目,会先发一篇深度文,再回来回填这里。
|
||||
> 这是一个**持续更新**的追踪索引。每收录一个新项目会先建档,够一批样本就发一篇横向对比,
|
||||
> 再按价值排序补单项目深度文——所以索引里「待发布」是常态,不是遗漏。
|
||||
> 所有数据来自 GitHub API 的定时快照,所有结论都标注了核实来源。
|
||||
> 数据与脚本开源在 `gitinbox/agent-radar`。
|
||||
|
||||
@@ -69,24 +70,48 @@ Codex Harness 开放、DeepSeek Harness 开放,一个个执行引擎被拆出
|
||||
| 项目 | 定位 | 目标用户 | 变现 | 许可证 | 成色 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **iPolloWork** | 站在 Agent 工具链的**工作台层**而非引擎层:不替代 Codex / DSH / OpenCode 中任何一个,而是把它们收进同一个项目空间,并把交付物从代码延伸到文档、演示稿、网站、设计与视频 | 企业研发团队、需要人机协作的 agent 团队、需要产出并持续修改 PPT / 网页 / 设计 / 视频的内容团队 | 双轨:个人自用与少于 3 人的小规模内部使用免费;3 人及以上、以及任何售卖 / 转售 / 付费服务 / SaaS / 托管 / 白标 / 市场分发 / 面向客户的使用,均需书面授权。配套 iPolloCloud 承载身份、组织、权益与商业 App | iPolloWork Source Available License 1.0 | L3 |
|
||||
| **maka** | 站在「执行记录层」:不追功能广度,把「agent 干过什么必须可复现、 可审计、可评测」做成地基。同时是四个里唯一自带评测框架的项目。 | 对可审计性与数据主权有硬要求的团队、需要横向评测不同 agent / 模型的研究者与工程团队、愿意用功能丰富度换确定性的自用开发者 | 无。Apache 孵化项目,无商业实体、无双许可、无付费层。 | Apache License 2.0 | L1 |
|
||||
| **openwork** | 站在「能力分发层」,不争桌面入口。核心资产是 OpenWork MCP 与 Den 控制面—— 把 skills / MCP / 连接服务做成可跨工具、跨人、跨机器复用的组织级能力包。 | 已在使用 Codex / Claude Code / Cursor 且不想换工具的开发者、需要统一管控 agent 能力的中大型组织、想把自己搭好的工作流分发给同事或朋友的团队 | Den 订阅(组织控制面)。免费额度写得很明确: 5 人以下组织的非 Enterprise 功能全免、任意规模 30 天评估、开发测试永久免费。 | 目录切分许可(GitLab 式):ee/ 之外为 MIT;ee/ 为 OpenWork EE License | L3 |
|
||||
| **orca** | 站在「执行编排层」:不造引擎、不卖模型,只做并行 agent 的调度与可观测外壳。 上游是各家 CLI agent,下游是开发者的本机/远程 worktree。 | 自称 100x builders 的重度个人开发者、需要同时试多种实现方案再择优合并的小团队、已在用 Codex / Claude Code / OpenCode 并想并行跑的人 | 未明示,且这是它最大的商业不确定性。MIT 全开源、无企业版、无订阅层, README 明确不提供模型也不代买订阅。收入路径尚未公开。 | MIT License | L1 |
|
||||
|
||||
### 场景维度
|
||||
|
||||
| 项目 | 交互形态 | 人机协作 | 交付物 |
|
||||
| --- | --- | --- | --- |
|
||||
| **iPolloWork** | desktop-app | approval-gated | code、doc、ppt、website、design、video |
|
||||
| **maka** | 桌面应用、TUI / CLI、评测框架 | 强:工具越过沙箱边界必须审批;运行可中止;失败被分类。 | code、artifact、eval-report、execution-record |
|
||||
| **openwork** | MCP 远程服务、桌面应用(可选)、浏览器登录授权 | 能力调用需人工在浏览器登录并选择组织;企业侧由管理员通过策略管控。 | workflow、skill-package、capability |
|
||||
| **orca** | 桌面应用、移动端伴随、CLI、SSH 远程 | 强人在环:需人工比并结果、批注 diff、批准合并。 但 5000 个自开 issue 说明产品自身也在被 AI 自动改进。 | code、diff、review-comment |
|
||||
|
||||
### 技术维度
|
||||
|
||||
| 项目 | 形态 | 引擎抽象 | 接入深度 | 模型绑定 | 本地优先 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **iPolloWork** | Electron 桌面应用(另支持 dev:ui 只起浏览器 UI) | Engine Protocol(经 local API);Codex 与 MCP 客户端另经 ipollowork-ui-mcp 控制面接入 | **三种引擎的接入深度并不对等**:OpenCode 是默认本地执行 runtime,走 local API → Engine Protocol 的原生 adapter,以独立 sidecar 形式存在(首次桌面构建时下载,不 fork、可独立升级);DeepSeek Harness 是可选 peer runtime 与子代理委派目标,同样走 Engine Protocol;Codex 则**仅**通过 ipollowork-ui-mcp 的 MCP 控制面接入,README 明确写明 currently uses the MCP control surface rather than claiming a native Codex engine adapter | byom | 是 |
|
||||
| **maka** | 桌面应用 + TUI/CLI + 评测框架,三者共用同一 Runtime Host | Runtime Host(单一所有者的生命周期 + 协议 + 客户端引导) | 与另外三家正好相反:Maka 对**自己的**引擎深度是满分 (一个 Runtime Host 统管所有入口),对**外部**引擎深度是零 (只在 eval 里当被测对象)。 它压根不参与「多引擎中立」这条赛道的竞争——这是定位选择而非能力缺陷, 但读者必须知道它和 Orca / OpenWork / iPolloWork 不是同一类选手。 | 完全 BYO:云 API、本地模型或兼容网关, 不捆绑任何共享账号(README 明确 "You bring the model")。 | 是 |
|
||||
| **openwork** | MCP 远程服务 + 可选桌面应用 + Web 控制面 | OpenWork MCP(能力面,不是执行面) | 接入「对等且都浅」:所有引擎都以同一个 MCP 客户端身份接入, 拿到的是能力调用,不是执行控制——OpenWork 不接管 agent 的执行循环、 不负责上下文管理、不做权限回调。 真正深度绑定的是 OpenCode:仓库描述即写 powered by opencode, 桌面端属 OpenCode 生态,其余引擎只是 MCP 外挂。 这与 iPolloWork 的「Codex 仅 MCP 控制面」是同一个问题的两种解法: iPolloWork 把 MCP 当补丁(主体是桌面),OpenWork 把 MCP 当主体(桌面是可选)。 | Den 侧可统一配置与配额控制 model provider;桌面 app 本身 BYO | 是 |
|
||||
| **orca** | 桌面应用(含移动端伴随 + CLI) | 无统一抽象层;实际是终端 PTY + 约定式输出解析 | 接入深度「完全对等,但对等在低处」:30+ 引擎共享同一套 PTY 外壳, 因此没有任何一家能拿到原生能力——拿不到结构化 tool call、 拿不到统一权限回调、拿不到一致口径的 token 统计。 广度最大,深度最浅。对照 iPolloWork 为 OpenCode 做的原生 Engine Protocol adapter,Orca 与任一单引擎的耦合都明显更浅。 | 完全 BYO——用户自带各 agent 的订阅与额度 | 是 |
|
||||
|
||||
### 生态与风险
|
||||
|
||||
| 项目 | Star | Fork | Issues | 最新 release | 节奏 | 成熟度 | 数据日期 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **iPolloWork** | 5201 | 976 | 80 | v0.50.12 | 一天多版 | beta | 2026-09-01 |
|
||||
| **maka** | 4373 | 406 | 398 | v0.2.0-dev.9.20260831 | 一天多版 | 孵化期 | 2026-09-01 |
|
||||
| **openwork** | 23253 | 2307 | 414 | v0.18.40 | 约 1 天/版本 | 0.x 活跃 | 2026-09-01 |
|
||||
| **orca** | 58771 | 3987 | 5000 | v1.4.194 | 约 1 天/版本 | 原型期 | 2026-09-01 |
|
||||
|
||||
**成熟度详解**
|
||||
|
||||
- **iPolloWork**:beta
|
||||
- **maka**:治理成熟度最高(ASF 流程 + 评测框架 + 文档契约),
|
||||
产品成熟度最低(无正式 release、平台只支持 arm64 macOS、功能默认保守)。
|
||||
属于「地基打得最实,房子盖得最慢」。
|
||||
- **openwork**:工程成熟度最高:有可执行测试规格(evals/)、声明式环境(worlds/)、
|
||||
明确的贡献契约与许可边界。但仍处 0.x 主版本(v0.18.40),
|
||||
API 与数据格式可能变动。
|
||||
- **orca**:功能广度第一,工程治理末位。一天多版(v1.4.194)说明迭代极快,
|
||||
但 issue 只进不出、巴士系数为 1,属于「高速原型期」而非「可托付生产期」。
|
||||
|
||||
### 宣称 vs 实测
|
||||
|
||||
@@ -99,36 +124,97 @@ Codex Harness 开放、DeepSeek Harness 开放,一个个执行引擎被拆出
|
||||
| **iPolloWork** | 统一的插件与 Skills 生态 | 统一的是**生命周期**(安装/启用/更新/卸载一次),不是能力本身。README 明确各 runtime 保留自己的 agents、Skills、插件与执行模型,引擎专属增强只是可选 | README 的 Agent runtime compatibility 一节 |
|
||||
| **iPolloWork** | 仓库 topics 标注 claude-code | 中英文 README 均未描述任何 Claude Code 接入方式。topic 更像搜索曝光策略,不应视为已支持的能力 | GitHub topics 与 README 全文对照 |
|
||||
| **iPolloWork** | 企业级(enterprise-grade) | 版本号仍在 0.50.x,无公开 Roadmap,发布节奏为一天多版,仓库体积 591 MB。工程投入可观,但版本语义上尚未到企业可用的稳定态 | GitHub releases;README 版本说明;仓库 size 字段 |
|
||||
| **maka** | Apache 品牌 = 成熟稳定、可用于生产 | 孵化状态恰恰是「尚未被 ASF 完全背书」的标记,README 自带官方免责声明。 且截至 2026-09-01 **没有任何 Apache 正式 release**, 对外分发的只有 Desktop Nightly,README 明确标注 「not an ASF release, not intended for production use」。 平台支持也最窄:macOS arm64 是唯一正式档, Windows 为未签名预览,Linux 标注 soon。 | README 的 NOTE / IMPORTANT 段、Releases and downloads 段、平台 badge |
|
||||
| **maka** | 「Your machine, your data」本地优先、数据主权 | 数据确实不出机器,但 **API key 以明文文件存放**—— README 原文承认 credential-vault.json 是 「local plaintext file, readable only by your OS account」。 防护依赖操作系统账户权限,而非加密或系统钥匙串。 renderer 进程拿不到,但同机任一以你身份运行的进程都能读到。 | README「Local data and recovery」段 |
|
||||
| **maka** | 崩溃恢复与可选续跑 | 续跑**默认关闭**,需手动设 MAKA_RUNTIME_SAFE_BOUNDARY_RESUME=1, 且 README 明确警告该调用会打模型、消耗 token。 另存在**升级断代**:旧版 JSONL transcripts 与 Electron safeStorage 凭据不会被导入,升级后的工作区可能出现空线程,凭据必须重新录入。 | README「Local data and recovery」段 |
|
||||
| **maka** | (无明确宣称,但易被期待为开箱即用的完整工作台) | 内置工具只有 Read / Write / Edit / Bash / Glob / Grep 六个; Computer Use 与 catalog skills 属可选且默认关闭; IM bot(聊天应用接入)标注 experimental。 功能面是四个里最保守的——取舍很明确:用广度换确定性。 | README「Current capabilities」段 |
|
||||
| **maka** | 支持 Graph 多算子并行 | Graph 的实现算子跑在独立 git worktree,且**要求源项目是干净 worktree**—— 有未提交改动就用不了。相比 Orca 的并行 worktree(产品级封装), Maka 的 Graph 更接近实验性能力。 | README「Terminal entry points」段 |
|
||||
| **openwork** | 「Claude Cowork 的开源替代品」 | 形态完全不同。Cowork 是桌面操控型 agent(直接操作你的电脑完成工作); OpenWork 是能力分发型 MCP 层,桌面 app 被明确定位为可选—— README 原文「The desktop app is there when you want a dedicated workspace, but it is not required」。 它替代的是 Cowork 唤起的「能力复用」诉求,不是 Cowork 本身。 | README 开篇与「Use OpenWork from any agent」段 |
|
||||
| **openwork** | 跨 Codex / Claude Code / Cursor / OpenCode 多引擎通用 | 对 OpenCode 是原生的(仓库描述 powered by opencode,桌面端属其生态); 对其余引擎只通过自己暴露的两个 MCP 工具对接。 这是能力调用面,不是引擎执行面——OpenWork 不接管这些引擎的执行循环、 上下文与权限。对外宣称的「跨引擎」与内部实际的主从关系存在落差。 | README「It exposes two tools」段;仓库 description「powered by opencode」 |
|
||||
| **openwork** | free, open-source(免费开源) | 需要分目录看:ee/ 之外确实 MIT 且免费;但 ee/(Den 组织控制面、 MCP 网关、推理)属 source-available,生产使用需付费订阅, 只有 5 人以下组织、30 天评估、开发测试三种情形豁免。 个人免费,团队与企业要付费。 | README Licensing 段 + ee/LICENSE 原文 72 行 |
|
||||
| **openwork** | (无明确宣称,但 SPDX 为 NOASSERTION,易被误读为「不够开源」) | 恰恰相反——它是四个项目里工程治理最规范的:强制 DCO sign-off、 ee/ 需额外 CLA、evals/ 目录放可执行测试规格并要求 PR 附测试证据、 worlds/ 放声明式的开发与测试环境定义。 NOASSERTION 只因根目录 LICENSE 是混合声明文件,不代表开放度低。 | README「Getting started (contributors)」与「Sending a pull request」段 |
|
||||
| **orca** | 5000 个 open issue 反映社区问题积压与项目质量压力 | 抽样最近 100 条:全部创建于 2026-09-01 同一天,94% 无任何标签, 标题为 fix(ssh) / refactor / fix(task-page) 等内部任务口吻; 提交者 TOP3 为 nwparker(51)、OrcaWin(13)、klaidliadon(12)。 实为「团队自开的任务待办板」,且带明显 AI 批量生成痕迹 (如 OrcaWin 提交的「pty.spawn can leak a running agent process unboundedly: host holds the door open」)。 它不是社区投诉,也不构成 triage 压力——但同样说明这 5000 条 从未被当作对外承诺来管理。 | GitHub API /repos/stablyai/orca/issues?state=open,2026-09-01 抽样 100 条 |
|
||||
| **orca** | 「100x builders 的 AI Orchestrator」,面向多人协作的成熟开发环境 | 巴士系数为 1:最近 100 次提交中 nwparker 独占 79 次, 第二名 AmethystLiang 仅 9 次。功能面由单人 + AI 高强度驱动, 而非团队协作成果。 | GitHub API /repos/stablyai/orca/commits?per_page=100,2026-09-01 |
|
||||
| **orca** | 支持 30+ 种 agent,生态最开放 | 兼容判据是「能在终端里跑」,靠 PTY 拉起而非协议对接。 广度最大但深度最浅——所有引擎都拿不到结构化 tool call 与统一的权限回调。与 iPolloWork 为 OpenCode 做原生 Engine Protocol adapter 相比,Orca 与任一单引擎的耦合都更浅。 | README「Works with any CLI agent — if it runs in a terminal, it runs in Orca」 |
|
||||
| **orca** | MIT 全开源,可放心用于生产 | 许可证本身无风险,但项目没有任何公开的营收路径 (无企业版、无订阅、不卖模型、不代买订阅)。 58k star 的体量与零商业化不匹配,属于「要么即将融资、 要么后续变更条款」的不稳定态。 | README License 段;README 明确「Orca does not provide models, nor does it buy subscriptions for you」 |
|
||||
| **orca** | 桌面 / 移动 / VPS 全覆盖 | 三端成熟度不对等:Android 仍是手工分发 APK(0.0.47)、 iOS 走 App Store 与 TestFlight、VPS 场景需另查 headless Linux 指南。 移动端实际是「监控与追加指令」的伴随形态,不是完整工作环境。 | README Install 段与 Mobile Companion 段 |
|
||||
|
||||
### 一句话结论
|
||||
|
||||
| 项目 | 结论 | 适合 | 不适合 |
|
||||
| --- | --- | --- | --- |
|
||||
| **iPolloWork** | 把多个 Agent 引擎收进同一个可编辑工作台的野心之作——方向踩在点上,但三种引擎的接入深度并不齐整,且许可证决定了它暂时只适合个人或极小团队试用 | 想在同一个界面管理多引擎项目、并希望 agent 产出的 PPT / 网页 / 设计 / 视频之后还能继续手工修改的个人开发者,或少于 3 人的极小团队 | 3 人以上需要正式落地的团队(许可证要求书面授权)、期待 Codex 原生级集成的用户(目前只有 MCP 控制面)、以及追求稳定的生产环境(v0.50.x、一天多版、DSH 集成未进稳定版) |
|
||||
| **maka** | 四个里唯一把「执行记录」当地基的项目——治理与可复现性最强,产品成熟度最弱。 | 对审计、数据主权、可复现评测有硬要求的团队; 做 agent 横向对比研究的工程团队。 | 要开箱即用、要 Windows / Linux、要丰富第三方集成的人; 以为 ASF 品牌等于生产可用的决策者。 |
|
||||
| **openwork** | 把「能力」而不是「桌面」做成产品——工程治理最好的一个,但它和自称要替代的东西不是一个物种。 | 已固定在某款 agent 上、只想让团队能力跨工具复用的开发者; 需要统一管控 agent 能力与模型配额的组织 IT。 | 想要一个开箱即用的桌面 agent 工作台的人; 以为拿到的是完整 Cowork 平替的人。 |
|
||||
| **orca** | 兼容面最广的并行 agent 编排外壳——广度做到极致,深度与治理都还是草稿。 | 已在重度使用 CLI agent、想同时跑多个方案横向比对的个人开发者与小团队。 | 需要稳定 SLA、指望社区 triage 与多人维护保障的团队; 指望它统一各引擎能力(结构化调用、统一权限、一致口径)的架构选型者。 |
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 执行引擎层 (暂无收录)
|
||||
│ └ 真正跑 agent loop 的地方
|
||||
│ │
|
||||
│ 编辑器内 Agent 层 (暂无收录)
|
||||
│ └ 嵌在 IDE / 编辑器里的交互层
|
||||
│ │
|
||||
│ 桌面工作台层 iPolloWork★5201
|
||||
│ └ 跨引擎、跨项目、跨交付物的统一界面
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
> 当前收录 1 个项目,不足 4 个,此阶段只出分层图。样本够了之后会自动切换为象限图。
|
||||
<svg viewBox="0 0 720 420" width="720" height="420" xmlns="http://www.w3.org/2000/svg" style="font-family:sans-serif;max-width:100%;height:auto">
|
||||
<text x="360" y="26" text-anchor="middle" font-size="15" font-weight="600" fill="#1f2328">视图 A · 形态纵深 × 引擎立场</text>
|
||||
<rect x="70" y="55" width="580" height="295" fill="#fafbfc" stroke="#d8dee4"/>
|
||||
<line x1="215.0" y1="55" x2="215.0" y2="350" stroke="#eaeef2"/>
|
||||
<line x1="70" y1="128.75" x2="650" y2="128.75" stroke="#eaeef2"/>
|
||||
<line x1="360.0" y1="55" x2="360.0" y2="350" stroke="#eaeef2"/>
|
||||
<line x1="70" y1="202.5" x2="650" y2="202.5" stroke="#eaeef2"/>
|
||||
<line x1="505.0" y1="55" x2="505.0" y2="350" stroke="#eaeef2"/>
|
||||
<line x1="70" y1="276.25" x2="650" y2="276.25" stroke="#eaeef2"/>
|
||||
<text x="70" y="372" font-size="11" fill="#6a737d">单点工具</text>
|
||||
<text x="650" y="372" text-anchor="end" font-size="11" fill="#6a737d">统一工作台</text>
|
||||
<text x="62" y="356" text-anchor="end" font-size="11" fill="#6a737d">绑定单引擎</text>
|
||||
<text x="62" y="67" text-anchor="end" font-size="11" fill="#6a737d">多引擎中立</text>
|
||||
<circle cx="534.0" cy="143.5" r="7" fill="#0969da" fill-opacity="0.75" stroke="#fff" stroke-width="1.5"/>
|
||||
<text x="545.0" y="147.5" font-size="12" fill="#1f2328">iPolloWork</text>
|
||||
<circle cx="418.0" cy="320.5" r="7" fill="#0969da" fill-opacity="0.75" stroke="#fff" stroke-width="1.5"/>
|
||||
<text x="429.0" y="324.5" font-size="12" fill="#1f2328">maka</text>
|
||||
<circle cx="360.0" cy="173.0" r="7" fill="#0969da" fill-opacity="0.75" stroke="#fff" stroke-width="1.5"/>
|
||||
<text x="371.0" y="177.0" font-size="12" fill="#1f2328">openwork</text>
|
||||
<circle cx="534.0" cy="84.5" r="7" fill="#0969da" fill-opacity="0.75" stroke="#fff" stroke-width="1.5"/>
|
||||
<text x="545.0" y="88.5" font-size="12" fill="#1f2328">orca</text>
|
||||
</svg>
|
||||
<svg viewBox="0 0 720 420" width="720" height="420" xmlns="http://www.w3.org/2000/svg" style="font-family:sans-serif;max-width:100%;height:auto">
|
||||
<text x="360" y="26" text-anchor="middle" font-size="15" font-weight="600" fill="#1f2328">视图 B · 本地优先 × 交付物纵深</text>
|
||||
<rect x="70" y="55" width="580" height="295" fill="#fafbfc" stroke="#d8dee4"/>
|
||||
<line x1="215.0" y1="55" x2="215.0" y2="350" stroke="#eaeef2"/>
|
||||
<line x1="70" y1="128.75" x2="650" y2="128.75" stroke="#eaeef2"/>
|
||||
<line x1="360.0" y1="55" x2="360.0" y2="350" stroke="#eaeef2"/>
|
||||
<line x1="70" y1="202.5" x2="650" y2="202.5" stroke="#eaeef2"/>
|
||||
<line x1="505.0" y1="55" x2="505.0" y2="350" stroke="#eaeef2"/>
|
||||
<line x1="70" y1="276.25" x2="650" y2="276.25" stroke="#eaeef2"/>
|
||||
<text x="70" y="372" font-size="11" fill="#6a737d">强云依赖</text>
|
||||
<text x="650" y="372" text-anchor="end" font-size="11" fill="#6a737d">完全本地</text>
|
||||
<text x="62" y="356" text-anchor="end" font-size="11" fill="#6a737d">只出代码</text>
|
||||
<text x="62" y="67" text-anchor="end" font-size="11" fill="#6a737d">多模态可编辑</text>
|
||||
<circle cx="476.0" cy="143.5" r="7" fill="#0969da" fill-opacity="0.75" stroke="#fff" stroke-width="1.5"/>
|
||||
<text x="487.0" y="147.5" font-size="12" fill="#1f2328">iPolloWork</text>
|
||||
<circle cx="650.0" cy="173.0" r="7" fill="#0969da" fill-opacity="0.75" stroke="#fff" stroke-width="1.5"/>
|
||||
<text x="661.0" y="177.0" font-size="12" fill="#1f2328">maka</text>
|
||||
<circle cx="418.0" cy="202.5" r="7" fill="#0969da" fill-opacity="0.75" stroke="#fff" stroke-width="1.5"/>
|
||||
<text x="429.0" y="206.5" font-size="12" fill="#1f2328">openwork</text>
|
||||
<circle cx="418.0" cy="261.5" r="7" fill="#0969da" fill-opacity="0.75" stroke="#fff" stroke-width="1.5"/>
|
||||
<text x="429.0" y="265.5" font-size="12" fill="#1f2328">orca</text>
|
||||
</svg>
|
||||
<!-- MATRIX:END -->
|
||||
|
||||
---
|
||||
|
||||
## 深度文索引
|
||||
## 文章索引
|
||||
|
||||
| 项目 | 一句话结论 | 文章 |
|
||||
> 这一段由 `scripts/build_matrix.py` 从项目档案的 `article` 块与
|
||||
> `articles/*.meta.yaml` 自动生成。发新文后重跑脚本即可,不要手工改。
|
||||
|
||||
<!-- INDEX:BEGIN -->
|
||||
| 项目 | 一句话结论 | 深度文 |
|
||||
| --- | --- | --- |
|
||||
| iPolloWork | 把多个 Agent 引擎收进同一个可编辑工作台的野心之作——方向踩在点上,但三种引擎的接入深度并不齐整,且许可证决定了它暂时只适合个人或极小团队试用 | 见同期发布的《iPolloWork 深度拆解》 |
|
||||
| **iPolloWork** | 把多个 Agent 引擎收进同一个可编辑工作台的野心之作 | [已发布](https://blog.apescale.com/?post=97) |
|
||||
| **maka** | 四个里唯一把「执行记录」当地基的项目 | 待发布(见下方横评) |
|
||||
| **openwork** | 把「能力」而不是「桌面」做成产品 | 待发布(见下方横评) |
|
||||
| **orca** | 兼容面最广的并行 agent 编排外壳 | 待发布(见下方横评) |
|
||||
|
||||
**跨项目文章**
|
||||
|
||||
- [四个 Agent 工作台的路线分野:把「多引擎中立」拆开看](https://blog.apescale.com/?post=99) — 2026-09-01
|
||||
|
||||
> 待补深度文:maka、openwork、orca。当前可先读上方跨项目文章,其中已包含这些项目的完整分维度对比。
|
||||
<!-- INDEX:END -->
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ ecosystem:
|
||||
- "2026-08-31 发布 v0.50.12,为最近一次 push"
|
||||
|
||||
risk:
|
||||
maturity_level: "beta"
|
||||
license_risk: "L3 级源码可用协议。3 人以上团队无论用途都需书面授权,且明确禁止未经授权的 SaaS / 托管 / 白标 / 市场分发。这意味着:企业落地前必须先谈授权,而源码镜像也只能是私有的"
|
||||
vendor_lockin: "引擎层是中立了,但工作台层本身是闭源商业产品。项目资产沉淀在 iPolloWork 的项目模型里,退出成本高于纯 CLI 方案;一旦停止授权,已建的工作流难以平移"
|
||||
claim_vs_reality:
|
||||
@@ -144,10 +145,10 @@ risk:
|
||||
maturity: beta
|
||||
|
||||
chart:
|
||||
form_depth: 9
|
||||
engine_neutral: 9
|
||||
local_first_score: 9
|
||||
deliverable_depth: 9
|
||||
form_depth: 8 # 桌面工作台整合填报/审核/查询,形态完整
|
||||
engine_neutral: 7 # 宣称三引擎中立,实际深度不对等(OpenCode 原生 / DSH preview / Codex 仅 MCP)
|
||||
local_first_score: 7 # 本地桌面运行,但依赖云端模型与配置服务
|
||||
deliverable_depth: 7 # 可编辑产出物,不止代码
|
||||
|
||||
verdict:
|
||||
one_liner: "把多个 Agent 引擎收进同一个可编辑工作台的野心之作——方向踩在点上,但三种引擎的接入深度并不齐整,且许可证决定了它暂时只适合个人或极小团队试用"
|
||||
@@ -155,13 +156,15 @@ verdict:
|
||||
who_should_not: "3 人以上需要正式落地的团队(许可证要求书面授权)、期待 Codex 原生级集成的用户(目前只有 MCP 控制面)、以及追求稳定的生产环境(v0.50.x、一天多版、DSH 集成未进稳定版)"
|
||||
|
||||
mirror:
|
||||
gitea_repo: "gitinbox/iPolloWork"
|
||||
gitea_repo: gitinbox/iPolloWork
|
||||
visibility: private
|
||||
upstream: "https://github.com/Devin-AXIS/iPolloWork"
|
||||
last_sync: ""
|
||||
sync_note: "L3 source-available,镜像强制私有;仓库 591 MB,全量裸仓镜像"
|
||||
last_sync: 2026-09-01
|
||||
mode: source-only
|
||||
sync_note: L3 source-available 强制私有。全量裸仓 69.9 MB 超过 Gitea 前置 nginx 约 50MB 的 client_max_body_size,推送被 HTTP 413 拒绝;已降级为源码快照(14.1 MB,剔除 png/gif/mp4/字体等非源码素材约 69MB)。完整保真快照保留在本地 .mirrors/iPolloWork.git(70MB)。服务器放开 nginx 上限后可推送完整版。
|
||||
|
||||
mode: source-only
|
||||
article:
|
||||
emlog_id: 97
|
||||
published_at: ""
|
||||
url: ""
|
||||
published_at: "2026-09-01 15:11:57"
|
||||
url: "https://blog.apescale.com/?post=97"
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
# maka · 项目档案
|
||||
# 生成于 2026-09-01 · 由 scripts/new_project.py 从 schema 生成
|
||||
# 人工补完重点:business / scenario / tech / risk.claim_vs_reality / verdict
|
||||
# 字段规范见 schema/project.schema.yaml
|
||||
|
||||
id: maka
|
||||
name: "maka"
|
||||
repo: "apache/maka"
|
||||
homepage: ""
|
||||
added_at: "2026-09-01"
|
||||
status: active
|
||||
identity:
|
||||
tagline: "Apache Maka (Incubating) is a local-first AI agent workspace. Model messages, tool calls, tool results, permission decisions, and termination events are recorded as an append-only log."
|
||||
vendor: "Apache 软件基金会(Incubating)"
|
||||
vendor_type: "foundation"
|
||||
region: "国际(ASF 孵化)"
|
||||
first_seen: "2026-05-27"
|
||||
business:
|
||||
|
||||
positioning: |-
|
||||
站在「执行记录层」:不追功能广度,把「agent 干过什么必须可复现、
|
||||
可审计、可评测」做成地基。同时是四个里唯一自带评测框架的项目。
|
||||
target_users:
|
||||
- 对可审计性与数据主权有硬要求的团队
|
||||
- 需要横向评测不同 agent / 模型的研究者与工程团队
|
||||
- 愿意用功能丰富度换确定性的自用开发者
|
||||
monetization: |-
|
||||
无。Apache 孵化项目,无商业实体、无双许可、无付费层。
|
||||
license:
|
||||
spdx: "Apache-2.0"
|
||||
name: "Apache License 2.0"
|
||||
openness: L1
|
||||
redistributable: true
|
||||
key_restrictions:
|
||||
- 含明确的专利授权与反诉终止条款(Apache-2.0 固有)
|
||||
- 修改需声明,NOTICE 文件须随分发保留
|
||||
- 不授予商标使用权(Apache / ASF 商标政策单列)
|
||||
moat: |-
|
||||
执行记录模型 + 可复现评测框架 + ASF 治理背书。
|
||||
三者都需要长期投入,不能靠快速迭代速成。
|
||||
competitive_risk: |-
|
||||
最容易被上游引擎把「记录与评测」原生化——
|
||||
一旦 Codex / Claude Code 自己把 event log 与 eval 做进产品,
|
||||
Maka 的地基优势会被稀释成一层「合规装饰」。
|
||||
|
||||
scenario:
|
||||
|
||||
primary:
|
||||
- 本地优先的 agent 工作区(数据与执行记录不出机器)
|
||||
- 可复现的 agent 评测实验(multi-arm experiments)
|
||||
- 崩溃恢复与中断续跑
|
||||
typical_use_cases:
|
||||
- 在保密项目里用本地模型跑 agent,所有 tool call 与权限决策落 append-only 日志备查
|
||||
- 用 maka eval run <spec> 对同一任务跑 task × repetition × subject 的多臂实验,横向比较得分/成本/耗时
|
||||
- 会话崩了以后从执行记录恢复,并从任一 Turn 分叉出新会话继续
|
||||
industries:
|
||||
- 软件研发
|
||||
- 受监管行业(金融、政务等有审计要求的场景)
|
||||
- AI 评测与研究
|
||||
interaction_mode:
|
||||
- 桌面应用
|
||||
- TUI / CLI
|
||||
- 评测框架
|
||||
human_in_the_loop: |-
|
||||
强:工具越过沙箱边界必须审批;运行可中止;失败被分类。
|
||||
deliverables:
|
||||
- code
|
||||
- artifact
|
||||
- eval-report
|
||||
- execution-record
|
||||
|
||||
tech:
|
||||
|
||||
form_factor: 桌面应用 + TUI/CLI + 评测框架,三者共用同一 Runtime Host
|
||||
stack:
|
||||
- Electron
|
||||
- React
|
||||
- TypeScript
|
||||
- Node.js
|
||||
- SQLite
|
||||
- Rust(peer mesh 的 native addon)
|
||||
- ripgrep(Runtime 的 Grep 工具依赖)
|
||||
engine_abstraction:
|
||||
supported:
|
||||
- Maka 自带 Runtime(AgentRun)
|
||||
mechanism: |-
|
||||
不做外挂接入——自带完整 agent runtime,
|
||||
Desktop / TUI / CLI / Eval 全部经同一个 Runtime Host 执行。
|
||||
对外部 agent 只在评测环节通过 external subject adapter 对接,
|
||||
不做执行层兼容。
|
||||
abstraction_layer: Runtime Host(单一所有者的生命周期 + 协议 + 客户端引导)
|
||||
depth_notes: |-
|
||||
与另外三家正好相反:Maka 对**自己的**引擎深度是满分
|
||||
(一个 Runtime Host 统管所有入口),对**外部**引擎深度是零
|
||||
(只在 eval 里当被测对象)。
|
||||
它压根不参与「多引擎中立」这条赛道的竞争——这是定位选择而非能力缺陷,
|
||||
但读者必须知道它和 Orca / OpenWork / iPolloWork 不是同一类选手。
|
||||
extension_system:
|
||||
skills: catalog skills 存在,但可选且**默认关闭**
|
||||
plugins: 无独立插件市场
|
||||
mcp: packages/mcp 提供 provider-neutral 的 MCP 客户端集成
|
||||
model_binding: |-
|
||||
完全 BYO:云 API、本地模型或兼容网关,
|
||||
不捆绑任何共享账号(README 明确 "You bring the model")。
|
||||
local_first: true
|
||||
multi_agent: |-
|
||||
Graph 模式,实现算子跑在独立 git worktree
|
||||
(要求源项目是干净 worktree)。
|
||||
permission_sandbox: |-
|
||||
沙箱边界明确:写文件 / 跑 shell 的工具必须先过边界;
|
||||
运行可中止;失败被分类;支持崩溃恢复与可选续跑。
|
||||
observability: |-
|
||||
最强项。append-only 执行记录:model 消息、tool call、tool result、
|
||||
turn 结束方式全部落盘。
|
||||
上下文压缩只省略下次 prompt 里的旧 tool 输出,不删已存证据——
|
||||
README 原话 "Shorter context is not deleted history"。
|
||||
|
||||
ecosystem:
|
||||
|
||||
community: |-
|
||||
ASF 孵化治理:有 DISCLAIMER-WIP、ASF_SOURCE_RELEASE 候选契约、
|
||||
CONTRIBUTING、SECURITY 等完整治理文件。中文文档齐全,
|
||||
另挂 DeepWiki 第三方 AI 生成文档。
|
||||
standards:
|
||||
- Apache 软件基金会(Incubator)
|
||||
notable_events:
|
||||
- 2026-05-27 建仓(进入 ASF 孵化)
|
||||
- 截至 2026-09-01 尚未产出任何 Apache 正式 release,对外只有 Desktop Nightly
|
||||
- 2026-09-01 达 4372 star,版本 v0.2.0-dev.9.20260831
|
||||
|
||||
metrics:
|
||||
stars: 4373
|
||||
forks: 406
|
||||
open_issues: 398
|
||||
subscribers: 23
|
||||
created_at: 2026-05-27
|
||||
pushed_at: 2026-09-01
|
||||
size_kb: 102390
|
||||
languages: {TypeScript: 91.7, JavaScript: 4.5, Rust: 1.7, CSS: 1.3, Python: 0.5, PowerShell: 0.2}
|
||||
latest_release: v0.2.0-dev.9.20260831
|
||||
release_cadence: 一天多版
|
||||
data_date: 2026-09-01
|
||||
stale: false
|
||||
risk:
|
||||
maturity_level: "孵化期"
|
||||
|
||||
license_risk: |-
|
||||
Apache-2.0,四个里最干净:可自由镜像与再分发,且自带专利授权条款,
|
||||
无任何商业条款陷阱。
|
||||
vendor_lockin: |-
|
||||
锁定点最低:无商业实体、无付费层、数据落在本地 SQLite、
|
||||
执行记录是自有格式但结构公开。退出成本主要是迁移历史会话记录。
|
||||
claim_vs_reality:
|
||||
- claim: |-
|
||||
Apache 品牌 = 成熟稳定、可用于生产
|
||||
actual: |-
|
||||
孵化状态恰恰是「尚未被 ASF 完全背书」的标记,README 自带官方免责声明。
|
||||
且截至 2026-09-01 **没有任何 Apache 正式 release**,
|
||||
对外分发的只有 Desktop Nightly,README 明确标注
|
||||
「not an ASF release, not intended for production use」。
|
||||
平台支持也最窄:macOS arm64 是唯一正式档,
|
||||
Windows 为未签名预览,Linux 标注 soon。
|
||||
source: README 的 NOTE / IMPORTANT 段、Releases and downloads 段、平台 badge
|
||||
- claim: |-
|
||||
「Your machine, your data」本地优先、数据主权
|
||||
actual: |-
|
||||
数据确实不出机器,但 **API key 以明文文件存放**——
|
||||
README 原文承认 credential-vault.json 是
|
||||
「local plaintext file, readable only by your OS account」。
|
||||
防护依赖操作系统账户权限,而非加密或系统钥匙串。
|
||||
renderer 进程拿不到,但同机任一以你身份运行的进程都能读到。
|
||||
source: README「Local data and recovery」段
|
||||
- claim: |-
|
||||
崩溃恢复与可选续跑
|
||||
actual: |-
|
||||
续跑**默认关闭**,需手动设 MAKA_RUNTIME_SAFE_BOUNDARY_RESUME=1,
|
||||
且 README 明确警告该调用会打模型、消耗 token。
|
||||
另存在**升级断代**:旧版 JSONL transcripts 与 Electron safeStorage
|
||||
凭据不会被导入,升级后的工作区可能出现空线程,凭据必须重新录入。
|
||||
source: README「Local data and recovery」段
|
||||
- claim: |-
|
||||
(无明确宣称,但易被期待为开箱即用的完整工作台)
|
||||
actual: |-
|
||||
内置工具只有 Read / Write / Edit / Bash / Glob / Grep 六个;
|
||||
Computer Use 与 catalog skills 属可选且默认关闭;
|
||||
IM bot(聊天应用接入)标注 experimental。
|
||||
功能面是四个里最保守的——取舍很明确:用广度换确定性。
|
||||
source: README「Current capabilities」段
|
||||
- claim: |-
|
||||
支持 Graph 多算子并行
|
||||
actual: |-
|
||||
Graph 的实现算子跑在独立 git worktree,且**要求源项目是干净 worktree**——
|
||||
有未提交改动就用不了。相比 Orca 的并行 worktree(产品级封装),
|
||||
Maka 的 Graph 更接近实验性能力。
|
||||
source: README「Terminal entry points」段
|
||||
maturity: |-
|
||||
治理成熟度最高(ASF 流程 + 评测框架 + 文档契约),
|
||||
产品成熟度最低(无正式 release、平台只支持 arm64 macOS、功能默认保守)。
|
||||
属于「地基打得最实,房子盖得最慢」。
|
||||
|
||||
chart:
|
||||
|
||||
form_depth: 6 # 桌面 + TUI + CLI + Eval 统一于 Runtime Host,但平台窄
|
||||
engine_neutral: 1 # 只跑自己的 Runtime,不参与多引擎竞争
|
||||
local_first_score: 10 # 完全本地,数据不出机器
|
||||
deliverable_depth: 6 # 代码 + artifact + 评测报告 + 执行记录
|
||||
|
||||
verdict:
|
||||
|
||||
one_liner: 四个里唯一把「执行记录」当地基的项目——治理与可复现性最强,产品成熟度最弱。
|
||||
who_should_use: |-
|
||||
对审计、数据主权、可复现评测有硬要求的团队;
|
||||
做 agent 横向对比研究的工程团队。
|
||||
who_should_not: |-
|
||||
要开箱即用、要 Windows / Linux、要丰富第三方集成的人;
|
||||
以为 ASF 品牌等于生产可用的决策者。
|
||||
|
||||
mirror:
|
||||
gitea_repo: gitinbox/maka
|
||||
visibility: public
|
||||
upstream: "https://github.com/apache/maka"
|
||||
last_sync: 2026-09-01
|
||||
sync_note: "depth=1 浅仓被 Gitea 拒绝(shallow update not allowed),脚本自动 --unshallow 补齐完整历史后达 147 MB,超过 nginx 约 50 MB 的 client_max_body_size,遂降级为源码快照(12 MB,剔除 png/gif/mp4/字体等非源码素材)。完整保真裸仓保留在本地 .mirrors/maka.git(163 MB)。服务器放开 nginx 上限后可推送完整版。"
|
||||
mode: source-only
|
||||
article:
|
||||
emlog_id: 0 # emlog 文章 ID,发布后回写
|
||||
published_at: ""
|
||||
url: ""
|
||||
@@ -0,0 +1,226 @@
|
||||
# openwork · 项目档案
|
||||
# 生成于 2026-09-01 · 由 scripts/new_project.py 从 schema 生成
|
||||
# 人工补完重点:business / scenario / tech / risk.claim_vs_reality / verdict
|
||||
# 字段规范见 schema/project.schema.yaml
|
||||
|
||||
id: openwork
|
||||
name: "openwork"
|
||||
repo: "different-ai/openwork"
|
||||
homepage: "https://openworklabs.com"
|
||||
added_at: "2026-09-01"
|
||||
status: active
|
||||
identity:
|
||||
tagline: "The open-source alternative to Claude Cowork (powered by opencode)"
|
||||
vendor: "Different AI, Inc."
|
||||
vendor_type: "vendor"
|
||||
region: "美国(YC 背书)"
|
||||
first_seen: "2026-01-14"
|
||||
business:
|
||||
|
||||
positioning: |-
|
||||
站在「能力分发层」,不争桌面入口。核心资产是 OpenWork MCP 与 Den 控制面——
|
||||
把 skills / MCP / 连接服务做成可跨工具、跨人、跨机器复用的组织级能力包。
|
||||
target_users:
|
||||
- 已在使用 Codex / Claude Code / Cursor 且不想换工具的开发者
|
||||
- 需要统一管控 agent 能力的中大型组织
|
||||
- 想把自己搭好的工作流分发给同事或朋友的团队
|
||||
monetization: |-
|
||||
Den 订阅(组织控制面)。免费额度写得很明确:
|
||||
5 人以下组织的非 Enterprise 功能全免、任意规模 30 天评估、开发测试永久免费。
|
||||
license:
|
||||
spdx: "NOASSERTION"
|
||||
name: "目录切分许可(GitLab 式):ee/ 之外为 MIT;ee/ 为 OpenWork EE License"
|
||||
openness: L3
|
||||
redistributable: false
|
||||
key_restrictions:
|
||||
- ee/ 目录(Den 控制面、MCP 网关、推理)生产使用需有效订阅;5 人以下组织在排除 Enterprise 功能后免费
|
||||
- 每个 ee/ 版本公开发布满两年后,自动追加 MIT 授权(Grant of Future License)
|
||||
- 客户端侧资源(image / font / CSS / 编译产出的 JS)始终为 MIT
|
||||
- 不得移除版权与许可声明;不授予商标使用权
|
||||
- 更早版本仍受原 FSL-1.1-MIT 约束,不自动升级到新许可
|
||||
moat: |-
|
||||
组织侧的能力分发与治理(谁能用什么模型、谁能拿到什么 skill),
|
||||
以及「一次创建、处处复用」带来的工作流网络效应。
|
||||
competitive_risk: |-
|
||||
最容易被 Anthropic / OpenAI 官方的 skills 市场与组织管理后台正面取代——
|
||||
OpenWork 的价值恰恰建立在「官方还没做好跨工具能力分发」这个时间窗口上。
|
||||
|
||||
scenario:
|
||||
|
||||
primary:
|
||||
- 把一套 skills + MCP + Google Workspace / Microsoft 365 连接,同时挂给 Codex、Claude Code、Cursor
|
||||
- 组织统一发布能力、配置模型配额与访问权限
|
||||
- 桌面端作为专注工作区(可选,非必需)
|
||||
typical_use_cases:
|
||||
- 团队写好一套内部 skill,通过 Den 发布到组织,成员在各自惯用的 agent 里调 search_capabilities 直接用
|
||||
- 管理员限制部分成员只能使用指定 model provider,并统一配置共享连接
|
||||
- 个人把工作流分享给朋友,对方装一个 MCP 就能用
|
||||
industries:
|
||||
- 软件研发
|
||||
- 企业 IT 与知识工作流
|
||||
interaction_mode:
|
||||
- MCP 远程服务
|
||||
- 桌面应用(可选)
|
||||
- 浏览器登录授权
|
||||
human_in_the_loop: |-
|
||||
能力调用需人工在浏览器登录并选择组织;企业侧由管理员通过策略管控。
|
||||
deliverables:
|
||||
- workflow
|
||||
- skill-package
|
||||
- capability
|
||||
|
||||
tech:
|
||||
|
||||
form_factor: MCP 远程服务 + 可选桌面应用 + Web 控制面
|
||||
stack:
|
||||
- Electron
|
||||
- React
|
||||
- TypeScript
|
||||
- Node.js 24
|
||||
- pnpm
|
||||
- MCP
|
||||
engine_abstraction:
|
||||
supported:
|
||||
- Codex
|
||||
- Claude Code
|
||||
- Cursor
|
||||
- OpenCode
|
||||
- ChatGPT
|
||||
- 任意 MCP client
|
||||
mechanism: |-
|
||||
「反向注入」:不做引擎抽象层,而是把自己做成 MCP server 让各引擎来连。
|
||||
对外只暴露两个工具——search_capabilities 与 execute_capability。
|
||||
abstraction_layer: OpenWork MCP(能力面,不是执行面)
|
||||
depth_notes: |-
|
||||
接入「对等且都浅」:所有引擎都以同一个 MCP 客户端身份接入,
|
||||
拿到的是能力调用,不是执行控制——OpenWork 不接管 agent 的执行循环、
|
||||
不负责上下文管理、不做权限回调。
|
||||
真正深度绑定的是 OpenCode:仓库描述即写 powered by opencode,
|
||||
桌面端属 OpenCode 生态,其余引擎只是 MCP 外挂。
|
||||
这与 iPolloWork 的「Codex 仅 MCP 控制面」是同一个问题的两种解法:
|
||||
iPolloWork 把 MCP 当补丁(主体是桌面),OpenWork 把 MCP 当主体(桌面是可选)。
|
||||
extension_system:
|
||||
skills: |-
|
||||
一等公民:可发布、可分配到组织/团队/个人;支持导入 Agent Plugins
|
||||
与 Anthropic 兼容插件并转为 OpenWork MCP 能力。
|
||||
plugins: 通过 marketplace 发布与分配
|
||||
mcp: |-
|
||||
自身即以 MCP server 形态对外提供服务(https://api.openworklabs.com/mcp/agent),
|
||||
同时也消费下游 MCP 连接。
|
||||
model_binding: Den 侧可统一配置与配额控制 model provider;桌面 app 本身 BYO
|
||||
local_first: true
|
||||
multi_agent: 未强调多 agent 编排,重心是能力的共享与治理
|
||||
permission_sandbox: |-
|
||||
组织级管控:桌面策略下发、限制本地模型访问、控制可用 app 版本、
|
||||
成员与团队的访问控制。
|
||||
observability: |-
|
||||
Den 提供推理配额管控与访问审计;
|
||||
桌面 app 侧未见 agent 执行轨迹的记录设计——它观测的是「谁用了什么能力」,
|
||||
不是「agent 怎么把事做完的」。
|
||||
|
||||
ecosystem:
|
||||
|
||||
community: |-
|
||||
YC 背书,23k star。贡献门槛是四个里最高的:
|
||||
强制 DCO sign-off(每个 commit 都要 Signed-off-by),
|
||||
ee/ 目录的改动还需额外 CLA。
|
||||
standards:
|
||||
- MCP
|
||||
notable_events:
|
||||
- 2026-01-14 建仓
|
||||
- 早期版本采用 FSL-1.1-MIT,后改为 MIT + EE 目录切分许可
|
||||
- ee/ 采用两年自动转 MIT 的 Grant of Future License 机制
|
||||
- 2026-09-01 达 23.2k star、版本 v0.18.40
|
||||
|
||||
metrics:
|
||||
stars: 23253
|
||||
forks: 2307
|
||||
open_issues: 414
|
||||
subscribers: 89
|
||||
created_at: 2026-01-14
|
||||
pushed_at: 2026-09-01
|
||||
size_kb: 298398
|
||||
languages: {TypeScript: 89.9, JavaScript: 6.7, MDX: 1.2, CSS: 0.9, Shell: 0.6, Swift: 0.5}
|
||||
latest_release: v0.18.40
|
||||
release_cadence: 约 1 天/版本
|
||||
data_date: 2026-09-01
|
||||
stale: false
|
||||
risk:
|
||||
maturity_level: "0.x 活跃"
|
||||
|
||||
license_risk: |-
|
||||
整体 SPDX 为 NOASSERTION,但**排除 ee/ 目录后是纯 MIT,可以公开镜像**。
|
||||
若完整镜像(含 ee/)则必须私有——EE License 明确要求生产使用需订阅,
|
||||
且禁止未经授权的复制与再分发。
|
||||
对实操的影响很小:5 人以下组织免费、30 天评估免费、开发测试免费,
|
||||
个人与小团队几乎不会触发付费条件。
|
||||
vendor_lockin: |-
|
||||
锁定在 Den 控制面与能力包格式。一旦组织把内部 skills 都发布到 Den,
|
||||
迁移成本取决于是否有导出路径——README 未说明。
|
||||
claim_vs_reality:
|
||||
- claim: |-
|
||||
「Claude Cowork 的开源替代品」
|
||||
actual: |-
|
||||
形态完全不同。Cowork 是桌面操控型 agent(直接操作你的电脑完成工作);
|
||||
OpenWork 是能力分发型 MCP 层,桌面 app 被明确定位为可选——
|
||||
README 原文「The desktop app is there when you want a dedicated
|
||||
workspace, but it is not required」。
|
||||
它替代的是 Cowork 唤起的「能力复用」诉求,不是 Cowork 本身。
|
||||
source: README 开篇与「Use OpenWork from any agent」段
|
||||
- claim: |-
|
||||
跨 Codex / Claude Code / Cursor / OpenCode 多引擎通用
|
||||
actual: |-
|
||||
对 OpenCode 是原生的(仓库描述 powered by opencode,桌面端属其生态);
|
||||
对其余引擎只通过自己暴露的两个 MCP 工具对接。
|
||||
这是能力调用面,不是引擎执行面——OpenWork 不接管这些引擎的执行循环、
|
||||
上下文与权限。对外宣称的「跨引擎」与内部实际的主从关系存在落差。
|
||||
source: README「It exposes two tools」段;仓库 description「powered by opencode」
|
||||
- claim: |-
|
||||
free, open-source(免费开源)
|
||||
actual: |-
|
||||
需要分目录看:ee/ 之外确实 MIT 且免费;但 ee/(Den 组织控制面、
|
||||
MCP 网关、推理)属 source-available,生产使用需付费订阅,
|
||||
只有 5 人以下组织、30 天评估、开发测试三种情形豁免。
|
||||
个人免费,团队与企业要付费。
|
||||
source: README Licensing 段 + ee/LICENSE 原文 72 行
|
||||
- claim: |-
|
||||
(无明确宣称,但 SPDX 为 NOASSERTION,易被误读为「不够开源」)
|
||||
actual: |-
|
||||
恰恰相反——它是四个项目里工程治理最规范的:强制 DCO sign-off、
|
||||
ee/ 需额外 CLA、evals/ 目录放可执行测试规格并要求 PR 附测试证据、
|
||||
worlds/ 放声明式的开发与测试环境定义。
|
||||
NOASSERTION 只因根目录 LICENSE 是混合声明文件,不代表开放度低。
|
||||
source: README「Getting started (contributors)」与「Sending a pull request」段
|
||||
maturity: |-
|
||||
工程成熟度最高:有可执行测试规格(evals/)、声明式环境(worlds/)、
|
||||
明确的贡献契约与许可边界。但仍处 0.x 主版本(v0.18.40),
|
||||
API 与数据格式可能变动。
|
||||
|
||||
chart:
|
||||
|
||||
form_depth: 5 # 桌面只是可选壳,主体是 MCP 能力层
|
||||
engine_neutral: 6 # 接得住 4+ 引擎,但都只在能力面
|
||||
local_first_score: 6 # 可本地跑,但核心服务是云端 MCP
|
||||
deliverable_depth: 5 # 交付工作流与能力包,不止代码
|
||||
|
||||
verdict:
|
||||
|
||||
one_liner: 把「能力」而不是「桌面」做成产品——工程治理最好的一个,但它和自称要替代的东西不是一个物种。
|
||||
who_should_use: |-
|
||||
已固定在某款 agent 上、只想让团队能力跨工具复用的开发者;
|
||||
需要统一管控 agent 能力与模型配额的组织 IT。
|
||||
who_should_not: |-
|
||||
想要一个开箱即用的桌面 agent 工作台的人;
|
||||
以为拿到的是完整 Cowork 平替的人。
|
||||
|
||||
mirror:
|
||||
gitea_repo: "gitinbox/openwork"
|
||||
visibility: public
|
||||
upstream: "https://github.com/different-ai/openwork"
|
||||
last_sync: ""
|
||||
mode: pending
|
||||
sync_note: "暂未镜像。仓库 291 MB,按本机到 GitHub 实测 50 KB/s 计算需 1.6 小时以上,且 git clone 无断点续传。许可证为目录切分(ee/ 之外 MIT,ee/ 为 EE License)——若后续镜像,排除 ee/ 后为纯 MIT,可公开;完整镜像则必须私有。"
|
||||
article:
|
||||
emlog_id: 0 # emlog 文章 ID,发布后回写
|
||||
published_at: ""
|
||||
url: ""
|
||||
@@ -0,0 +1,238 @@
|
||||
# orca · 项目档案
|
||||
# 生成于 2026-09-01 · 由 scripts/new_project.py 从 schema 生成
|
||||
# 人工补完重点:business / scenario / tech / risk.claim_vs_reality / verdict
|
||||
# 字段规范见 schema/project.schema.yaml
|
||||
|
||||
id: orca
|
||||
name: "orca"
|
||||
repo: "stablyai/orca"
|
||||
homepage: "https://onOrca.dev"
|
||||
added_at: "2026-09-01"
|
||||
status: active
|
||||
identity:
|
||||
tagline: "Orca is the ADE for working with a fleet of parallel agents. Run any coding agent with your own subscription. Available on desktop, mobile and VPS."
|
||||
vendor: "Stably AI(stablyai)"
|
||||
vendor_type: "vendor"
|
||||
region: "美国(据 Discord / App Store 与社群信息推断,未公开确认)"
|
||||
first_seen: "2026-03-17"
|
||||
business:
|
||||
|
||||
positioning: |-
|
||||
站在「执行编排层」:不造引擎、不卖模型,只做并行 agent 的调度与可观测外壳。
|
||||
上游是各家 CLI agent,下游是开发者的本机/远程 worktree。
|
||||
target_users:
|
||||
- 自称 100x builders 的重度个人开发者
|
||||
- 需要同时试多种实现方案再择优合并的小团队
|
||||
- 已在用 Codex / Claude Code / OpenCode 并想并行跑的人
|
||||
monetization: |-
|
||||
未明示,且这是它最大的商业不确定性。MIT 全开源、无企业版、无订阅层,
|
||||
README 明确不提供模型也不代买订阅。收入路径尚未公开。
|
||||
license:
|
||||
spdx: "MIT"
|
||||
name: "MIT License"
|
||||
openness: L1
|
||||
redistributable: true
|
||||
key_restrictions:
|
||||
- 无实质性使用限制
|
||||
- MIT 不含商标许可,Orca 名称与 Logo 未单独授权
|
||||
moat: |-
|
||||
并行 worktree + 自研 WebGL 终端 + 移动端配对 + Design Mode 的组合体验,
|
||||
以及已铺开的 30+ agent 兼容面。功能广度本身构成短期壁垒。
|
||||
competitive_risk: |-
|
||||
最容易被 Anthropic / OpenAI 官方桌面端直接吃掉——因为 Orca 既不拥有引擎
|
||||
也不拥有模型,只拥有外壳。一旦官方 IDE 原生支持并行 worktree,差异化立刻归零。
|
||||
|
||||
scenario:
|
||||
|
||||
primary:
|
||||
- 同一任务并行分派给多个 agent 各自实现,人工比对后合并优胜解
|
||||
- 在远程大机上跑 agent(SSH worktree)
|
||||
- 移动端监控 agent 并追加指令
|
||||
typical_use_cases:
|
||||
- 一条 prompt 扇出到 5 个 agent,各自在独立 git worktree 里改,最后 diff 对比选一个 merge
|
||||
- 开 Design Mode 点页面元素,把 HTML/CSS/裁剪截图直接塞进 agent 的提示词
|
||||
- 通勤路上用手机查看刚跑完的 agent 结果并追加一句 follow-up
|
||||
industries:
|
||||
- 软件研发
|
||||
- 独立开发者 / 外包交付
|
||||
interaction_mode:
|
||||
- 桌面应用
|
||||
- 移动端伴随
|
||||
- CLI
|
||||
- SSH 远程
|
||||
human_in_the_loop: |-
|
||||
强人在环:需人工比并结果、批注 diff、批准合并。
|
||||
但 5000 个自开 issue 说明产品自身也在被 AI 自动改进。
|
||||
deliverables:
|
||||
- code
|
||||
- diff
|
||||
- review-comment
|
||||
|
||||
tech:
|
||||
|
||||
form_factor: 桌面应用(含移动端伴随 + CLI)
|
||||
stack:
|
||||
- Electron
|
||||
- React
|
||||
- TypeScript
|
||||
- Node.js
|
||||
- WebGL 终端
|
||||
- Swift(iOS 伴随端)
|
||||
engine_abstraction:
|
||||
supported:
|
||||
- Codex
|
||||
- Claude Code
|
||||
- OpenCode
|
||||
- Pi
|
||||
- Grok
|
||||
- Cursor
|
||||
- GitHub Copilot
|
||||
- MiMo Code
|
||||
- Amp
|
||||
- Antigravity
|
||||
- Hermes Agent
|
||||
- Devin
|
||||
- Goose
|
||||
- Auggie
|
||||
- Cline
|
||||
- Continue
|
||||
- Droid
|
||||
- Kilocode
|
||||
- Kimi
|
||||
- Kiro
|
||||
- Mistral Vibe
|
||||
- Qwen Code
|
||||
- Rovo Dev
|
||||
- oh-my-pi
|
||||
mechanism: |-
|
||||
「只要在终端里能跑」即为兼容——通过 PTY 拉起任意 CLI agent,
|
||||
不做协议级抽象,不解析结构化 tool call。
|
||||
abstraction_layer: 无统一抽象层;实际是终端 PTY + 约定式输出解析
|
||||
depth_notes: |-
|
||||
接入深度「完全对等,但对等在低处」:30+ 引擎共享同一套 PTY 外壳,
|
||||
因此没有任何一家能拿到原生能力——拿不到结构化 tool call、
|
||||
拿不到统一权限回调、拿不到一致口径的 token 统计。
|
||||
广度最大,深度最浅。对照 iPolloWork 为 OpenCode 做的原生
|
||||
Engine Protocol adapter,Orca 与任一单引擎的耦合都明显更浅。
|
||||
extension_system:
|
||||
skills: README 未提及独立 skills 体系
|
||||
plugins: 无插件机制,能力依赖各 agent 自身生态
|
||||
mcp: 未作为一等公民提及
|
||||
model_binding: 完全 BYO——用户自带各 agent 的订阅与额度
|
||||
local_first: true
|
||||
multi_agent: |-
|
||||
并行 worktree 编排:一条 prompt 扇出到 N 个独立 git worktree,
|
||||
比对结果后合并优胜解;另有任务页与 CLI 编排能力。
|
||||
permission_sandbox: |-
|
||||
README 未强调沙箱边界。Computer Use(操控桌面应用)与 SSH 远程执行
|
||||
均属高权限面,且未见统一的越界审批机制描述。
|
||||
observability: |-
|
||||
终端分屏 + AI diff 批注 + 通知与未读态 + 用量与限流追踪 + 账号热切换。
|
||||
观测的是「agent 的输出与结果」,不是「agent 的内部执行轨迹」。
|
||||
|
||||
ecosystem:
|
||||
|
||||
community: |-
|
||||
Discord + X(@orca_build) + 微信社群(已开到第 8 群),中文市场渗透明显。
|
||||
但 5000 个 open issue 中绝大多数由主力开发者自开,社区 triage 尚未成型。
|
||||
standards: []
|
||||
notable_events:
|
||||
- 2026-03-17 建仓
|
||||
- 2026-09-01 达 58.7k star、5000 open issue、版本 v1.4.194
|
||||
|
||||
metrics:
|
||||
stars: 58771
|
||||
forks: 3987
|
||||
open_issues: 5000
|
||||
subscribers: 121
|
||||
created_at: 2026-03-17
|
||||
pushed_at: 2026-09-01
|
||||
size_kb: 687575
|
||||
languages: {TypeScript: 97.0, JavaScript: 2.3, Swift: 0.2, CSS: 0.2, HTML: 0.1, PowerShell: 0.1}
|
||||
latest_release: v1.4.194
|
||||
release_cadence: 约 1 天/版本
|
||||
data_date: 2026-09-01
|
||||
stale: false
|
||||
risk:
|
||||
maturity_level: "原型期"
|
||||
|
||||
license_risk: |-
|
||||
MIT,无许可证风险。但「商业模式的空白」本身就是风险:
|
||||
一旦融资或转型,存在事后变更许可的可能(参考 Redis / Terraform 先例)。
|
||||
vendor_lockin: |-
|
||||
锁定点很弱——这既是优点也是缺点。用户锁的是 worktree 布局与 UI 习惯,
|
||||
不是数据格式,迁移成本低;但反过来用户粘性也低。
|
||||
claim_vs_reality:
|
||||
- claim: |-
|
||||
5000 个 open issue 反映社区问题积压与项目质量压力
|
||||
actual: |-
|
||||
抽样最近 100 条:全部创建于 2026-09-01 同一天,94% 无任何标签,
|
||||
标题为 fix(ssh) / refactor / fix(task-page) 等内部任务口吻;
|
||||
提交者 TOP3 为 nwparker(51)、OrcaWin(13)、klaidliadon(12)。
|
||||
实为「团队自开的任务待办板」,且带明显 AI 批量生成痕迹
|
||||
(如 OrcaWin 提交的「pty.spawn can leak a running agent process
|
||||
unboundedly: host holds the door open」)。
|
||||
它不是社区投诉,也不构成 triage 压力——但同样说明这 5000 条
|
||||
从未被当作对外承诺来管理。
|
||||
source: GitHub API /repos/stablyai/orca/issues?state=open,2026-09-01 抽样 100 条
|
||||
- claim: |-
|
||||
「100x builders 的 AI Orchestrator」,面向多人协作的成熟开发环境
|
||||
actual: |-
|
||||
巴士系数为 1:最近 100 次提交中 nwparker 独占 79 次,
|
||||
第二名 AmethystLiang 仅 9 次。功能面由单人 + AI 高强度驱动,
|
||||
而非团队协作成果。
|
||||
source: GitHub API /repos/stablyai/orca/commits?per_page=100,2026-09-01
|
||||
- claim: |-
|
||||
支持 30+ 种 agent,生态最开放
|
||||
actual: |-
|
||||
兼容判据是「能在终端里跑」,靠 PTY 拉起而非协议对接。
|
||||
广度最大但深度最浅——所有引擎都拿不到结构化 tool call
|
||||
与统一的权限回调。与 iPolloWork 为 OpenCode 做原生
|
||||
Engine Protocol adapter 相比,Orca 与任一单引擎的耦合都更浅。
|
||||
source: README「Works with any CLI agent — if it runs in a terminal, it runs in Orca」
|
||||
- claim: |-
|
||||
MIT 全开源,可放心用于生产
|
||||
actual: |-
|
||||
许可证本身无风险,但项目没有任何公开的营收路径
|
||||
(无企业版、无订阅、不卖模型、不代买订阅)。
|
||||
58k star 的体量与零商业化不匹配,属于「要么即将融资、
|
||||
要么后续变更条款」的不稳定态。
|
||||
source: README License 段;README 明确「Orca does not provide models, nor does it buy subscriptions for you」
|
||||
- claim: |-
|
||||
桌面 / 移动 / VPS 全覆盖
|
||||
actual: |-
|
||||
三端成熟度不对等:Android 仍是手工分发 APK(0.0.47)、
|
||||
iOS 走 App Store 与 TestFlight、VPS 场景需另查 headless Linux 指南。
|
||||
移动端实际是「监控与追加指令」的伴随形态,不是完整工作环境。
|
||||
source: README Install 段与 Mobile Companion 段
|
||||
maturity: |-
|
||||
功能广度第一,工程治理末位。一天多版(v1.4.194)说明迭代极快,
|
||||
但 issue 只进不出、巴士系数为 1,属于「高速原型期」而非「可托付生产期」。
|
||||
|
||||
chart:
|
||||
|
||||
form_depth: 8 # 桌面 + 移动伴随 + CLI + SSH,形态铺得最广
|
||||
engine_neutral: 9 # 30+ 引擎,兼容面第一
|
||||
local_first_score: 6 # 本地起进程,但强依赖各家云端订阅
|
||||
deliverable_depth: 3 # 主要产出 code / diff / review comment
|
||||
|
||||
verdict:
|
||||
|
||||
one_liner: 兼容面最广的并行 agent 编排外壳——广度做到极致,深度与治理都还是草稿。
|
||||
who_should_use: |-
|
||||
已在重度使用 CLI agent、想同时跑多个方案横向比对的个人开发者与小团队。
|
||||
who_should_not: |-
|
||||
需要稳定 SLA、指望社区 triage 与多人维护保障的团队;
|
||||
指望它统一各引擎能力(结构化调用、统一权限、一致口径)的架构选型者。
|
||||
|
||||
mirror:
|
||||
gitea_repo: "gitinbox/orca"
|
||||
visibility: public
|
||||
upstream: "https://github.com/stablyai/orca"
|
||||
last_sync: ""
|
||||
mode: pending
|
||||
sync_note: "暂未镜像。仓库 671 MB,按实测 50 KB/s 需 3.7 小时,且 git clone 无断点续传,中途失败即全部作废。MIT 许可(L1)无分发障碍,纯粹是带宽约束。待服务器侧放开或改用服务端迁移后再补。"
|
||||
article:
|
||||
emlog_id: 0 # emlog 文章 ID,发布后回写
|
||||
published_at: ""
|
||||
url: ""
|
||||
@@ -8,3 +8,22 @@ projects:
|
||||
added_at: 2026-09-01
|
||||
license_level: L3 # source-available,镜像私有
|
||||
mirror_visibility: private
|
||||
- id: openwork
|
||||
repo: different-ai/openwork
|
||||
status: active
|
||||
added_at: 2026-09-01
|
||||
license_level: L3 # 目录切分许可:ee/ 之外 MIT,ee/ 为 EE License
|
||||
mirror_visibility: public # 人工复核:镜像时排除 ee/ 后为纯 MIT,故可公开
|
||||
mirror_excludes: ["ee/"]
|
||||
- id: orca
|
||||
repo: stablyai/orca
|
||||
status: active
|
||||
added_at: 2026-09-01
|
||||
license_level: L1
|
||||
mirror_visibility: public
|
||||
- id: maka
|
||||
repo: apache/maka
|
||||
status: active
|
||||
added_at: 2026-09-01
|
||||
license_level: L1
|
||||
mirror_visibility: public
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"stars": 4373, "forks": 406, "open_issues": 398, "subscribers": 23, "created_at": "2026-05-27", "pushed_at": "2026-09-01", "size_kb": 102390, "languages": {"TypeScript": 91.7, "JavaScript": 4.5, "Rust": 1.7, "CSS": 1.3, "Python": 0.5, "PowerShell": 0.2}, "latest_release": "v0.2.0-dev.9.20260831", "release_cadence": "一天多版", "data_date": "2026-09-01", "stale": false}
|
||||
@@ -0,0 +1 @@
|
||||
{"stars": 23253, "forks": 2307, "open_issues": 414, "subscribers": 89, "created_at": "2026-01-14", "pushed_at": "2026-09-01", "size_kb": 298398, "languages": {"TypeScript": 89.9, "JavaScript": 6.7, "MDX": 1.2, "CSS": 0.9, "Shell": 0.6, "Swift": 0.5}, "latest_release": "v0.18.40", "release_cadence": "约 1 天/版本", "data_date": "2026-09-01", "stale": false}
|
||||
@@ -0,0 +1 @@
|
||||
{"stars": 58771, "forks": 3987, "open_issues": 5000, "subscribers": 121, "created_at": "2026-03-17", "pushed_at": "2026-09-01", "size_kb": 687575, "languages": {"TypeScript": 97.0, "JavaScript": 2.3, "Swift": 0.2, "CSS": 0.2, "HTML": 0.1, "PowerShell": 0.1}, "latest_release": "v1.4.194", "release_cadence": "约 1 天/版本", "data_date": "2026-09-01", "stale": false}
|
||||
@@ -67,6 +67,24 @@ manual_review_triggers:
|
||||
- "README 自称 open source 但 SPDX 不是 OSI 认证标识(名实不符,需显式记录)"
|
||||
- "LICENSE 中出现 SaaS / hosting / white-label / marketplace / resale 任一关键词"
|
||||
|
||||
# ---------- 混合许可的降级规则 ----------
|
||||
# 自动定级只认 SPDX 标识,无法识别「目录切分许可」这类混合形态。
|
||||
# 遇到需人工复核的仓库,按此流程处理:
|
||||
mixed_license_procedure:
|
||||
- "定位受限目录:读根目录 LICENSE 与各子目录 LICENSE,找出采用非 OSI 许可的路径"
|
||||
- "评估剩余部分:确认排除受限目录后,其余内容是否为单一 OSI 许可"
|
||||
- "若成立 -> 镜像时排除受限目录,可见性按剩余部分的许可定级(通常可 public)"
|
||||
- "若不成立 -> 整仓按最严格的那个许可定级(通常 L3 / private)"
|
||||
- "必须在档案 mirror.sync_note 与仓库内 MIRROR-NOTE.md 写明:排除了什么、为什么、如何恢复完整镜像"
|
||||
|
||||
# 补这条规则的缘由(注意:这是独立顶层键,不能写进上面的列表里——
|
||||
# YAML 不允许序列与映射键同级混用,写进去会直接让整份 schema 解析失败)
|
||||
mixed_license_note: |
|
||||
这条规则是 2026-09-01 处理 different-ai/openwork 时补上的。
|
||||
自动定级把它判成 L3/private,但人工复核后发现 ee/ 之外是纯 MIT,
|
||||
排除 ee/ 后完全可以公开镜像——同一个仓库,两种正确答案,
|
||||
取决于镜像是否包含受限目录。没有这条规则就会做出过度保守的误判。
|
||||
|
||||
# ---------- 已定级样本(供后续同类项目参照) ----------
|
||||
precedents:
|
||||
- repo: Devin-AXIS/iPolloWork
|
||||
@@ -79,3 +97,26 @@ precedents:
|
||||
面向客户的使用,无论个人还是企业,均需事先书面授权;且面向用户的前端
|
||||
必须保留 iPolloWork 名称、Logo 与产品归属。公开镜像落入「托管/市场分发」
|
||||
的解释空间,故设为私有。
|
||||
|
||||
- repo: different-ai/openwork
|
||||
level: L3
|
||||
visibility: public
|
||||
decided_at: 2026-09-01
|
||||
manual_review: true
|
||||
trigger_hit:
|
||||
- "同一仓库存在多个 LICENSE 文件(LICENSE + ee/LICENSE)"
|
||||
- "README 自称 open source 但 SPDX 为 NOASSERTION"
|
||||
- "ee/LICENSE 含 production / subscription / resale 关键词"
|
||||
license_name: "目录切分许可:ee/ 之外 MIT;ee/ 为 OpenWork EE License"
|
||||
reason: |
|
||||
根目录 LICENSE(36 行)声明 ee/ 之外的部分为 MIT;
|
||||
ee/LICENSE(72 行)为 OpenWork EE License,要求生产使用需订阅,
|
||||
仅 5 人以下组织、30 天评估、开发测试三种情形豁免,
|
||||
且每个 ee/ 版本公开发布满两年后自动追加 MIT 授权。
|
||||
**排除 ee/ 目录后,镜像内容为纯 MIT,可公开分发**;
|
||||
若完整镜像(含 ee/)则必须私有。
|
||||
mirror_strategy: "镜像时排除 ee/ -> public;完整镜像 -> private"
|
||||
lesson: |
|
||||
这是「自动定级过度保守」的典型案例:SPDX=NOASSERTION 直接判 L3/private
|
||||
是安全的默认值,但对目录切分许可仓库会误伤。
|
||||
已据此补充 mixed_license_procedure 规则。
|
||||
|
||||
@@ -167,6 +167,52 @@ def update_block(text: str, block_key: str, values: dict, indent: int | None = N
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def upsert_block(
|
||||
text: str, block_key: str, values: dict, parent: str, indent: int | None = None
|
||||
) -> str:
|
||||
"""update_block 的容错版:块不存在时,在 parent 块末尾**新建**它。
|
||||
|
||||
为什么需要这个:手工补完档案时习惯整块替换(ecosystem、business 等),
|
||||
一不留神就会把块内的子块(如 ecosystem.metrics)一起覆盖掉。
|
||||
update_block 遇到块缺失只会抛 ValueError,被上层 catch 成一条警告,
|
||||
表现是「指标栏静默变空」——很难察觉,等发现时原始数据已经没了。
|
||||
有了 upsert,这类误删能自动长回来。
|
||||
"""
|
||||
try:
|
||||
return update_block(text, block_key, values, indent)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
lines = text.split("\n")
|
||||
pat = re.compile(rf"^(\s*){re.escape(parent)}:\s*$")
|
||||
pstart = None
|
||||
pbase = 0
|
||||
for i, ln in enumerate(lines):
|
||||
m = pat.match(ln)
|
||||
if m:
|
||||
pstart, pbase = i, len(m.group(1))
|
||||
break
|
||||
if pstart is None:
|
||||
raise ValueError(f"未找到父块 `{parent}:`,无法新建 `{block_key}:`")
|
||||
|
||||
pend = len(lines)
|
||||
for j in range(pstart + 1, len(lines)):
|
||||
ln = lines[j]
|
||||
if not ln.strip() or ln.lstrip().startswith("#"):
|
||||
continue
|
||||
if len(ln) - len(ln.lstrip()) <= pbase:
|
||||
pend = j
|
||||
break
|
||||
|
||||
child = pbase + 2
|
||||
grand = child + 2
|
||||
new = [f"{' ' * child}{block_key}:"] + [
|
||||
f"{' ' * grand}{k}: {_fmt(v)}" for k, v in values.items()
|
||||
]
|
||||
lines[pend:pend] = new
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 快照
|
||||
def snapshot_path(pid: str) -> Path:
|
||||
return SNAPSHOTS / f"{pid}.jsonl"
|
||||
|
||||
+138
-4
@@ -28,6 +28,28 @@ from _common import ARTICLES, PROJECTS, load_yaml # noqa: E402
|
||||
|
||||
QUAD_THRESHOLD = 4
|
||||
BEGIN, END = "<!-- MATRIX:BEGIN -->", "<!-- MATRIX:END -->"
|
||||
IDX_BEGIN, IDX_END = "<!-- INDEX:BEGIN -->", "<!-- INDEX:END -->"
|
||||
OVERVIEW_AID = "00-overview"
|
||||
|
||||
|
||||
def short_maturity(risk: dict) -> str:
|
||||
"""成熟度在档案里是一整段判断(几十字),直接塞进表格会把列撑爆。
|
||||
|
||||
优先用档案给的 `maturity_level` 短标签(人工判断最准);
|
||||
没有则从长文本截取第一个分句兜底。
|
||||
"""
|
||||
r = risk or {}
|
||||
lv = r.get("maturity_level")
|
||||
if lv:
|
||||
return str(lv)
|
||||
m = (r.get("maturity") or "").strip()
|
||||
if not m:
|
||||
return "-"
|
||||
for sep in ("。", ";", "\n"):
|
||||
if sep in m:
|
||||
m = m.split(sep)[0]
|
||||
break
|
||||
return m[:14] + ("…" if len(m) > 14 else "")
|
||||
|
||||
|
||||
def cell(v, empty="-") -> str:
|
||||
@@ -93,9 +115,18 @@ def build_tables(items: list[dict]) -> str:
|
||||
stale = " ⚠" if m.get("stale") else ""
|
||||
add(f"| **{d.get('name','')}** | {cell(m.get('stars'))} | {cell(m.get('forks'))} | "
|
||||
f"{cell(m.get('open_issues'))} | {cell(m.get('latest_release'))} | "
|
||||
f"{cell(m.get('release_cadence'))} | {cell((d.get('risk') or {}).get('maturity'))} | "
|
||||
f"{cell(m.get('release_cadence'))} | {short_maturity(d.get('risk') or {})} | "
|
||||
f"{cell(m.get('data_date'))}{stale} |")
|
||||
|
||||
# 成熟度是长文本,塞进表格会把列撑爆。表格里只放短标签,
|
||||
# 完整的成熟度判断单独列在表后,读者想看细节再往下读。
|
||||
mats = [(d.get("name", ""), (d.get("risk") or {}).get("maturity")) for d in items]
|
||||
if any(v for _, v in mats):
|
||||
add("\n**成熟度详解**\n")
|
||||
for name, v in mats:
|
||||
if v:
|
||||
add(f"- **{name}**:{v}")
|
||||
|
||||
# 宣称 vs 实测:全套矩阵里信息密度最高的一张表
|
||||
rows = []
|
||||
for d in items:
|
||||
@@ -121,6 +152,87 @@ def build_tables(items: list[dict]) -> str:
|
||||
return "\n".join(L)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 文章索引
|
||||
def read_h1(aid: str) -> str:
|
||||
"""取 articles/<aid>.md 的第一个一级标题作为文章名。"""
|
||||
p = ARTICLES / f"{aid}.md"
|
||||
if not p.exists():
|
||||
return aid
|
||||
for line in p.read_text(encoding="utf-8").lstrip().split("\n"):
|
||||
if line.startswith("# "):
|
||||
return line[2:].strip()
|
||||
return aid
|
||||
|
||||
|
||||
def collect_cross_articles() -> list[dict]:
|
||||
"""跨项目文章(总纲 / 横评 / 专题)的发布元数据。
|
||||
|
||||
ID 落在 articles/<aid>.meta.yaml 里,由 publish_emlog.py 维护。
|
||||
总纲文自身要排除——索引出现在总纲内部,自己链自己没有意义。
|
||||
"""
|
||||
out = []
|
||||
for mp in sorted(ARTICLES.glob("*.meta.yaml")):
|
||||
aid = mp.name[: -len(".meta.yaml")]
|
||||
if aid == OVERVIEW_AID:
|
||||
continue
|
||||
d = load_yaml(mp) or {}
|
||||
if not d.get("emlog_id"):
|
||||
continue
|
||||
out.append({
|
||||
"aid": aid,
|
||||
"title": read_h1(aid),
|
||||
"emlog_id": d["emlog_id"],
|
||||
"url": d.get("url") or f"https://blog.apescale.com/?post={d['emlog_id']}",
|
||||
"last_published": d.get("last_published", ""),
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
def article_index_section(items: list[dict]) -> str:
|
||||
"""文章索引。
|
||||
|
||||
这段原来是手抄的,结果新发了横评文、索引里却没有它——手写的索引
|
||||
每发一篇文章就会漂移一次。改成从两处事实源生成:
|
||||
项目深度文 ← data/projects/*.yaml 的 article 块
|
||||
跨项目文章 ← articles/*.meta.yaml
|
||||
"""
|
||||
L = ["<!-- INDEX:BEGIN -->"]
|
||||
cross = collect_cross_articles()
|
||||
|
||||
L.append("| 项目 | 一句话结论 | 深度文 |")
|
||||
L.append("| --- | --- | --- |")
|
||||
fallback = cross[0]["url"] if cross else ""
|
||||
for d in items:
|
||||
art = d.get("article") or {}
|
||||
url = art.get("url") or ""
|
||||
if url:
|
||||
link = f"[已发布]({url})"
|
||||
elif fallback:
|
||||
link = f"待发布(见下方横评)"
|
||||
else:
|
||||
link = "待发布"
|
||||
one = (d.get("verdict") or {}).get("one_liner") or "-"
|
||||
# 一句话结论本来就长,索引表里截断到第一个分句,避免一行几十字
|
||||
for sep in ("——", "。", ";"):
|
||||
if sep in one:
|
||||
one = one.split(sep)[0]
|
||||
break
|
||||
L.append(f"| **{d.get('name','')}** | {one} | {link} |")
|
||||
|
||||
if cross:
|
||||
L.append("\n**跨项目文章**\n")
|
||||
for a in cross:
|
||||
L.append(f"- [{a['title']}]({a['url']}) — {a['last_published']}")
|
||||
|
||||
pending = [d.get("name", "") for d in items if not (d.get("article") or {}).get("url")]
|
||||
if pending:
|
||||
L.append(f"\n> 待补深度文:{'、'.join(pending)}。"
|
||||
f"当前可先读上方跨项目文章,其中已包含这些项目的完整分维度对比。")
|
||||
|
||||
L.append("<!-- INDEX:END -->")
|
||||
return "\n".join(L)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 图
|
||||
def stack_diagram(items: list[dict]) -> str:
|
||||
"""项目 <4 个时的赛道分层图:按交互形态把项目归到三层栈。"""
|
||||
@@ -161,8 +273,12 @@ def quadrant_svg(items: list[dict]) -> str:
|
||||
svg = []
|
||||
for i, (title, xk, yk, xlo, xhi, ylo, yhi) in enumerate(plots):
|
||||
ox = i * (W + 30)
|
||||
# max-width:100% + height:auto 不能省:SVG 带固定 width/height 属性,
|
||||
# 在窄屏(手机、窄主题)里会撑破容器横向溢出。有 viewBox 时浏览器
|
||||
# 才能按这两个声明等比缩放。
|
||||
svg.append(f'<svg viewBox="0 0 {W} {H}" width="{W}" height="{H}" '
|
||||
f'xmlns="http://www.w3.org/2000/svg" style="font-family:sans-serif">')
|
||||
f'xmlns="http://www.w3.org/2000/svg" '
|
||||
f'style="font-family:sans-serif;max-width:100%;height:auto">')
|
||||
svg.append(f'<text x="{W//2}" y="26" text-anchor="middle" font-size="15" '
|
||||
f'font-weight="600" fill="#1f2328">{title}</text>')
|
||||
px, py, pw, ph = PAD, 55, W - PAD * 2, H - PAD - 55
|
||||
@@ -190,7 +306,16 @@ def quadrant_svg(items: list[dict]) -> str:
|
||||
name = d.get("name", "")
|
||||
svg.append(f'<circle cx="{cx:.1f}" cy="{cy:.1f}" r="7" fill="#0969da" '
|
||||
f'fill-opacity="0.75" stroke="#fff" stroke-width="1.5"/>')
|
||||
svg.append(f'<text x="{cx+11:.1f}" y="{cy+4:.1f}" font-size="12" fill="#1f2328">{name}</text>')
|
||||
# 标签默认画在点右侧,但满分点会顶到绘图区右边界(cx == px+pw),
|
||||
# 文字就会溢出画布被裁掉。靠右时翻到点的左侧。
|
||||
# 估算文字宽度:中文按 12px、其余按 7px,够用且不依赖字体度量。
|
||||
tw = sum(12 if ord(ch) > 0x2E80 else 7 for ch in name)
|
||||
if cx + 11 + tw > W - 4:
|
||||
svg.append(f'<text x="{cx-11:.1f}" y="{cy+4:.1f}" text-anchor="end" '
|
||||
f'font-size="12" fill="#1f2328">{name}</text>')
|
||||
else:
|
||||
svg.append(f'<text x="{cx+11:.1f}" y="{cy+4:.1f}" font-size="12" '
|
||||
f'fill="#1f2328">{name}</text>')
|
||||
svg.append("</svg>")
|
||||
return "\n".join(svg)
|
||||
|
||||
@@ -216,7 +341,7 @@ def main() -> int:
|
||||
print(block)
|
||||
return 0
|
||||
|
||||
path = ARTICLES / f"{'00-overview'}.md"
|
||||
path = ARTICLES / f"{OVERVIEW_AID}.md"
|
||||
if not path.exists():
|
||||
print(f"总纲文不存在:{path}")
|
||||
return 1
|
||||
@@ -225,6 +350,15 @@ def main() -> int:
|
||||
print(f"总纲文缺少 {BEGIN} / {END} 占位标记,未回填。")
|
||||
return 1
|
||||
text = re.sub(rf"{re.escape(BEGIN)}.*?{re.escape(END)}", lambda _: block, text, flags=re.S)
|
||||
|
||||
# 文章索引同样回填,两个块各管一段,互不干扰
|
||||
index = article_index_section(items)
|
||||
if IDX_BEGIN in text and IDX_END in text:
|
||||
text = re.sub(rf"{re.escape(IDX_BEGIN)}.*?{re.escape(IDX_END)}",
|
||||
lambda _: index, text, flags=re.S)
|
||||
else:
|
||||
print(f" [warn] 总纲文缺少 {IDX_BEGIN} / {IDX_END},文章索引未回填")
|
||||
|
||||
path.write_text(text, encoding="utf-8")
|
||||
print(f"==> 已回填总纲文:{path}")
|
||||
return 0
|
||||
|
||||
@@ -34,6 +34,7 @@ from _common import ( # noqa: E402
|
||||
load_yaml,
|
||||
today,
|
||||
update_block,
|
||||
upsert_block,
|
||||
)
|
||||
|
||||
|
||||
@@ -125,7 +126,9 @@ def fetch_one(pid: str, repo: str, force: bool) -> bool:
|
||||
if ypath.exists():
|
||||
text = ypath.read_text(encoding="utf-8")
|
||||
try:
|
||||
text = update_block(text, "metrics", metrics)
|
||||
# 用 upsert 而非 update:手工补完档案时若整块覆盖了 ecosystem,
|
||||
# metrics 子块会消失,upsert 能把它重建回来。
|
||||
text = upsert_block(text, "metrics", metrics, parent="ecosystem")
|
||||
except ValueError as e:
|
||||
print(f" [warn] 回写 metrics 失败:{e}")
|
||||
# 许可证若尚未定级,顺手补上
|
||||
|
||||
+210
-51
@@ -16,29 +16,35 @@ DISPLAY_URL="${GITEA_DISPLAY_URL:-https://gitea.apescale.com}"
|
||||
ORG="${GITEA_ORG:-gitinbox}"
|
||||
ID=""
|
||||
DEPTH=""
|
||||
# auto = 体积超限时自动降级为源码快照;no = 始终尝试完整镜像(超限会 413 失败)。
|
||||
# 这两个变量必须在 set -u 之前就有值:下面体积闸门的条件里会引用它们,
|
||||
# 而这个分支只在特定体积下才走到——不初始化就成了「偶尔才炸」的雷。
|
||||
SOURCE_ONLY="auto"
|
||||
SOURCE_ONLY_FORCE="no"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--id) ID="$2"; shift 2;;
|
||||
--org) ORG="$2"; shift 2;;
|
||||
--url) GITEA_URL="$2"; shift 2;;
|
||||
--depth) DEPTH="$2"; shift 2;;
|
||||
--id) ID="$2"; shift 2;;
|
||||
--org) ORG="$2"; shift 2;;
|
||||
--url) GITEA_URL="$2"; shift 2;;
|
||||
--depth) DEPTH="$2"; shift 2;;
|
||||
--no-source-only) SOURCE_ONLY="no"; shift;;
|
||||
*) echo "未知参数: $1" >&2; exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$ID" ]] && { echo "缺少 --id" >&2; exit 1; }
|
||||
YAML="$ROOT/data/projects/$ID.yaml"
|
||||
[[ -f "$YAML" ]] || { echo "档案不存在: $YAML" >&2; exit 1; }
|
||||
[[ -z "${ID}" ]] && { echo "缺少 --id" >&2; exit 1; }
|
||||
YAML="${ROOT}/data/projects/${ID}.yaml"
|
||||
[[ -f "${YAML}" ]] || { echo "档案不存在: ${YAML}" >&2; exit 1; }
|
||||
|
||||
# ---- 从档案读取镜像配置(bash 不适合解析 YAML,借用 python 输出 shell 赋值) ----
|
||||
# 用"写临时文件再 source"而不是 eval "$(...)":
|
||||
# eval 会二次解析内容,YAML 里的括号/引号一旦漏转义就会变成难定位的
|
||||
# syntax error,而且报错行号会偏移到完全无关的位置。
|
||||
CONF_FILE="$(mktemp)"
|
||||
trap 'rm -f "$CONF_FILE"' EXIT
|
||||
trap 'rm -f "${CONF_FILE}"' EXIT
|
||||
|
||||
/Users/figmarlite/.workbuddy/binaries/python/envs/default/bin/python - "$YAML" > "$CONF_FILE" <<'PY'
|
||||
/Users/figmarlite/.workbuddy/binaries/python/envs/default/bin/python - "${YAML}" > "${CONF_FILE}" <<'PY'
|
||||
import sys, shlex
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(sys.argv[1]).resolve().parent.parent.parent / "scripts"))
|
||||
@@ -56,81 +62,233 @@ print(f"LIC_NAME={shlex.quote(str(lic.get('name') or ''))}")
|
||||
PY
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONF_FILE"
|
||||
source "${CONF_FILE}"
|
||||
|
||||
[[ -z "$UPSTREAM" ]] && { echo "档案缺少 mirror.upstream" >&2; exit 1; }
|
||||
[[ -z "${UPSTREAM}" ]] && { echo "档案缺少 mirror.upstream" >&2; exit 1; }
|
||||
|
||||
echo "==> 镜像 $ID"
|
||||
echo " 上游 : $UPSTREAM"
|
||||
echo " 目标 : $ORG/$REPO_NAME"
|
||||
echo " 许可证 : $LIC_LEVEL ($LIC_NAME)"
|
||||
echo " 可见性 : $VISIBILITY"
|
||||
if [[ "$VISIBILITY" == "private" ]]; then
|
||||
echo "==> 镜像 ${ID}"
|
||||
echo " 上游 : ${UPSTREAM}"
|
||||
echo " 目标 : ${ORG}/${REPO_NAME}"
|
||||
echo " 许可证 : ${LIC_LEVEL} (${LIC_NAME})"
|
||||
echo " 可见性 : ${VISIBILITY}"
|
||||
if [[ "${VISIBILITY}" == "private" ]]; then
|
||||
echo " ⚠ L3 级 source-available,已强制私有。公开镜像可能构成未经授权的分发。"
|
||||
fi
|
||||
|
||||
# ---- 凭据:优先环境变量,回退到 macOS 钥匙串 ----
|
||||
if [[ -z "${GITEA_TOKEN:-}" ]]; then
|
||||
CRED="$(printf "host=%s\nprotocol=https\n" "${DISPLAY_URL#*://}" | git credential-osxkeychain get 2>/dev/null || true)"
|
||||
GITEA_USER="${GITEA_USER:-$(printf '%s' "$CRED" | sed -n 's/^username=//p')}"
|
||||
GITEA_TOKEN="$(printf '%s' "$CRED" | sed -n 's/^password=//p')"
|
||||
GITEA_USER="${GITEA_USER:-$(printf '%s' "${CRED}" | sed -n 's/^username=//p')}"
|
||||
GITEA_TOKEN="$(printf '%s' "${CRED}" | sed -n 's/^password=//p')"
|
||||
fi
|
||||
[[ -z "${GITEA_TOKEN:-}" || -z "${GITEA_USER:-}" ]] && {
|
||||
echo "未拿到 Gitea 凭据(设 GITEA_TOKEN/GITEA_USER,或存入钥匙串)" >&2; exit 1; }
|
||||
|
||||
# ---- 确保目标仓库存在(组织端点,不是 /user/repos) ----
|
||||
API="$GITEA_URL/api/v1"
|
||||
HTTP="$(curl -sS -o /dev/null -w '%{http_code}' -u "$GITEA_USER:$GITEA_TOKEN" "$API/repos/$ORG/$REPO_NAME" || echo 000)"
|
||||
if [[ "$HTTP" == "200" ]]; then
|
||||
API="${GITEA_URL}/api/v1"
|
||||
HTTP="$(curl -sS -o /dev/null -w '%{http_code}' -u "${GITEA_USER}:${GITEA_TOKEN}" "${API}/repos/${ORG}/${REPO_NAME}" || echo 000)"
|
||||
if [[ "${HTTP}" == "200" ]]; then
|
||||
echo " 仓库已存在"
|
||||
elif [[ "$HTTP" == "404" ]]; then
|
||||
echo " 创建仓库 $ORG/$REPO_NAME (private=$([[ $VISIBILITY == private ]] && echo true || echo false)) ..."
|
||||
CREATE="$(curl -sS -o /tmp/gitea_mirror_create.json -w '%{http_code}' -u "$GITEA_USER:$GITEA_TOKEN" \
|
||||
-H 'Content-Type: application/json' -X POST "$API/org/$ORG/repos" \
|
||||
-d "{\"name\":\"$REPO_NAME\",\"private\":$([[ $VISIBILITY == private ]] && echo true || echo false),\"auto_init\":false,\"description\":\"Mirror of $UPSTREAM (license: $LIC_NAME)\"}")"
|
||||
[[ "$CREATE" =~ ^(200|201)$ ]] || {
|
||||
echo "创建失败 (HTTP $CREATE):" >&2; cat /tmp/gitea_mirror_create.json >&2; exit 1; }
|
||||
elif [[ "${HTTP}" == "404" ]]; then
|
||||
echo " 创建仓库 ${ORG}/${REPO_NAME} (private=$([[ ${VISIBILITY} == private ]] && echo true || echo false)) ..."
|
||||
CREATE="$(curl -sS -o /tmp/gitea_mirror_create.json -w '%{http_code}' -u "${GITEA_USER}:${GITEA_TOKEN}" \
|
||||
-H 'Content-Type: application/json' -X POST "${API}/org/${ORG}/repos" \
|
||||
-d "{\"name\":\"${REPO_NAME}\",\"private\":$([[ ${VISIBILITY} == private ]] && echo true || echo false),\"auto_init\":false,\"description\":\"Mirror of ${UPSTREAM} (license: ${LIC_NAME})\"}")"
|
||||
[[ "${CREATE}" =~ ^(200|201)$ ]] || {
|
||||
echo "创建失败 (HTTP ${CREATE}):" >&2; cat /tmp/gitea_mirror_create.json >&2; exit 1; }
|
||||
echo " 已创建"
|
||||
else
|
||||
echo "查询仓库失败 (HTTP $HTTP)" >&2; exit 1
|
||||
echo "查询仓库失败 (HTTP ${HTTP})" >&2; exit 1
|
||||
fi
|
||||
|
||||
# 已存在的仓库若应为私有,显式修正(创建时的 private 对已存在仓库不生效)
|
||||
if [[ "$VISIBILITY" == "private" ]]; then
|
||||
curl -sS -o /dev/null -u "$GITEA_USER:$GITEA_TOKEN" -X PATCH \
|
||||
-H 'Content-Type: application/json' -d '{"private":true}' "$API/repos/$ORG/$REPO_NAME" || true
|
||||
if [[ "${VISIBILITY}" == "private" ]]; then
|
||||
curl -sS -o /dev/null -u "${GITEA_USER}:${GITEA_TOKEN}" -X PATCH \
|
||||
-H 'Content-Type: application/json' -d '{"private":true}' "${API}/repos/${ORG}/${REPO_NAME}" || true
|
||||
fi
|
||||
|
||||
# ---- 同步 ----
|
||||
MIRROR_DIR="$ROOT/.mirrors/$REPO_NAME.git"
|
||||
mkdir -p "$ROOT/.mirrors"
|
||||
MIRROR_DIR="${ROOT}/.mirrors/${REPO_NAME}.git"
|
||||
mkdir -p "${ROOT}/.mirrors"
|
||||
|
||||
if [[ -d "$MIRROR_DIR" ]]; then
|
||||
echo " 更新已有裸仓 ..."
|
||||
git -C "$MIRROR_DIR" remote update --prune
|
||||
else
|
||||
echo " 首次全量镜像(大仓请耐心)..."
|
||||
if [[ -n "$DEPTH" ]]; then
|
||||
git clone --mirror --depth "$DEPTH" "$UPSTREAM" "$MIRROR_DIR"
|
||||
DEEPEN_STEP="${DEEPEN_STEP:-100}"
|
||||
|
||||
# 注意:不能用 `rev-parse --git-dir` 判断仓库是否有效。git clone 会先建好
|
||||
# 目录骨架再下载 pack,被中断的半成品照样能通过 rev-parse,于是被当成
|
||||
# "已有仓库"走 fetch 分支,报出 "CONNECT tunnel failed" 这类误导性错误。
|
||||
# 真正的判据是"里面有没有提交"。
|
||||
repo_ok() {
|
||||
[[ -d "${MIRROR_DIR}" ]] || return 1
|
||||
git -C "${MIRROR_DIR}" rev-parse --git-dir >/dev/null 2>&1 || return 1
|
||||
local n
|
||||
n="$(git -C "${MIRROR_DIR}" rev-list --count --all 2>/dev/null || echo 0)"
|
||||
[[ "${n}" -gt 0 ]]
|
||||
}
|
||||
is_shallow() { [[ "$(git -C "${MIRROR_DIR}" rev-parse --is-shallow-repository 2>/dev/null || echo false)" == "true" ]]; }
|
||||
|
||||
# clone/fetch 失败时清掉半成品,避免下次误判(脚本有 set -e,失败会直接退出)
|
||||
cleanup_broken() {
|
||||
if [[ -d "${MIRROR_DIR}" ]] && ! repo_ok; then
|
||||
echo " 清理未完成的半成品裸仓 ..."
|
||||
rm -rf "${MIRROR_DIR}"
|
||||
fi
|
||||
}
|
||||
trap cleanup_broken EXIT
|
||||
|
||||
if [[ -d "${MIRROR_DIR}" ]] && ! repo_ok; then
|
||||
echo " 清理上次中断的半成品裸仓 ..."
|
||||
rm -rf "${MIRROR_DIR}"
|
||||
fi
|
||||
|
||||
if repo_ok; then
|
||||
if is_shallow && [[ -z "${DEPTH}" ]]; then
|
||||
# 慢链路续传:分批 --deepen。每批成功即落盘,中断后重跑本脚本可继续,
|
||||
# 不像 `git clone` 那样失败一次全部作废。
|
||||
echo " 浅仓分批续传(每批 --deepen=${DEEPEN_STEP},中断可重跑)..."
|
||||
while is_shallow; do
|
||||
PRE="$(git -C "${MIRROR_DIR}" rev-list --count --all 2>/dev/null || echo 0)"
|
||||
echo " 已有 ${PRE} 条历史,再加深 ${DEEPEN_STEP} ..."
|
||||
if ! git -C "${MIRROR_DIR}" fetch --deepen="${DEEPEN_STEP}" origin; then
|
||||
echo " ⚠ 本批中断;已落盘的 ${PRE} 条历史保留,重跑本脚本继续。"
|
||||
break
|
||||
fi
|
||||
POST="$(git -C "${MIRROR_DIR}" rev-list --count --all 2>/dev/null || echo 0)"
|
||||
[[ "${POST}" == "${PRE}" ]] && { echo " 已补齐完整历史"; break; }
|
||||
done
|
||||
elif is_shallow && [[ -n "${DEPTH}" ]]; then
|
||||
echo " 浅仓加深到 depth=${DEPTH} ..."
|
||||
git -C "${MIRROR_DIR}" fetch --depth="${DEPTH}" origin || \
|
||||
echo " ⚠ 加深中断,已保留现有深度,重跑可继续"
|
||||
else
|
||||
git clone --mirror "$UPSTREAM" "$MIRROR_DIR"
|
||||
echo " 更新已有裸仓 ..."
|
||||
git -C "${MIRROR_DIR}" remote update --prune
|
||||
fi
|
||||
else
|
||||
# 走沙箱代理时偶发 `CONNECT tunnel failed, response 502`,且 git clone
|
||||
# 失败不保留已下载内容,所以首次克隆必须带重试。
|
||||
CLONE_ARGS=(clone --mirror)
|
||||
[[ -n "${DEPTH}" ]] && CLONE_ARGS+=(--depth "${DEPTH}")
|
||||
if [[ -n "${DEPTH}" ]]; then
|
||||
echo " 首次镜像(--depth ${DEPTH},只取近期历史)..."
|
||||
else
|
||||
echo " 首次全量镜像(大仓请耐心)..."
|
||||
fi
|
||||
for attempt in 1 2 3; do
|
||||
if git "${CLONE_ARGS[@]}" "${UPSTREAM}" "${MIRROR_DIR}"; then
|
||||
break
|
||||
fi
|
||||
echo " ⚠ 第 ${attempt} 次克隆失败,8s 后重试 ..."
|
||||
rm -rf "${MIRROR_DIR}"
|
||||
sleep 8
|
||||
[[ ${attempt} -eq 3 ]] && { echo "克隆三次均失败,请检查网络/代理" >&2; exit 1; }
|
||||
done
|
||||
fi
|
||||
|
||||
# ---- 浅仓处理:服务端拒绝 shallow update,必须先补齐历史 ----
|
||||
# Git 服务端默认 `receive.shallowUpdate = deny`,推送浅仓会被拒:
|
||||
# ! [remote rejected] main -> main (shallow update not allowed)
|
||||
# 而 --depth 是本脚本自己提供的选项,推不上去等于自相矛盾,所以在这里兜住:
|
||||
# 先 --unshallow 补齐(增量下载,通常只比浅仓多几十 MB),失败再降级为快照。
|
||||
UNSHALLOWED="no"
|
||||
if is_shallow; then
|
||||
echo " 浅仓无法直接推送(服务端拒绝 shallow update),先补齐完整历史 ..."
|
||||
for attempt in 1 2 3; do
|
||||
if git -C "${MIRROR_DIR}" fetch --unshallow origin 2>/dev/null; then
|
||||
UNSHALLOWED="yes"; break
|
||||
fi
|
||||
echo " ⚠ 第 ${attempt} 次 unshallow 失败,10s 后重试 ..."
|
||||
sleep 10
|
||||
done
|
||||
if [[ "${UNSHALLOWED}" == "yes" ]]; then
|
||||
echo " 已补齐完整历史"
|
||||
else
|
||||
# 网络或体积原因补不齐 —— 退回快照模式,丢历史但至少把代码留下来。
|
||||
echo " ⚠ 补齐失败,将降级为【源码快照】(会丢失提交历史)"
|
||||
SOURCE_ONLY_FORCE="yes"
|
||||
fi
|
||||
fi
|
||||
|
||||
PROTO="${GITEA_URL%%://*}"
|
||||
HOST="${GITEA_URL#*//}"
|
||||
U_ENC="$(/Users/figmarlite/.workbuddy/binaries/python/envs/default/bin/python -c \
|
||||
'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1],safe=""))' "$GITEA_USER")"
|
||||
'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1],safe=""))' "${GITEA_USER}")"
|
||||
P_ENC="$(/Users/figmarlite/.workbuddy/binaries/python/envs/default/bin/python -c \
|
||||
'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1],safe=""))' "$GITEA_TOKEN")"
|
||||
'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1],safe=""))' "${GITEA_TOKEN}")"
|
||||
|
||||
echo " 推送到 $ORG/$REPO_NAME ..."
|
||||
git -C "$MIRROR_DIR" push --mirror "$PROTO://$U_ENC:$P_ENC@$HOST/$ORG/$REPO_NAME.git"
|
||||
# ---- 体积闸门:Gitea 前置 nginx 有 client_max_body_size 上限 ----
|
||||
# 实测 gitea.apescale.com:48 MB 放行、64 MB 拒绝(推算为 50m,宝塔常见默认值)。
|
||||
# 超限推送会在传完全部对象后才收到 HTTP 413,白白浪费几十分钟下载。
|
||||
# 所以推送前先量体积,超限则自动降级为「源码快照」:剔除图片/音视频/字体等
|
||||
# 非源码素材——这些内容通常占一半以上体积,却不是"跟踪源码"要看的东西。
|
||||
MAX_PUSH_MB="${MAX_PUSH_MB:-45}"
|
||||
PACK_KB="$(git -C "${MIRROR_DIR}" count-objects -v | awk '/^size-pack:/{print $2}')"
|
||||
PACK_MB=$(( PACK_KB / 1024 ))
|
||||
MIRROR_MODE="full"
|
||||
|
||||
echo " 本地裸仓打包体积: ${PACK_MB} MB(推送上限 ${MAX_PUSH_MB} MB)"
|
||||
|
||||
if [[ ( "${PACK_MB}" -gt "${MAX_PUSH_MB}" || "${SOURCE_ONLY_FORCE}" == "yes" ) \
|
||||
&& "${SOURCE_ONLY}" != "no" ]]; then
|
||||
MIRROR_MODE="source-only"
|
||||
if [[ "${SOURCE_ONLY_FORCE}" == "yes" ]]; then
|
||||
echo " ⚠ 降级为【源码快照】:提交历史不完整,按快照提交(仍会剔除素材)"
|
||||
else
|
||||
echo " ⚠ 超出上限,降级为【源码快照】:剔除非源码素材后推送"
|
||||
fi
|
||||
SNAP="$(mktemp -d)"
|
||||
# shellcheck disable=SC2064
|
||||
trap "cleanup_broken; rm -rf '${SNAP}'" EXIT
|
||||
|
||||
git init -q "${SNAP}/repo"
|
||||
git --git-dir="${MIRROR_DIR}" --work-tree="${SNAP}/repo" checkout HEAD -- . 2>/dev/null
|
||||
find "${SNAP}/repo" -type f -not -path '*/.git/*' \( \
|
||||
-iname '*.png' -o -iname '*.gif' -o -iname '*.jpg' -o -iname '*.jpeg' \
|
||||
-o -iname '*.webp' -o -iname '*.ico' -o -iname '*.mp4' -o -iname '*.mov' \
|
||||
-o -iname '*.webm' -o -iname '*.wav' -o -iname '*.mp3' -o -iname '*.glb' \
|
||||
-o -iname '*.ttf' -o -iname '*.woff' -o -iname '*.woff2' -o -iname '*.eot' \
|
||||
-o -iname '*.pdf' -o -iname '*.zip' \) -delete
|
||||
|
||||
cat > "${SNAP}/repo/MIRROR-NOTE.md" <<NOTE
|
||||
# 镜像说明
|
||||
|
||||
- 上游:${UPSTREAM}
|
||||
- 许可证:${LIC_NAME}(${LIC_LEVEL})
|
||||
- 抓取日期:$(date +%F)
|
||||
|
||||
## 这不是完整保真镜像
|
||||
|
||||
本仓库只含**源码与文档**,已剔除图片、音视频、字体等非源码素材。
|
||||
原因:Gitea 前置 nginx 的 \`client_max_body_size\` 上限约 ${MAX_PUSH_MB} MB,
|
||||
完整裸仓打包 ${PACK_MB} MB 会被 HTTP 413 拒绝。
|
||||
|
||||
完整保真快照保留在追踪体系的本地裸仓 \`.mirrors/${REPO_NAME}.git\` 中。
|
||||
把服务器 nginx 放开到 \`client_max_body_size 512m;\` 后即可推送完整版。
|
||||
NOTE
|
||||
|
||||
PUSH_DIR="${SNAP}/repo"
|
||||
( cd "${PUSH_DIR}" && git add -A \
|
||||
&& git -c user.name='agent-radar' -c user.email='radar@local' \
|
||||
commit -q -m "源码快照 @ $(date +%F) (source-only, 剔除非源码素材)" )
|
||||
# 必须先 repack 再量:新 commit 出来的都是 loose object,
|
||||
# size-pack 为 0,会打印出「源码快照体积: 0 MB」这种误导性输出。
|
||||
# repack 顺带也把推送体积压下去(实测 iPolloWork:散装 57 MB → 打包 14 MB)。
|
||||
git -C "${PUSH_DIR}" repack -a -d -q --window=50 --depth=50 2>/dev/null || true
|
||||
SNAP_MB=$(( $(git -C "${PUSH_DIR}" count-objects -v | awk '/^size-pack:/{print $2}') / 1024 ))
|
||||
echo " 源码快照体积: ${SNAP_MB} MB"
|
||||
else
|
||||
PUSH_DIR="${MIRROR_DIR}"
|
||||
fi
|
||||
|
||||
echo " 推送到 ${ORG}/${REPO_NAME} ..."
|
||||
if [[ "${MIRROR_MODE}" == "full" ]]; then
|
||||
git -C "${PUSH_DIR}" push --mirror "${PROTO}://${U_ENC}:${P_ENC}@${HOST}/${ORG}/${REPO_NAME}.git"
|
||||
else
|
||||
git -C "${PUSH_DIR}" push "${PROTO}://${U_ENC}:${P_ENC}@${HOST}/${ORG}/${REPO_NAME}.git" HEAD:refs/heads/main
|
||||
fi
|
||||
|
||||
# ---- 回写档案 ----
|
||||
/Users/figmarlite/.workbuddy/binaries/python/envs/default/bin/python - "$YAML" "$VISIBILITY" "$REPO_NAME" <<'PY'
|
||||
/Users/figmarlite/.workbuddy/binaries/python/envs/default/bin/python - "${YAML}" "${VISIBILITY}" "${REPO_NAME}" "${MIRROR_MODE}" <<'PY'
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(sys.argv[1]).resolve().parent.parent.parent / "scripts"))
|
||||
from _common import today, update_block
|
||||
@@ -140,9 +298,10 @@ text = update_block(text, "mirror", {
|
||||
"visibility": sys.argv[2],
|
||||
"gitea_repo": f"{'gitinbox'}/{sys.argv[3]}",
|
||||
"last_sync": today(),
|
||||
"mode": sys.argv[4],
|
||||
})
|
||||
p.write_text(text, encoding="utf-8")
|
||||
PY
|
||||
|
||||
SIZE="$(du -sh "$MIRROR_DIR" 2>/dev/null | cut -f1 || echo '?')"
|
||||
echo "==> 完成。$DISPLAY_URL/$ORG/$REPO_NAME (本地裸仓 $SIZE)"
|
||||
SIZE="$(du -sh "${MIRROR_DIR}" 2>/dev/null | cut -f1 || echo '?')"
|
||||
echo "==> 完成。${DISPLAY_URL}/${ORG}/${REPO_NAME} (模式 ${MIRROR_MODE},本地裸仓 ${SIZE})"
|
||||
|
||||
+33
-24
@@ -9,13 +9,20 @@
|
||||
|
||||
两个关键设计
|
||||
------------
|
||||
1. **update 前必须先 detail 取回全文**。
|
||||
emlog 的 article_update 接口在 content 为空时会把正文清空 —— 这是接口行为,
|
||||
不是 bug。总纲文每次回填都要更新,所以这条保护是刚需。
|
||||
1. **防空保护点放在"本地正文非空",不是"先 detail 取回线上全文"**。
|
||||
emlog 的 article_update 在 content 为空时会把正文清空,所以必须有保护。
|
||||
但保护不能建立在 detail 上,原因有二:
|
||||
a) 本脚本的 content 永远来自本地 Markdown 源文,不存在"忘了传"的情况;
|
||||
b) detail 接口**查不到草稿**(实测:草稿 id 返回 400 Article not found,
|
||||
已发布文章正常)。若以 detail 为门槛,草稿将永远无法转正。
|
||||
因此 detail 降级为可选的健全性检查:取得到就对比长度,取不到不阻断。
|
||||
2. **文章 ID 回写**。
|
||||
项目文回写到 data/projects/<id>.yaml 的 article 块;
|
||||
总纲文回写到 articles/00-overview.meta.yaml。
|
||||
有同名项目档案(data/projects/<id>.yaml)→ 回写到档案的 article 块;
|
||||
没有(总纲文 00-overview、横评文 00-comparison 这类跨项目文章)
|
||||
→ 回写到 articles/<id>.meta.yaml。
|
||||
有了 ID 才能做"更新"而不是每次都发一篇新的。
|
||||
判断顺序不能反:先按"是不是项目文"分流,再决定落哪里,
|
||||
否则新增一篇横评类文章时 ID 会无处可写,下次发布就变成重复发一篇。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -33,8 +40,6 @@ from _common import ARTICLES, PROJECTS, today, update_block # noqa: E402
|
||||
import publish_article as emlog # noqa: E402
|
||||
|
||||
DEFAULT_SORT_ID = 17 # 可巴蒂
|
||||
OVERVIEW_ID = "00-overview"
|
||||
META_PATH = ARTICLES / f"{OVERVIEW_ID}.meta.yaml"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 转换
|
||||
@@ -69,34 +74,38 @@ def to_excerpt(md: str, limit: int = 110) -> str:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- ID 读写
|
||||
def meta_path(aid: str) -> Path:
|
||||
return ARTICLES / f"{aid}.meta.yaml"
|
||||
|
||||
|
||||
def read_emlog_id(aid: str) -> int | None:
|
||||
if aid == OVERVIEW_ID:
|
||||
if not META_PATH.exists():
|
||||
return None
|
||||
return (yaml.safe_load(META_PATH.read_text(encoding="utf-8")) or {}).get("emlog_id")
|
||||
p = PROJECTS / f"{aid}.yaml"
|
||||
if not p.exists():
|
||||
return None
|
||||
d = yaml.safe_load(p.read_text(encoding="utf-8")) or {}
|
||||
v = (d.get("article") or {}).get("emlog_id")
|
||||
return int(v) if v else None
|
||||
if p.exists():
|
||||
d = yaml.safe_load(p.read_text(encoding="utf-8")) or {}
|
||||
v = (d.get("article") or {}).get("emlog_id")
|
||||
return int(v) if v else None
|
||||
mp = meta_path(aid)
|
||||
if mp.exists():
|
||||
return (yaml.safe_load(mp.read_text(encoding="utf-8")) or {}).get("emlog_id")
|
||||
return None
|
||||
|
||||
|
||||
def write_emlog_id(aid: str, emlog_id: int) -> None:
|
||||
url = f"{emlog.BLOG_URL}/?post={emlog_id}"
|
||||
if aid == OVERVIEW_ID:
|
||||
META_PATH.write_text(
|
||||
f"# 总纲文发布元数据(由 scripts/publish_emlog.py 维护)\n"
|
||||
p = PROJECTS / f"{aid}.yaml"
|
||||
|
||||
if not p.exists():
|
||||
# 跨项目文章(总纲 / 横评 / 专题):没有档案可挂,单独存 meta。
|
||||
mp = meta_path(aid)
|
||||
mp.write_text(
|
||||
f"# 发布元数据(由 scripts/publish_emlog.py 维护)\n"
|
||||
f"# 本文不属于单个项目,ID 不落在 data/projects/ 下\n"
|
||||
f"emlog_id: {emlog_id}\nurl: \"{url}\"\nlast_published: \"{today()}\"\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
print(f" 已写入 {META_PATH.name}")
|
||||
print(f" 已写入 {mp.name}")
|
||||
return
|
||||
|
||||
p = PROJECTS / f"{aid}.yaml"
|
||||
if not p.exists():
|
||||
print(f" [warn] 档案不存在,未回写 ID: {p}")
|
||||
return
|
||||
text = p.read_text(encoding="utf-8")
|
||||
try:
|
||||
text = update_block(text, "article", {
|
||||
|
||||
Reference in New Issue
Block a user