@ckanner/flywheel 0.22.1 → 0.27.3
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.
- package/CHANGELOG.md +51 -1
- package/README.md +183 -115
- package/bin/flywheel-dsh-worker.js +16 -0
- package/bin/flywheel.js +18 -25
- package/bin/runtime.js +87 -0
- package/docs/configuration.md +126 -0
- package/docs/examples/create-first-task.sh +138 -0
- package/docs/getting-started.md +347 -0
- package/npm/install.js +412 -0
- package/package.json +24 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.27.3
|
|
4
|
+
|
|
5
|
+
当前统一 npm 发行目标。本节记录修复内容,不代表本版门禁、registry 发布、latest 提升或用户 Mac 实机验收已完成。0.27.0/0.27.1 未上传 npm 包;0.27.2 的发行在验证阶段主动取消,未上传包,旧 tag 均保留不移动。
|
|
6
|
+
|
|
7
|
+
- 兼容 npm 12 `view --json` 的单元素数组结果,同时保留 npm 11 标量结果;多结果、错误结构与缺失 integrity 仍拒绝,只有结构化 E404 可表示版本不存在。
|
|
8
|
+
- 增加真实 npm CLI 对隔离本地 registry 的 `view` 协议回归,覆盖 latest、integrity、缺失字段及 E404/E403;不调用发布接口或真实账号。
|
|
9
|
+
- CI、六包候选构建、双平台验证与 publish 统一固定 Node 24.22.1/npm 12.2.1,并记录实际工具版本,避免测试与发布使用不同 npm 协议;原有权限、候选身份和全部门禁保留。
|
|
10
|
+
- 六个包、精确同版依赖、插件锁文件、构建入口和当前安装教程统一到 0.27.3。
|
|
11
|
+
|
|
12
|
+
## 0.27.2
|
|
13
|
+
|
|
14
|
+
[PR #19 CI](https://github.com/ckanner/flywheel/actions/runs/41388914605) 与 [main CI](https://github.com/ckanner/flywheel/actions/runs/41389529756) 均通过。随后预核真实 npm 12 的 `view --json` 发现单值结果变为数组,[0.27.2 发行 run](https://github.com/ckanner/flywheel/actions/runs/41389716920)在 preflight/build 成功后的 verify 阶段主动取消;未执行 npm 上传,不是 CI 失败。原 tag 保留不移动,后续修正版使用 0.27.3。
|
|
15
|
+
|
|
16
|
+
- 控制面测试夹具通过原子替换发布完整状态 JSON,消除模拟引擎直接覆写文件时被并发读取到半写内容的竞态;控制接口和生产状态文件契约不变。
|
|
17
|
+
- 发行构建兼容 npm 11 的单元素数组及 npm 12 按完整包名索引的单包对象 `pack --json` 输出,继续严格核对对象键、包名、版本、文件名及候选 integrity。
|
|
18
|
+
- 六个包、精确同版依赖、插件锁文件、构建入口及当前安装教程统一到 0.27.2;保留旧版本的实际验证证据和失败发行记录。
|
|
19
|
+
|
|
20
|
+
## 0.27.1
|
|
21
|
+
|
|
22
|
+
修复私有 GitHub 仓库的 npm 发行路径。`v0.27.0` 的首个 publish run 在上传前因裸 `git fetch` 缺少认证而停止,未发布任何 0.27.0 npm 包;该 tag 保留为源码和验证记录,不移动。随后 [0.27.1 发行 run](https://github.com/ckanner/flywheel/actions/runs/41387321980) 的 macOS 验证通过,但 Ubuntu 验证因控制面测试夹具的状态文件写入竞态失败,publish job 跳过,未上传任何 0.27.1 npm 包。0.27.1 tag 同样保留,后续修正版使用 0.27.2。
|
|
23
|
+
|
|
24
|
+
- 发布 job 在工具链准备后,通过官方 checkout 的短暂只读认证刷新 source 与完整 refs,随后下载候选并检查精确 tag;不再执行凭证已清理后的远端 fetch。
|
|
25
|
+
- 发布命令要求显式仓库可见性。public 保留 provenance;private 使用 `--provenance=false`,符合 npm 不支持私有源码仓库证明的限制,未知值在 registry 请求前失败。仓库可见性、六包 integrity、完整门禁和 latest 提升顺序不变。
|
|
26
|
+
- 增加私有仓库发布协议回归,实测显式参数覆盖 npm 环境默认值;六包、锁文件、构建入口和当前用户教程同步到 0.27.1。
|
|
27
|
+
|
|
28
|
+
## 0.27.0
|
|
29
|
+
|
|
30
|
+
DSH 桌面集成、可靠交付闭环与 npm 一条命令安装。此节记录对应版本的源码变更,不单独证明 npm 已发布或用户 Mac 已验收;用 `npm view @ckanner/flywheel version` 核对实际发行。旧 0.22.1 不包含本节新增安装和 DSH 接口。安装与迁移见 [入门教程](docs/getting-started.md)。
|
|
31
|
+
|
|
32
|
+
- **启动期间的停止快照**:修复真实子进程已经运行、`Cmd.Start` 尚未返回登记时,`KillActiveChildren` 可能取得空快照的竞态;启动与登记由同一把快照锁保护,失败路径也释放锁。新增真实进程的确定性回归,原验收停止测试及 3 秒期限保持不变。正常 machine SIGTERM 路径已有 context 取消兜底,本修复补足独立 kill snapshot 的已启动进程契约。
|
|
33
|
+
- **npm 一条命令安装**:新增 `npx -y @ckanner/flywheel@latest install` 与 `flywheel setup`,全局安装精确同版 CLI、平台二进制、worker、10 个内嵌 skills,再通过已有桌面 DSH 的正式接口注册并核验插件。支持 prefix/project/DSH 命令/profile、显式跳过、超时和 JSON 输出;普通 npm 包安装不通过 postinstall 修改用户目录。
|
|
34
|
+
- **软件安装与运行准备分开报告**:本地 doctor 有效失败保留完整检查项,允许 `ok:true, ready:false`;账号状态保持 unknown,不自动安装/登录其他 harness,不发送模型 prompt。取消、超时、损坏协议和版本漂移阻止安装成功,报告已完成步骤。
|
|
35
|
+
- **Mac GUI 自动入口**:生成使用绝对 Node/CLI 路径的 launcher 和带 SHA-256 的安装记录,桌面插件自动发现并验证;用户显式 `flywheelBinary` 优先。损坏或改动的入口明确失败,不静默使用其他版本,无需手写 GUI 启动脚本。
|
|
36
|
+
- **skills 更新保护**:快照清单校验内容、类型和模式,用户修改、新增/删除文件及未归属目录保留并报告冲突;逐 skill 暂存、rename 和失败回滚。旧纯文本标记仅在完整内容匹配时迁移。新增 `install-skills --json` 的完整逐项结果。
|
|
37
|
+
- **npm 启动器生命周期**:异步启动真实 Go 进程,传递取消信号并等待清理完成;核对原生包/worker 的精确版本,兼容依赖提升,避免启动旧二进制或留下 wrapper 的子进程。
|
|
38
|
+
- **六包发行门禁**:meta、四个平台包和 DSH 插件统一版本。先构建 worker,在隔离副本构建并打包一次,再于 Linux/macOS 验证同一候选的真实 npm 安装。发布要求 HEAD 精确 tag 与干净来源;已存在版本核对 integrity,只有明确 404 视为不存在;全部包与精确版本安装验证通过后才更新 latest,且不倒退标签。
|
|
39
|
+
- **GitHub 可信发布**:参考现有 DSH 插件的 Actions 流程,固定发布工具链为 Node 24.22.1/npm 12.2.1,支持 OIDC 的 publish 与 dist-tag;无 token 时使用独立空 npmrc,有 token 时保留后备认证。补充六包逐项授权、精确 tag 触发及原候选重试的维护者指南。
|
|
40
|
+
|
|
41
|
+
- **配置与模型 profiles**:新增可复用 `profiles`、阶段引用与显式覆盖;DSH `provider` 和账号 `channel` 分离,所有角色和渠道降级均传递实际 provider;指标分别保留两者。严格拒绝未知字段、无效引用和 provider 冲突,兼容旧内联绑定与合法显式零值。
|
|
42
|
+
- **只读预检**:新增 `config inspect`、`doctor` 和相应 control actions;检查有效配置、工具和任务所需路由。可选 SDK probe 验证配置、目录、沙箱与关闭,零推理,整体验证有界;账号、额度与实际模型能力明确标为未验证。
|
|
43
|
+
- **桌面可观察性**:面板展示配置、逐项诊断、完整 packet 状态与未知计量;区分完成、暂停、停止和升级,路径切换后忽略迟到的旧响应;增加宿主 React 18 对应的真实 DOM 交互回归。
|
|
44
|
+
- **DSH 入口与 worker**:新增 `control start/status/stop`、桌面面板、人工命令与模型工具。Go 继续独占状态;worker 校验 provider/model/cwd/effort、权限、可靠终态及 SDK 关闭。
|
|
45
|
+
- **模型路由**:默认 DeepSeek Flash 实现,Kimi CLI 规划/前端/整理,DSH ChatGPT provider 评审/终验,独立 Codex CLI 处理困难任务。Kimi 不使用 ACP;外部 Codex Pro adapter 与原生 LLM provider 分开,当前路由不隐式消费外部 subagent 工具。
|
|
46
|
+
- **可靠性**:验证证据绑定候选和契约;契约修订跨重启失效旧证据;L1、Review、里程碑 gate 与 Judge 前后检查身份;提交及合并后确认持久化结果,失败保留 worktree。
|
|
47
|
+
- **发布前复核**:修复从子目录漏读项目配置;持久化检查独立核对执行位与链接类型;验收输出截断不能因退出码符合预期而通过。CI 增加 Linux/macOS 插件、跨进程和 npm 安装检查;Actions artifact 用于传递和审查候选,正式用户从 npm 安装。
|
|
48
|
+
- **Mac CI 修复**:任务目录和 Git 执行根统一使用物理路径,避免 `/var`/`/private/var` 别名造成账本、报告和恢复判断失真;补充真实符号链接启动/恢复回归,并消除 Kimi 路径断言与 Zcode fixture 的写入/退出竞态。进程组清理探测的暂态 `EPERM` 在原宽限内重试,仍须确认 `ESRCH`;持续权限错误继续阻止交付。
|
|
49
|
+
- **生命周期与计量**:按运行 token 停止、运行 lease 防重入、超时/取消/清理失败严格区分;未知 usage/cost 不再当成零。
|
|
50
|
+
- **测试与文档**:普通单测/回归可随实现修改,冻结验收显式保护;保留逐函数 100% Go statement coverage;更新全部内嵌 skills 的控制接口、单环语义与配置说明,补充完整 Mac 安装和首个任务教程。
|
|
51
|
+
- **兼容边界**:旧配置不自动迁移;任务/项目/全局配置选择首个完整文件,不深度合并。Mac 桌面渲染、Seatbelt 和真实账号调用仍需用户环境验收;离线通过不等于实机通过。
|
|
52
|
+
|
|
3
53
|
## 0.22.1 (2026-09-14)
|
|
4
54
|
|
|
5
55
|
修复 0.22.0 meta 包缺 bin shim:`bin/flywheel.js` 此前被 .gitignore 的 `/bin/`(postinstall 下载目录时代的遗留规则)挡在 git 之外——CI checkout 无此文件,发布的 meta 包只有 3 个文件、`flywheel` 命令不存在。本版将 shim 入库并全量 bump 版本;旧 0.22.0 meta 请勿使用。
|
|
@@ -13,4 +63,4 @@
|
|
|
13
63
|
- **配置四级发现链**:任务目录 → 项目根 → 全局 `~/.config/flywheel/flywheel.config.json`(首装自动物化,可直接编辑)→ 内嵌法定表兜底。
|
|
14
64
|
- watchdog 交互钩子(协议驱动)、multimodal 执行路由、`FLYWHEEL_CONFIG` 覆盖。
|
|
15
65
|
|
|
16
|
-
更早历史见 docs/design.md
|
|
66
|
+
更早历史见 [历史设计](docs/design-history-v0.24.md) 的变更记录。
|
package/README.md
CHANGED
|
@@ -1,162 +1,230 @@
|
|
|
1
1
|
# Flywheel(飞轮)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Flywheel 用确定性 Go 引擎驱动 Plan、Execute、Review 和分层验收,把已经明确的需求变成有边界、可验证、可持久化的软件交付。模型负责规划、实现和评审;引擎负责状态、重试、预算、工作区、验收、提交与停止。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- 角色与知识 = Agent Skills(本仓库 `skills/`,只是"按钮")
|
|
7
|
-
- 契约 = `docs/<slug>/` 下的静态文件(spec.md + task-packet + state.json,全部进 git)
|
|
5
|
+
当前 **v0.27.3 实施版**以 DSH Mac 桌面为入口:Kimi CLI 规划,DSH 原生 DeepSeek Flash 执行,DSH 原生 GPT 评审,较难的执行任务可走独立 Codex CLI。DSH 插件提供控制面板、配置查看和安装预检;具名 model profiles 可复用于不同阶段,Go 仍是唯一状态所有者。设计依据见 [docs/design.md](docs/design.md),仓库约束见 [AGENTS.md](AGENTS.md)。
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
**本教程适用于 0.27.3 及以后的完整 npm 发行版。** 安装前用 `npm view @ckanner/flywheel version` 确认 registry 的 latest 已更新到所需版本;源码合并或 GitHub Release 不代表 npm 已发行。旧 0.22.1 没有本教程使用的安装与 DSH 接口,当前发行状态见[发行记录](docs/releasing.md#3-失败恢复)。
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
测试结果与实机边界见[验证记录](docs/validation/2026-11-01-npm-installation.md)。旧版本的选型、维护机环境与演练记录保存在[历史设计](docs/design-history-v0.24.md),不代表当前默认值或用户机器状态。
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
首次使用请按 [Mac 安装与首个任务教程](docs/getting-started.md) 完成安装、配置、无推理检查和小任务验收。本文保留各接口与设计边界的速查。
|
|
12
|
+
|
|
13
|
+
## 1. 一条命令安装
|
|
14
|
+
|
|
15
|
+
已有 **Node.js 22 或更高、npm 和 DSH Mac 桌面及其 `dsh` 命令**后,在终端运行:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npx -y @ckanner/flywheel@latest install
|
|
16
19
|
```
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
- 支持平台:darwin/linux × amd64/arm64(`os`/`cpu` 字段自动选取,`--no-optional` 会导致平台包缺失)。
|
|
20
|
-
- 非 npm 渠道:[GitHub Releases](https://github.com/ckanner/flywheel/releases) 下载平台 tar.gz(`shasum -a 256 -c checksums.txt` 校验)。
|
|
21
|
-
- 开发机源码运行:`go install ./cmd/flywheel`;`flywheel install-skills --link` 可选软链源码树(仅维护者便利,非安装路径)。
|
|
21
|
+
Agent 可在同一命令后加 `--json`,读取一个结构化结果。安装器把本次解析到的**精确版本**的 CLI、对应平台 Go 二进制和 DSH worker 装到 npm 全局目录,安装 10 个内嵌 skills,再将同版本 DSH 插件注册到桌面 profile 并核对版本。无需下载压缩包、克隆源码或自行编译 Go。
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
安装完成会显示每个步骤和本地预检结果。`ok:true` 表示软件安装成功;`ready:false` 表示当前配置仍有运行阻断项,例如缺少 Kimi/Codex CLI,二者可以同时出现。安装器不替换现有 DSH,也不安装或代登录 Kimi/Codex,不代配 DeepSeek API 凭证。运行真实任务前需完成所选渠道的准备。
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
- **zcode**:driver 默认 `node /Applications/ZCode.app/Contents/Resources/glm/zcode.cjs app-server`(app 内置 runtime,随桌面自动更新;`FLY_ZCODE_SERVER_BIN` 覆盖 launcher,`FLY_ZCODE_BIN` 只管桌面注册二进制——v0.22 语义拆分)。Z.AI Coding Plan 凭证在 `~/.zcode/cli/config.json`(provider zai);app-server 协议 live 冒烟 2026-09-10 通过(8.2s 全闭环);协议无公开文档——桌面大版本更新后跑协议探针(design.md §10-5)。
|
|
27
|
-
- **codex**:CLI 已升 0.154.0(gpt-6-astra 硬门槛 ≥0.154 npm / ≥0.153 app 内置);chatgpt 订阅 OAuth 已登录。注意:代理经 HKG 节点偶发 403,重试即过。
|
|
28
|
-
- **kimi code**:订阅 OAuth 登录在;stream-json 无 usage 暴露——Plan 的 token 不可观测(接受现状,看门狗兜底)。
|
|
29
|
-
- **omp(B 通道)**:已装、`zhipu-coding-plan` provider 配好(智谱国内计划)——zcode 协议漂移时的应急通道。
|
|
25
|
+
默认使用 npm 的全局 prefix 和用户级 skills 目录。没有全局目录写权限时,可显式选择用户目录:
|
|
30
26
|
|
|
31
|
-
|
|
27
|
+
```sh
|
|
28
|
+
npx -y @ckanner/flywheel@latest install --prefix "$HOME/.local"
|
|
29
|
+
```
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
自定义 prefix 后,如需在终端直接输入 `flywheel`,将该 prefix 的 `bin` 加入自己的 shell PATH。`--project`、`--dsh-bin`、`--profile`、`--no-dsh`、`--no-global` 和超时参数见[完整教程](docs/getting-started.md#2-一条命令安装)。
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
# 默认:软链(仓库即唯一信源,改了即生效;幂等,重复运行报告"已安装,跳过")
|
|
37
|
-
flywheel install-skills
|
|
33
|
+
普通 `npm install --global @ckanner/flywheel` 只安装包文件,不通过 postinstall 修改用户的 skills、配置或 DSH profile。已经用它安装的用户可显式运行 `flywheel setup --no-global` 完成配置;`install` 与 `setup` 共用安装流程,默认都会确保本版全局包已安装。
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
flywheel install-skills --force
|
|
35
|
+
## 2. DSH 入口与独立 worker
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
flywheel install-skills --copy
|
|
37
|
+
本实现针对已核对接口的 **DSH Mac 桌面 0.2.1-rc.5**。先核对 About、`dsh --version` 和命令实际来源。它不要求 alpha、OAR、Forge、Teams 或一直存活的桌面父智能体。
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
上一节的安装器使用**桌面安装的 `dsh` 命令**执行正式插件管理接口:`dsh plugin --profile desktop add --save-exact --ignore-scripts @ckanner/dsh-plugin-flywheel@<本次版本>`,然后通过 `plugin list` 核对版本。普通 npm DSH CLI 不能管理保留的 `desktop` profile;命令不在 PATH 时用 `--dsh-bin` 指定已核对的桌面命令入口。安装后按宿主提示重新载入。不要猜测 `.app` 内部可执行文件路径。
|
|
40
|
+
|
|
41
|
+
原生 worker 使用独立的 SDK profile。桌面装有某个 provider,不代表 SDK profile 已经加载或授权。DeepSeek 使用实际 provider `deepseek-official`;GPT 原生路由要求在 SDK profile 中配置对应的 ChatGPT adapter,例如本轮核对的版本:
|
|
48
42
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
```sh
|
|
44
|
+
dsh plugin --profile sdk add dsh-plugin-chatgpt@0.1.17
|
|
45
|
+
```
|
|
52
46
|
|
|
53
|
-
|
|
47
|
+
这是已核对的 adapter 版本示例;采用其他版本前按其文档检查兼容性。按 provider 自身的正常流程完成授权,保持所选 `DSH_HOME` 与配置来源一致。安装 adapter 不会自动迁移桌面凭证,也不证明账号可以调用默认表里的所有模型。`subagent_codex_pro` 是外部 agent 工具,不是这个 SDK 入口的 LLM provider;Flywheel 的 `hard` 路由使用独立 Codex CLI。
|
|
54
48
|
|
|
55
|
-
|
|
49
|
+
### worker 入口配置
|
|
56
50
|
|
|
57
|
-
|
|
|
51
|
+
| 环境变量 | 默认值与作用 |
|
|
58
52
|
|---|---|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
53
|
+
| `FLYWHEEL_DSH_WORKER` | npm 启动器自动解析同版本依赖中的 worker 绝对路径;显式环境变量优先。直接运行 Go 二进制时默认从 PATH 查找 `flywheel-dsh-worker` |
|
|
54
|
+
| `FLYWHEEL_DSH_BIN` | PATH 中的 `dsh`;建议固定为已核对的官方命令入口 |
|
|
55
|
+
| `FLYWHEEL_DSH_PROFILE` | `sdk`;必须是 SDK profile,不能是 `desktop` |
|
|
56
|
+
| `DSH_HOME` | 交给 DSH 的自定义配置根;不设置时沿用 DSH 自己的默认行为 |
|
|
57
|
+
| `FLYWHEEL_KIMI_BIN` | PATH 中的 `kimi`;可指定 Kimi CLI 的绝对路径 |
|
|
58
|
+
|
|
59
|
+
Codex CLI 通过 PATH 中的 `codex` 启动。所有选定 CLI、Node 与账户配置必须对**启动 Go 引擎的进程环境**可见。
|
|
60
|
+
|
|
61
|
+
安装器自动生成 `~/.config/flywheel/npm-launcher` 和 `installation.json`:记录绝对 Node/CLI 路径、已核对的 DSH 入口及安装时可见的工具 PATH。桌面插件未显式配置 `flywheelBinary` 时,读取这份安装记录,校验 launcher 是普通可执行文件且 SHA-256 匹配后使用。**无需手写 GUI 启动脚本或修改 profile 配置。**
|
|
62
|
+
|
|
63
|
+
用户原有的显式 `flywheelBinary` 优先,安装器不会替用户覆盖它。安装记录不存在时保留 PATH 查找兼容行为;记录或 launcher 已损坏/被修改时明确失败,不悄悄使用另一份引擎。升级 Node、移动全局 prefix 或新增 CLI 后,可在工具均可见的终端运行 `flywheel setup --no-global` 更新安装记录;先保存自己对 launcher 的修改,冲突不会被覆盖。终端里的 `export` 本身不会改变已运行的桌面进程,仍需在面板内复验。
|
|
69
64
|
|
|
70
|
-
|
|
65
|
+
### 不调用模型的检查
|
|
71
66
|
|
|
72
|
-
|
|
67
|
+
在实际目标仓库目录、使用与引擎 worker 相同的环境变量执行:
|
|
73
68
|
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
```sh
|
|
70
|
+
flywheel config inspect --cwd "$PWD" --json
|
|
71
|
+
flywheel doctor --cwd "$PWD" --json
|
|
72
|
+
flywheel doctor --cwd "$PWD" --probe --json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`config inspect` 只读展开有效配置;`doctor` 检查目录、配置、所需命令与声明的环境变量。加 `--task-dir docs/my-task` 会校验 Spec/packet,并把工具检查缩小到任务所需阶段与路由;不指定任务时检查全部配置绑定。没有 packet 时按规划前状态检查默认路由,不会擅自生成契约。
|
|
76
|
+
|
|
77
|
+
`--probe` 进一步启动本地 DSH SDK、创建会话、核对 cwd/权限并关闭,**不发送推理 prompt、不运行验收命令**;provider 初始化可能查询模型目录。输出保留 pass/fail/warn/unknown/skipped,阻断项使退出码为 1;没有阻断项也不能证明账号授权、订阅额度或真实模型推理可用。相同账号/模型/目录/effort/权限的 SDK 检查去重,检查预算 40 秒,进程各收尾阶段有 5 秒宽限,保守预留另 15 秒清理。不会物化全局配置、写任务状态或创建 worktree。
|
|
78
|
+
|
|
79
|
+
完整安装、权限、生命周期说明见 [DSH 插件 README](plugins/dsh/README.md),线协议见 [PROTOCOL.md](plugins/dsh/PROTOCOL.md)。
|
|
80
|
+
|
|
81
|
+
## 3. 当前默认路由
|
|
82
|
+
|
|
83
|
+
默认内联模板是 [templates/flywheel.config.example.json](templates/flywheel.config.example.json),可复用配置见 [profiles 示例](templates/flywheel.profiles.example.json) 与[配置指南](docs/configuration.md)。`harness` 指执行后端,`provider` 是 DSH SDK 实际注册的 LLM adapter ID,`channel` 是本配置的账号/计量渠道名;`model` 与 `thinking` 逐调用传入。旧配置未写 provider 时继续把 DSH channel 解释为 provider ID。
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
| 阶段或执行路由 | Harness | Provider/渠道 | 模型 | Thinking |
|
|
86
|
+
|---|---|---|---|---|
|
|
87
|
+
| Plan | Kimi CLI | `kimi-subscription` | `kimi-code/k3.1-pro` | `high`,匹配身份后可恢复会话 |
|
|
88
|
+
| Execute `default` | DSH SDK | `deepseek-official` | `deepseek-flash` | `high` |
|
|
89
|
+
| Execute `frontend` | Kimi CLI | `kimi-subscription` | `kimi-code/k3.1-pro` | `high` |
|
|
90
|
+
| Execute `hard` | Codex CLI | `chatgpt-subscription` | `gpt-6.1-sol` | `high` |
|
|
91
|
+
| Review | DSH SDK | `chatgpt` | `gpt-6.1-sol` | `high`,只读 |
|
|
92
|
+
| Judge | DSH SDK | `chatgpt` | `gpt-6-astra` | `high`,里程碑终验 |
|
|
93
|
+
| Intake | DSH SDK | `deepseek-official` | `deepseek-flash` | `low` |
|
|
94
|
+
| Garden | Kimi CLI | `kimi-subscription` | `kimi-code/k3.1-pro` | `high` |
|
|
82
95
|
|
|
83
|
-
|
|
84
|
-
/skill:flywheel-run docs/<slug> # 全自动
|
|
85
|
-
/skill:flywheel-run docs/<slug> review # 监督模式:Review 前冻结,等你看完再续
|
|
96
|
+
这些是可编辑的初始绑定,并非模型排名或性能承诺。DeepSeek 只用 Flash;当前 Mac rc.5 的合法 effort 为 `off/low/high/max`,没有 `medium`。双 judge 默认关闭,也不默认跨渠道付费降级。`multimodal` 暂无默认绑定:当前 bridge 只有文本契约,模型名称或目录里的 vision 标记不能证明附件已正确传递。
|
|
86
97
|
|
|
87
|
-
|
|
98
|
+
Kimi 使用原生 CLI print/stream-json,不使用 ACP;恢复的是会话历史,不是常驻后台进程。该模式不提供本适配器可宣称的 OS 级只读/工作区沙箱,worktree 只是代码隔离。DSH/Codex 使用明确的 workspace-write 或 read-only 与禁止扩权策略。具体账号的可用模型、额度和使用授权需要由所选服务确认。
|
|
88
99
|
|
|
89
|
-
|
|
90
|
-
flywheel status docs/<slug>
|
|
91
|
-
flywheel stop docs/<slug>
|
|
100
|
+
usage 或美元成本未上报时保留 unknown,不能把订阅调用记成免费。默认 soft token budget 会报告未知量;hard budget 无法证明未知用量未超额时停止。汇总数字是已知量下限。
|
|
92
101
|
|
|
93
|
-
|
|
102
|
+
### 迁移已有配置
|
|
103
|
+
|
|
104
|
+
配置按**第一个存在的完整文件**选择,不逐字段合并:任务目录 → 项目根 → 全局 `~/.config/flywheel/flywheel.config.json` → 二进制内嵌模板;`FLYWHEEL_CONFIG` 可指定全局配置路径。已有文件不会因升级自动改写。
|
|
105
|
+
|
|
106
|
+
可用新二进制查看模板,再审阅并迁移自己的配置:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
flywheel template flywheel.config.example.json
|
|
110
|
+
flywheel template flywheel.profiles.example.json
|
|
111
|
+
flywheel config inspect --cwd /repo --task-dir /repo/docs/my-task --json
|
|
94
112
|
```
|
|
95
113
|
|
|
96
|
-
|
|
114
|
+
`drivers` 选择 `real/mock`,不是模型或 provider 名称。模型在 `loops` 和 `routes` 配置,可直接写字段,也可引用 `profiles` 后按需覆盖。未知 profile、拼错字段、模型字段的 null 和 provider 冲突会拒绝加载;允许 `_note` 注释,原有 `env-key:null`、`judge-panel:null` 仍合法。渠道别名不会自动切换 DSH 账号,授权仍由选定 SDK profile/provider 管理。修改运行配置后重启引擎;Plan 不允许悄悄改模型、权限或其他运行配置。
|
|
97
115
|
|
|
98
|
-
##
|
|
116
|
+
## 4. 创建、运行和观察任务
|
|
99
117
|
|
|
118
|
+
先在目标 Git 仓库准备并审阅 `docs/<slug>/` 中的 Spec、packet 与配置:需求编号、L1/L2/L3 命令、禁止修改的验收路径和失败出口都应明确。模板可通过 `flywheel template` 查看;可选的 `flywheel-new` skill 用于辅助生成,最终仍由人确认契约。不要把 DSH 面板当成需求生成器。
|
|
119
|
+
|
|
120
|
+
桌面面板位于会话输入区底部的 **Flywheel** 按钮。输入仓库路径后可查看配置、检查环境或执行 SDK 无推理探测;启动、刷新和停止还需绝对任务路径。面板展示实际阶段绑定、检查项、完整 packet 列表与未知用量,原始 JSON 可按需展开。也可使用人工命令:
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
/flywheel {"action":"status","taskDir":"/repo/docs/my-task","cwd":"/repo"}
|
|
100
124
|
```
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
watchdog/ # 双计时看门狗(流 idle + wall-clock;进程组 kill + quiescence)
|
|
111
|
-
errs/ # 错误分类/退避/降级集(RE2 等价改写)
|
|
112
|
-
fallback/ # 渠道降级链(绑定表改写)
|
|
113
|
-
circuitbreaker/ # 预算熔断(off|soft|hard)+ DC4 空转检测
|
|
114
|
-
defectinjection/ # 缺陷注入抽检(DC3)
|
|
115
|
-
metrics/ # JSONL 指标(模型/token/成本/缓存命中/prompt 哈希)
|
|
116
|
-
prompts/ # 提示词构建(§8 缓存纪律:稳定前置/易变尾部)+ 裁决解析
|
|
117
|
-
drivers/ # mock(测试)/ pi / omp(B 通道)/ zcode(app-server)/ codex / kimi(长驻会话)
|
|
118
|
-
config/ # 法定配置表加载与校验
|
|
119
|
-
childenv/ # 子进程环境白名单(凭证卫生 §9)
|
|
120
|
-
ledger/ # 账本区路径分类(边界判定的单一信源)
|
|
121
|
-
modules/ # 外挂:intake/garden/install-skills/module 共享上下文
|
|
122
|
-
skillsembed/ # go:embed skills 数据层(skills list/read + 规范门禁测试)
|
|
123
|
-
templates/ # 契约模板与 schema(task-packet / spec / state 示例)
|
|
124
|
-
skills/ # 10 个 SKILL.md(Agent Skills 标准)+ embed.go
|
|
125
|
-
scripts/ # npm 分发安装器(lark-cli 模式)
|
|
126
|
-
drills/ # 实证演练记录(403 降级、缺陷注入)
|
|
125
|
+
|
|
126
|
+
面板、人工命令和 `flywheel_control` 模型工具复用同一组 Go 接口:
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
flywheel control start --task-dir /repo/docs/my-task --cwd /repo
|
|
130
|
+
flywheel control status --task-dir /repo/docs/my-task --cwd /repo
|
|
131
|
+
flywheel control stop --task-dir /repo/docs/my-task --cwd /repo
|
|
132
|
+
flywheel control config --cwd /repo
|
|
133
|
+
flywheel control doctor --task-dir /repo/docs/my-task --cwd /repo --probe
|
|
127
134
|
```
|
|
128
135
|
|
|
129
|
-
|
|
136
|
+
`start` 派生独立后台引擎,日志进入任务目录的 `orchestrator.log`;关闭面板或桌面不等于停止任务。`status` 只读。`stop` 向本次运行提交带随机 token 的停止请求,等待本次引擎退出受管调用栈、释放任务 lease 后才保存停止状态;不按文件里的 PID 盲目发送信号。引擎只管理本次拥有的进程组;已观测的 `CleanupError` 仍保留在证据和指标中、阻止交付,需要按错误记录处理,`stopped` 不代表任意逃逸进程已清理。停止等待最长 45 秒,桌面入口预留 60 秒。
|
|
137
|
+
|
|
138
|
+
控制接口始终返回一个 protocol 1 JSON;`ok` 表示控制操作成功,任务是否完成应看 `state.status`。`config` 返回 `configuration`,`doctor` 返回 `diagnostic`;诊断有阻断项时保留完整报告并返回退出码 1,不能当成空白传输错误。若启动就绪超时,后台引擎可能仍在加载:先刷新状态和查看日志,不要另起重复任务。
|
|
139
|
+
|
|
140
|
+
原有前台/单步 CLI 仍可使用;这些命令应在目标仓库根目录运行:
|
|
130
141
|
|
|
131
|
-
|
|
142
|
+
```sh
|
|
143
|
+
flywheel run docs/my-task
|
|
144
|
+
flywheel run docs/my-task --pause-before review
|
|
145
|
+
flywheel run docs/my-task --only execute --packet M01-T01
|
|
146
|
+
flywheel run docs/my-task --only verify --packet M01-T01
|
|
147
|
+
flywheel status docs/my-task
|
|
148
|
+
flywheel stop docs/my-task
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`--only` 与 `--pause-before` 不能同时使用。前台 `run` 的退出码为 0 成功、1 用法/运行错误、3 升级/预算/单步失败、143 中断;控制命令的操作失败返回 1。
|
|
132
152
|
|
|
133
|
-
|
|
153
|
+
### 已安装的 skills
|
|
134
154
|
|
|
155
|
+
```sh
|
|
156
|
+
flywheel install-skills
|
|
157
|
+
flywheel install-skills --project /path/to/project
|
|
158
|
+
flywheel skills list
|
|
159
|
+
flywheel skills read flywheel-new
|
|
135
160
|
```
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
161
|
+
|
|
162
|
+
一条命令安装已包含全部 10 个 skills。默认把 Go 内嵌快照安装到 `~/.agents/skills`,`--project` 改为项目的 `.agents/skills`;上述命令可用于补装、检查或显式安装项目范围的副本。
|
|
163
|
+
|
|
164
|
+
每个快照保存带文件内容 hash 的 `.flywheel-snapshot` 清单。重跑先校验当前树:未改过的自有快照可更新,相同内容跳过;用户修改、新增/删除文件、链接或未标记目录都保留并报告冲突。更新按每个 skill 暂存、rename 替换,失败时回滚,不会因为存在一个标记就覆盖用户内容。旧纯文本标记仅在整棵目录与当前内嵌内容一致时迁移。冲突恢复见[升级教程](docs/getting-started.md#8-升级排错与卸载);`--force` 只用于源码 `--link` 模式,不能绕过默认快照保护。
|
|
165
|
+
|
|
166
|
+
安装同时会在全局配置不存在时生成默认配置,已有配置保留。源码开发者才使用 `--link`。skills 只是 CLI 操作按钮,宿主的发现规则和调用语法按其配置使用,不能承担重试或状态机逻辑;安装成功不等于所有宿主已经发现它们。可用按钮涵盖 new、run、plan、exec、review、verify、status、stop、intake、garden。
|
|
167
|
+
|
|
168
|
+
## 5. 什么才算完成
|
|
169
|
+
|
|
170
|
+
执行者可以一起修改实现、单测和回归测试;冻结验收通过 packet 的 `boundaries.must-not-touch` 或配置的 `protected-test-patterns` 显式保护。100% 覆盖率仍是本项目的门禁,断言是否有效由验收和独立评审共同检查。
|
|
171
|
+
|
|
172
|
+
L1 在命令前后检查候选身份。验收过程中代码或契约变了,即使命令 exit 0 也不能继续交付。Review 同样必须针对当前候选;最终 L2/L3 与 Judge 还会核对主 checkout 的前后身份。正常完成需要代码实际提交、合并到本地目标 checkout,并确认提交后的 Git tree 与已验证候选一致;commit hook 修改 index 或文件也不能绕过这一步。任务最终完成前再次检查持久化状态,重启不会把上次验收留下的未提交副作用当成交付。
|
|
173
|
+
|
|
174
|
+
Plan 只能直接修改任务目录内的 Spec 和 packet。它若修改外部验收、实现或运行配置,引擎保留现场并停止,不自动提交或丢弃。修订后的契约 fingerprint 会跨重启保存:旧 completed 单元的契约或验证策略改变时重新打开,早先已关闭的里程碑也会被重新调度。同一 run 对一个里程碑最多访问 3 次,再反复重开就升级给人。最终要求所有当前契约单元完成、里程碑关闭。
|
|
175
|
+
|
|
176
|
+
提交或合并失败保留工作区。交付已持久化而仅工作区清理失败时,可以完成,但必须保留 worktree 引用和 `cleanup-error`,供后续处理。非 Git 演练不能完成。Flywheel 任务引擎本身不会自动 push、发布或合并远端 PR;这些仓库管理操作按使用者的授权另行执行。
|
|
177
|
+
|
|
178
|
+
任务目录保存契约、状态、报告、复盘、升级记录和指标。`state.json` 只有 Go 可以写;执行报告先写在单元 worktree 内,再由 Go 校验并归档。日志、PID、运行锁、停止请求和其他易变运行文件排除在自动提交之外。
|
|
179
|
+
|
|
180
|
+
## 6. 验证与当前边界
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
make test
|
|
184
|
+
make coverage
|
|
185
|
+
make quality-gate
|
|
186
|
+
npm --prefix plugins/dsh ci --ignore-scripts
|
|
187
|
+
npm --prefix plugins/dsh run check
|
|
188
|
+
npm test
|
|
189
|
+
npm run build:npm
|
|
190
|
+
FLYWHEEL_NPM_PACKAGES_DIR=dist-npm npm run test:install
|
|
144
191
|
```
|
|
145
192
|
|
|
146
|
-
|
|
193
|
+
Go 的交付门禁是 vet、格式检查与**逐函数 100% statement coverage**;statement coverage 不等于分支覆盖或真实模型质量。测试使用真实 Git、hook、index、worktree、子进程与协议桩,默认不需要账号或模型调用。
|
|
147
194
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
195
|
+
DSH 检查包括离线 worker 生命周期、事件与 usage 折叠、控制入口一致性、真实 React DOM 交互、Go → Node → SDK 协议桩,以及前轮官方 npm rc.5 的真实 Host 注册和 SDK 初始化/关闭。npm 检查覆盖安装与取消、同版包解析、GUI 安装记录及发行门禁;`test:install` 使用构建出的六个真实 npm 包和隔离环境验证安装,DSH 命令由协议桩替代,不使用用户 profile 或模型账号。
|
|
196
|
+
|
|
197
|
+
React DOM 与安装协议桩不代替 Mac 桌面验证。当前 Linux 环境没有可用的完整原生沙箱,SDK probe 在 prompt 前正确拒绝;这项结果证明拒绝路径,不能冒充 Mac Seatbelt 成功。
|
|
198
|
+
|
|
199
|
+
**尚需在用户 Mac 验证**:桌面安装与 UI 渲染、Seatbelt 探针、GUI 环境中的 CLI/凭证可见性、真实 Flash/GPT/Kimi/Codex 响应,以及真实机器重启后的恢复。当前不支持 DSH worker resume、附件传输、外部 Codex Pro adapter 消费或自动多 agent Teams 调度。
|
|
200
|
+
|
|
201
|
+
## 7. 开发与备用构建
|
|
202
|
+
|
|
203
|
+
普通用户使用上面的 npm 安装流程。开发者需要 Go 1.27 或更高、Node.js 22 或更高及 Git,在仓库根构建:
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
go build -o ./bin/flywheel ./cmd/flywheel
|
|
207
|
+
npm --prefix plugins/dsh ci --ignore-scripts
|
|
208
|
+
npm --prefix plugins/dsh run check
|
|
209
|
+
npm run build:npm
|
|
152
210
|
```
|
|
153
211
|
|
|
154
|
-
|
|
155
|
-
v0.18 起按**逐函数**检查——任何函数 <100% 直接失败,不再只看总数。mock 驱动覆盖全部编排逻辑,
|
|
156
|
-
可在无凭证环境完整运行;verify/watchdog 用真实 git 与真实子进程组测试。
|
|
212
|
+
`build:npm` 先构建 worker,再在隔离目录打包 meta、四个平台包和 DSH 插件,输出到 `dist-npm/`。六个包版本必须一致;本地 tarball 和 Actions artifact 用于开发、验证或备用分发,不是日常用户的安装入口,也不等于已签名的 Mac 安装程序。源码构建及开发模式 skills 说明见[教程末节](docs/getting-started.md#9-开发与备用构建)。
|
|
157
213
|
|
|
158
|
-
##
|
|
214
|
+
## 文档与代码入口
|
|
159
215
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
216
|
+
| 路径 | 内容 |
|
|
217
|
+
|---|---|
|
|
218
|
+
| [docs/getting-started.md](docs/getting-started.md) | Mac 安装、首个任务、配置迁移与排错 |
|
|
219
|
+
| [docs/design.md](docs/design.md) | 权威设计、不变量、闭环与边界 |
|
|
220
|
+
| [docs/configuration.md](docs/configuration.md) | profiles、阶段覆盖、provider/channel 与预检语义 |
|
|
221
|
+
| [docs/plans/2026-11-01-dsh-integration.md](docs/plans/2026-11-01-dsh-integration.md) | 本轮实施和验收记录 |
|
|
222
|
+
| [docs/plans/2026-11-01-model-profiles-preflight.md](docs/plans/2026-11-01-model-profiles-preflight.md) | 配置、预检与面板后续实施计划 |
|
|
223
|
+
| [docs/plans/2026-11-01-npm-installation.md](docs/plans/2026-11-01-npm-installation.md) | npm 安装、GUI 入口与发行验证计划 |
|
|
224
|
+
| [plugins/dsh/README.md](plugins/dsh/README.md) | DSH 安装、独立 SDK profile 与本机检查 |
|
|
225
|
+
| [plugins/dsh/PROTOCOL.md](plugins/dsh/PROTOCOL.md) | worker v1 请求、事件、终态和 usage 契约 |
|
|
226
|
+
| [templates/](templates/) | 契约、配置模板与 JSON Schema |
|
|
227
|
+
| [internal/machine/](internal/machine/) | 状态机、契约修订、证据、生命周期 |
|
|
228
|
+
| [internal/drivers/](internal/drivers/) | DSH/Kimi/Codex 与保留的兼容 driver |
|
|
229
|
+
| [internal/verify/](internal/verify/) | 确定性验收、边界、Git 持久化与工作区 |
|
|
230
|
+
| [docs/design-history-v0.24.md](docs/design-history-v0.24.md) | 历史设计与当时的研究、环境记录 |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright (c) 2026 ckanner. SPDX-License-Identifier: UNLICENSED
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const { loadWorkerPath, runChild } = require("./runtime.js");
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
return runChild(process.execPath, [loadWorkerPath(), ...process.argv.slice(2)], {
|
|
9
|
+
label: "flywheel-dsh-worker",
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
main().then(code => { process.exitCode = code; }).catch(error => {
|
|
14
|
+
console.error(`flywheel-dsh-worker: ${error.message}`);
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
});
|
package/bin/flywheel.js
CHANGED
|
@@ -1,33 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Copyright (c) 2026 ckanner. SPDX-License-Identifier: UNLICENSED
|
|
3
|
-
//
|
|
4
|
-
// npm bin shim(v0.22 平台包模式,esbuild 先例):按 platform/arch 解析
|
|
5
|
-
// optionalDependencies 里的平台二进制包并透传执行——无 postinstall、无网络
|
|
6
|
-
// 下载、无 checksum 步骤(npm 自身的包完整性承担校验)。安装即得二进制,
|
|
7
|
-
// 维护者与分用户用法完全一致。
|
|
8
3
|
"use strict";
|
|
9
4
|
|
|
10
|
-
const {
|
|
5
|
+
const { loadNativePath, loadWorkerPath, runChild } = require("./runtime.js");
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
const
|
|
7
|
+
async function main() {
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
if (args[0] === "install" || args[0] === "setup") {
|
|
10
|
+
return require("../npm/install.js").main(args.slice(1));
|
|
11
|
+
}
|
|
14
12
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
const env = { ...process.env };
|
|
14
|
+
// An npm dependency's bin is not necessarily on a desktop application's
|
|
15
|
+
// PATH. Resolve this installation's worker without changing an explicit
|
|
16
|
+
// user override or relying on the shell's global npm prefix.
|
|
17
|
+
if (env.FLYWHEEL_DSH_WORKER === undefined) {
|
|
18
|
+
env.FLYWHEEL_DSH_WORKER = loadWorkerPath();
|
|
19
|
+
}
|
|
20
|
+
return runChild(loadNativePath(), args, { env });
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
} catch (_) {
|
|
27
|
-
console.error(`flywheel: 平台包 ${pkg} 未安装。npm 会按 os/cpu 自动选取 optionalDependencies——`);
|
|
28
|
-
console.error(` 若被 --no-optional 跳过,请去掉该标志重装:npm i -g @ckanner/flywheel`);
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const result = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
33
|
-
process.exit(result.status === null ? 1 : result.status);
|
|
23
|
+
main().then(code => { process.exitCode = code; }).catch(error => {
|
|
24
|
+
console.error(`flywheel: ${error.message}`);
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
});
|
package/bin/runtime.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Copyright (c) 2026 ckanner. SPDX-License-Identifier: UNLICENSED
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawn } = require("node:child_process");
|
|
5
|
+
const { constants } = require("node:os");
|
|
6
|
+
|
|
7
|
+
const platforms = { darwin: "darwin", linux: "linux" };
|
|
8
|
+
const architectures = { arm64: "arm64", x64: "amd64" };
|
|
9
|
+
const forwardedSignals = ["SIGTERM", "SIGINT", "SIGHUP"];
|
|
10
|
+
const ownPackage = require("../package.json");
|
|
11
|
+
|
|
12
|
+
function packageEntry(name, entry, expectedVersion, missingMessage) {
|
|
13
|
+
let installed;
|
|
14
|
+
let entryPath;
|
|
15
|
+
try {
|
|
16
|
+
installed = require(`${name}/package.json`);
|
|
17
|
+
entryPath = require.resolve(`${name}/${entry}`);
|
|
18
|
+
} catch {
|
|
19
|
+
throw new Error(missingMessage);
|
|
20
|
+
}
|
|
21
|
+
// npm may legitimately hoist dependencies. Check the resolved package's
|
|
22
|
+
// identity, rather than assuming a particular machine's node_modules path.
|
|
23
|
+
// A partial installation must not pick up an incompatible parent package.
|
|
24
|
+
if (installed.name !== name) {
|
|
25
|
+
throw new Error(`package identity mismatch for ${name}: found ${installed.name}; reinstall @ckanner/flywheel`);
|
|
26
|
+
}
|
|
27
|
+
if (typeof expectedVersion !== "string" || installed.version !== expectedVersion) {
|
|
28
|
+
throw new Error(`version mismatch for ${name}: expected ${expectedVersion}, found ${installed.version}; reinstall @ckanner/flywheel`);
|
|
29
|
+
}
|
|
30
|
+
return entryPath;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function loadNativePath() {
|
|
34
|
+
const platform = platforms[process.platform];
|
|
35
|
+
const arch = architectures[process.arch];
|
|
36
|
+
if (!platform || !arch) {
|
|
37
|
+
throw new Error(`unsupported platform ${process.platform}/${process.arch} (supported: darwin/linux × amd64/arm64)`);
|
|
38
|
+
}
|
|
39
|
+
const name = `@ckanner/flywheel-${platform}-${arch}`;
|
|
40
|
+
return packageEntry(name, "flywheel", ownPackage.optionalDependencies?.[name],
|
|
41
|
+
`platform package ${name} is missing; reinstall @ckanner/flywheel with optional dependencies enabled`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function loadWorkerPath() {
|
|
45
|
+
const name = "@ckanner/dsh-plugin-flywheel";
|
|
46
|
+
return packageEntry(name, "worker", ownPackage.dependencies?.[name],
|
|
47
|
+
"the matching @ckanner/dsh-plugin-flywheel worker is missing; reinstall @ckanner/flywheel");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function signalExitCode(signal) {
|
|
51
|
+
return constants.signals[signal] ? 128 + constants.signals[signal] : 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Preserve the native owner's cancellation and cleanup lifetime through npm. */
|
|
55
|
+
function runChild(command, args, { env = process.env, cwd, label = "flywheel" } = {}) {
|
|
56
|
+
return new Promise(resolve => {
|
|
57
|
+
const child = spawn(command, args, { env, cwd, stdio: "inherit", shell: false, detached: false });
|
|
58
|
+
let cancelledBy;
|
|
59
|
+
let spawnFailed = false;
|
|
60
|
+
const listeners = new Map();
|
|
61
|
+
for (const signal of forwardedSignals) {
|
|
62
|
+
const listener = () => {
|
|
63
|
+
cancelledBy ??= signal;
|
|
64
|
+
// Go owns its worker groups. Keep this wrapper alive until Go confirms
|
|
65
|
+
// its own exit; killing only this PID would orphan that cleanup stack.
|
|
66
|
+
child.kill(signal);
|
|
67
|
+
};
|
|
68
|
+
listeners.set(signal, listener);
|
|
69
|
+
process.on(signal, listener);
|
|
70
|
+
}
|
|
71
|
+
child.once("error", error => {
|
|
72
|
+
spawnFailed = true;
|
|
73
|
+
console.error(`${label}: could not start ${command}: ${error.message}`);
|
|
74
|
+
});
|
|
75
|
+
child.once("close", (code, signal) => {
|
|
76
|
+
for (const [name, listener] of listeners) process.removeListener(name, listener);
|
|
77
|
+
if (spawnFailed) return resolve(1);
|
|
78
|
+
if (signal) return resolve(signalExitCode(signal));
|
|
79
|
+
// A child trapping termination as exit 0 still cannot turn cancellation
|
|
80
|
+
// into success. Preserve the native program's nonzero failure otherwise.
|
|
81
|
+
if (cancelledBy && code === 0) return resolve(signalExitCode(cancelledBy));
|
|
82
|
+
resolve(code ?? 1);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { loadNativePath, loadWorkerPath, runChild };
|