@ckanner/dsh-plugin-flywheel 0.28.1 → 0.28.2
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/PROTOCOL.md +6 -6
- package/README.en.md +30 -13
- package/README.md +7 -5
- package/dist/client.js +64 -42
- package/dist/codex-pro-bridge.js +4 -2
- package/dist/policy.js +10 -8
- package/dist/protocol.js +6 -4
- package/package.json +1 -1
package/PROTOCOL.md
CHANGED
|
@@ -18,7 +18,7 @@ Go 在工作目录启动 `flywheel-dsh-worker`,写入**一条** UTF-8 JSON 行
|
|
|
18
18
|
| `maxTokens` | 正整数,每次原生模型请求的输出上限,传入 SDK initialize |
|
|
19
19
|
| `timeoutMs` | 1–86,400,000,默认 1,800,000;涵盖权限准备、初始化、运行和关闭 |
|
|
20
20
|
|
|
21
|
-
`permissionMode`
|
|
21
|
+
`permissionMode` 允许 `read-only`、`workspace-write` 和显式 `danger-full-access`。未知字段一律拒绝。v1 不接受 `sessionId` / resume、附件、任意额外 CLI 参数和额外写目录;不进行隐式冷启动替代恢复。
|
|
22
22
|
|
|
23
23
|
## 运行参数
|
|
24
24
|
|
|
@@ -46,12 +46,12 @@ worker 用 `dsh --profile <profile> --patch <临时覆盖文件>` 启动 DSH。
|
|
|
46
46
|
发出 ready 前必须依次满足:
|
|
47
47
|
|
|
48
48
|
1. 官方 SDK `initialize` 返回 `deepseek-harness-sdk-runtime`,adapter 接受指定 provider/model/effort。
|
|
49
|
-
2. worker 策略插件经真实 `ctx.shell` 执行本地无副作用的 `:` 探针;退出码为 0
|
|
49
|
+
2. worker 策略插件经真实 `ctx.shell` 执行本地无副作用的 `:` 探针;退出码为 0、模式匹配且未被拒绝。受限模式还须报告 `sandbox.enforcement:full`,不可用或部分实施的沙箱拒绝通过;明确完全权限时记录 `confinedShell:false` 并省略 enforcement,不伪造 OS 强制。
|
|
50
50
|
3. `session/working-directory/get` 创建全新的 Session,返回实际 cwd。
|
|
51
51
|
4. Session 的 `sandbox/mode`、`approval/policy` 被固定,`ctx.sandboxPolicy.resolve()` 与 `ctx.approval.effectivePolicy()` 的实际读值形成 `flywheel/policy` 事件。
|
|
52
|
-
5. worker 比对 policy 事件的 cwd、mode、`never`
|
|
52
|
+
5. worker 比对 policy 事件的 cwd、mode、`never` 及对应权限证据:受限模式为完整强制,完全权限为明确无 OS 限制。
|
|
53
53
|
|
|
54
|
-
任一项缺失,不发送 `session/prompt`。`never`
|
|
54
|
+
任一项缺失,不发送 `session/prompt`。`never` 控制交互审批,不单独扩大访问范围;必须同时显式选择 `danger-full-access` 才请求完全权限。既有静态拒绝或外部权限不足仍按真实失败处理。
|
|
55
55
|
|
|
56
56
|
### event:可审计的原始持久事件
|
|
57
57
|
|
|
@@ -99,7 +99,7 @@ Go 的 `DSHArgs.Doctor` 使用这个入口;为保持 v1 必填 `prompt` 的 ad
|
|
|
99
99
|
{"type":"start","protocol":1,"requestId":"call-123","cwd":"/absolute/worktree","model":"gpt-6.1-sol","reasoningEffort":"high","permissionMode":"workspace-write","prompt":"执行冻结工作单元","timeoutMs":1800000}
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
只接受这些字段;不接受 provider、resume、threadId、额外写目录、任意 CLI
|
|
102
|
+
只接受这些字段;不接受 provider、resume、threadId、额外写目录、任意 CLI 参数或未知权限模式。requestId 非空且最多 256 字符;cwd 为绝对路径,Go 先规范化成物理目录。model/reasoningEffort/prompt 必须是非空文本,permissionMode 为 read-only/workspace-write/danger-full-access;最后一项在 Node adapter 边界映射为 machine API 的 `dangerously-bypass-approvals-and-sandbox`,Go 同时核对映射后的 requested 字段和 canonical 生效模式。timeoutMs 显式值必须为 1–2,147,483,647 的安全整数,省略使用上游 600,000 毫秒;worker 不接受通过 0 关闭期限。Go 传递有效 wall deadline 和 high 默认 thinking;worker 将 reasoningEffort 映射到 machine 的 effort,固定 threadMode=ephemeral、command=`FLYWHEEL_CODEX_BIN` 或 `codex`,不恢复旧线程。
|
|
103
103
|
|
|
104
104
|
**这里与 DSH worker 的 EOF 语义不同:Go 写入一行后保持 stdin 开放。** stdin 是父进程的存活连接;未完成前 EOF、读取错误或第二条非空输入触发 AbortSignal,并等待插件有界清理。不能用 `echo … | worker` 的关闭 stdin 行为冒充正常调用。运行请求首行等待上限 10 秒;请求最多 1 MiB,machine 原始输出上限 1 MiB,序列化 progress 累计最多 1 MiB,完整终态帧最多 8 MiB。超限明确失败,不能裁掉回执后判成功。
|
|
105
105
|
|
|
@@ -119,7 +119,7 @@ Go 的 `DSHArgs.Doctor` 使用这个入口;为保持 v1 必填 `prompt` 的 ad
|
|
|
119
119
|
|
|
120
120
|
启动前不能获得 machine receipt 时为 null。运行已开始则即使失败也保留完整 receipt;`error` 非 null、重复/丢失 result、尾部垃圾、请求身份或 adapter 版本不符都不能成功。本通道没有 DSH `ready` 帧,不能据此虚构运行前完整沙箱握手或实际 effort 回显。
|
|
121
121
|
|
|
122
|
-
Go 同时校验外层 worker 物理终态和插件 receipt:请求及 run/thread/turn 身份、模型、cwd、approval=never、实际沙箱模式、完整 sandboxPolicy、stopReason、cleanup
|
|
122
|
+
Go 同时校验外层 worker 物理终态和插件 receipt:请求及 run/thread/turn 身份、模型、cwd、approval=never、实际沙箱模式、完整 sandboxPolicy、stopReason、cleanup 与执行事实。受限模式的 workspaceWrite 显式 writableRoots 必须处于工作区内,networkAccess 必须明确 false;显式完全权限要求 `sandbox:danger-full-access` 与精确的 `{type:"dangerFullAccess"}` policy,不要求不存在的受限模式字段;Codex 标准临时目录标记保留,不宣称只能写 cwd。插件 0.8.0 只在 turn 前检查模式等基本配置;完整策略是返回后的交付资格检查,**不能阻止自定义 Codex 配置已经允许的访问**。
|
|
123
123
|
|
|
124
124
|
effort 的证据只可为 turn/start 请求接受,保留 `effectiveEffort:null`。usage 使用 `run` 增量,不能把 threadTotal 当成本次消耗;只有 complete 才记入可知预算,partial/unknown 原样存入 receipt 并增加未知调用计数。cached input/reasoning output 是总 input/output 的子项,不重复累加。订阅 costUSD 保持 null、costStatus 保持 unknown。receipt 没有实际 provider 时保持未知,不根据 channel 发明 provider。
|
|
125
125
|
|
package/README.en.md
CHANGED
|
@@ -7,7 +7,7 @@ provides its DSH SDK and Codex Pro workers. The engine owns task state, budgets,
|
|
|
7
7
|
verification, Git delivery, and process lifecycles. The desktop is a control and
|
|
8
8
|
observation interface; it does not need a parent model session to keep a task alive.
|
|
9
9
|
|
|
10
|
-
This guide describes the **Flywheel 0.28.
|
|
10
|
+
This guide describes the **Flywheel 0.28.2** interfaces. Source availability does
|
|
11
11
|
not mean that npm `latest` already contains them. Check the registry version and
|
|
12
12
|
the [release guide](https://github.com/ckanner/flywheel/blob/main/docs/en/releasing.md) before installing. The desktop
|
|
13
13
|
compatibility baseline is **DSH Mac 0.2.1-rc.5**, not a claim that every DSH build
|
|
@@ -17,6 +17,8 @@ or provider adapter is compatible.
|
|
|
17
17
|
uses the composer popup. Upgrade the CLI and desktop plugin to the same new
|
|
18
18
|
version; merging source does not update an installed plugin.
|
|
19
19
|
|
|
20
|
+
Subscription preset migration and `permissions.mode` are available from **0.28.2**. Upgrades preserve existing configuration; only an explicit migration backs it up and updates model bindings and permissions.
|
|
21
|
+
|
|
20
22
|
## Components
|
|
21
23
|
|
|
22
24
|
| Component | Responsibility |
|
|
@@ -162,10 +164,14 @@ actual inference result. Keep `warn`, `unknown`, and `skipped` distinct from
|
|
|
162
164
|
|
|
163
165
|
Open a DSH session and choose **Flywheel** in the native view bar alongside
|
|
164
166
|
Chat and Trajectory. If the entry is not directly visible, check that bar's
|
|
165
|
-
More menu.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
More menu. Ask the chat assistant to prepare and review the task using
|
|
168
|
+
`flywheel-new`; it reports the actual paths. **Task files directory** contains
|
|
169
|
+
the task description and acceptance files, for example
|
|
170
|
+
`/Users/me/project/docs/fix-login`. **Project directory (optional)** can stay
|
|
171
|
+
blank to use the current DSH session directory; specify the matching project
|
|
172
|
+
worktree when the task lives elsewhere. Configuration and diagnostic actions
|
|
173
|
+
can also leave the task directory blank. These are project paths, not plugin
|
|
174
|
+
installation paths.
|
|
169
175
|
|
|
170
176
|
The panel, `/flywheel`, and `flywheel_control` share these Go operations:
|
|
171
177
|
|
|
@@ -188,6 +194,13 @@ entry point, select its task directory and refresh. Effective configuration come
|
|
|
188
194
|
from the engine's selected file; installing the plugin or changing DSH's chat
|
|
189
195
|
model does not migrate that configuration.
|
|
190
196
|
|
|
197
|
+
**Check installation and configuration** performs local checks without starting
|
|
198
|
+
the SDK. **Check DSH connection** additionally starts a temporary SDK process,
|
|
199
|
+
checks its configuration, directory and permissions, and closes it. Use these
|
|
200
|
+
when installing, upgrading or troubleshooting; they are not required before
|
|
201
|
+
every task. Neither sends an inference prompt or proves account access, quota
|
|
202
|
+
or actual model response capability.
|
|
203
|
+
|
|
191
204
|
### View, request, and task lifetimes
|
|
192
205
|
|
|
193
206
|
Task and repository drafts and fetched snapshots are retained separately for
|
|
@@ -271,12 +284,15 @@ Conversely, desktop installation and sign-in do not prove that the SDK profile
|
|
|
271
284
|
loads and authorizes the same capabilities. Verify each selected harness through
|
|
272
285
|
its own configuration and supported authorization flow.
|
|
273
286
|
|
|
274
|
-
The DSH worker
|
|
275
|
-
`
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
287
|
+
The DSH worker supports `read-only`, `workspace-write`, and explicit
|
|
288
|
+
`danger-full-access`, with approvals fixed to `never`. New subscription presets
|
|
289
|
+
select `permissions.mode:full-access` for every stage; Kimi print retains native
|
|
290
|
+
auto behavior. Existing files without this setting retain role-based access
|
|
291
|
+
until explicitly migrated. The actual SDK policy is checked before prompting.
|
|
292
|
+
Restricted modes use the checked Mac Seatbelt runner and filesystem fence;
|
|
293
|
+
workspace writes include DSH's permitted temporary area. Explicit full access
|
|
294
|
+
is recorded without claiming OS confinement. Host static denials, account
|
|
295
|
+
restrictions, and operating-system permissions remain in effect.
|
|
280
296
|
|
|
281
297
|
The SDK overlay disables worker-owned delegation, workflow/goal drivers, model
|
|
282
298
|
titles, and automatic model compaction so the Go owner can bound work and
|
|
@@ -286,14 +302,15 @@ not a claim that every DSH web operation requires a model: the official
|
|
|
286
302
|
[web tool](https://github.com/deepseek-ai/deepseek-harness/blob/fcc0ad462172522aaf5c93a7b2e01f6b9f0b0159/packages/web/tool-web/README.md)
|
|
287
303
|
configures search and fetch independently, while the
|
|
288
304
|
[DeepSeek search backend](https://github.com/deepseek-ai/deepseek-harness/blob/fcc0ad462172522aaf5c93a7b2e01f6b9f0b0159/packages/web/web-search-deepseek/README.md)
|
|
289
|
-
does use an auxiliary model request.
|
|
305
|
+
does use an auxiliary model request. Full access changes the host access policy;
|
|
306
|
+
these tool and auxiliary-model restrictions still apply.
|
|
290
307
|
Restoring retrieval or [automatic compaction](https://github.com/deepseek-ai/deepseek-harness/blob/fcc0ad462172522aaf5c93a7b2e01f6b9f0b0159/packages/compaction/compaction-basic/README.md)
|
|
291
308
|
requires a separate check of accounting, budgets, cancellation, and directory
|
|
292
309
|
ownership; the view's location does not establish those guarantees.
|
|
293
310
|
|
|
294
311
|
Codex Pro uses fresh ephemeral calls and checks the model, directory, approval
|
|
295
312
|
policy, and sandbox mode before execution. In version 0.8.0, the full
|
|
296
|
-
`sandboxPolicy` reaches Flywheel only in the returned receipt. Flywheel rejects
|
|
313
|
+
`sandboxPolicy` reaches Flywheel only in the returned receipt. In restricted modes, Flywheel rejects
|
|
297
314
|
delivery when that policy permits unexpected writable roots or network access,
|
|
298
315
|
but **this check happens after execution and cannot undo access already
|
|
299
316
|
permitted by custom Codex configuration**. It is not a complete pre-execution
|
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
**Flywheel 0.28.1 引入原生会话视图**;0.28.0 仍使用输入区底部弹层。需要升级 CLI 与桌面插件到同一新版本,源码合并不会自动更新已安装的插件。
|
|
10
10
|
|
|
11
|
+
订阅预设迁移命令和 `permissions.mode` 从 **0.28.2** 开始提供。升级保留已有配置;只有用户明确应用迁移时才备份并更新模型与权限。
|
|
12
|
+
|
|
11
13
|
## 提供什么
|
|
12
14
|
|
|
13
15
|
| 部分 | 行为 |
|
|
@@ -84,7 +86,7 @@ reasoningEffort = off | low | high | max
|
|
|
84
86
|
|
|
85
87
|
npm 启动器自动解析并注入 `FLYWHEEL_CODEX_PRO_WORKER`。独立 Go 二进制默认从 PATH 查找 `flywheel-codex-pro-worker`,源码开发可显式设为构建后的 `dist/codex-pro-worker.js`。`FLYWHEEL_CODEX_BIN` 可指定底层 Codex 可执行程序,默认 `codex`;`CODEX_HOME` 沿用用户选择的配置/登录目录。Go 会将这些运行必需项交给插件,不把凭证复制到配置、契约或 Git。
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
新调用使用 ephemeral 会话,权限由 `permissions.mode` 决定,approval 为 never。role-based 选择 `read-only`/`workspace-write`;full-access 请求 `danger-full-access`。插件运行前核对模型、cwd 和权限模式,完整 sandboxPolicy 由 Flywheel 在返回后审查。受限模式允许 Codex 标准临时目录、拒绝工作区外额外 roots 与网络启用;完全权限则核对 `dangerFullAccess` 策略,不声称 OS 隔离。0.8.0 尚无公开的运行前完整策略 guard,事后拒绝交付不能阻止先前的访问;可信 Codex 配置仍是本机运行前提。
|
|
88
90
|
|
|
89
91
|
完整 machine receipt 写入指标,保留 accepted effort、未知 effectiveEffort、usage 完整性与进程终态。插件正常收尾可能主动 TERM/KILL app-server,其物理 exit code 可以为空;Flywheel 只有在完整终态证明任务完成、清理已确认且外层 worker 干净退出时才能继续。取消、超时、截断、启动或清理失败均阻止成功。Go 为此 worker 预留至少 10 秒终止宽限,避免截断插件内部清理。
|
|
90
92
|
|
|
@@ -121,7 +123,7 @@ node --input-type=module -e 'console.log(JSON.stringify({type:"start",protocol:1
|
|
|
121
123
|
|
|
122
124
|
## 桌面使用
|
|
123
125
|
|
|
124
|
-
打开一个 DSH 会话,在顶部“对话/轨迹”所在的视图栏选择 **Flywheel**。入口由 DSH
|
|
126
|
+
打开一个 DSH 会话,在顶部“对话/轨迹”所在的视图栏选择 **Flywheel**。入口由 DSH 的原生视图栏管理;若未直接显示,检查该栏的更多视图入口。先在“对话”中让助手按 `flywheel-new` 准备任务说明与验收清单,审阅后由助手给出实际路径。“任务资料目录”就是这些文件所在的目录,例如 `/Users/me/project/docs/fix-login`,不是插件安装目录。“项目目录(可选)”默认取当前 DSH 会话目录,可留空;任务在另一份 worktree 时填对应的项目路径,例如 `/Users/me/project`。只看配置或做检查时,任务资料目录也可留空。按钮只执行以下 Go JSON 控制接口:
|
|
125
127
|
|
|
126
128
|
```sh
|
|
127
129
|
flywheel control status --task-dir /repo/docs/my-task --cwd /repo
|
|
@@ -139,7 +141,7 @@ flywheel control doctor --task-dir /repo/docs/my-task --cwd /repo --probe
|
|
|
139
141
|
|
|
140
142
|
模型工具 `flywheel_control` 接受 `action`、`taskDir`、`cwd`,以及仅用于 doctor 的可选 `probe` 布尔值。config/doctor 可省 taskDir,cwd 可沿用会话目录;start/status/stop 必须指定任务。输入统一校验,使用 argv 调用,不拼接 shell 命令。打开视图不会启动任务或探测;视图只在点击时读取状态,不自动轮询,也不自动发现其他入口启动的任务。查看已有任务时,选择同一任务目录再刷新。
|
|
141
143
|
|
|
142
|
-
|
|
144
|
+
“查看模型配置”展示实际配置来源、展开后的阶段绑定及 provider/channel/权限。“检查安装与配置”仅做本地预检,不启动 SDK;“检查 DSH 接入”额外临时启动 SDK、核对配置/目录/权限后退出。首次安装、升级或排障时按需使用,平时不必每次点击;两项都不向模型发送消息,也不验证账号、额度和实际回答能力。doctor 的合法退出码 1 会显示逐项诊断,超时、坏协议或不匹配的任务身份仍视为控制错误。`ok:true` 不代表账号、额度和真实推理通过。
|
|
143
145
|
|
|
144
146
|
状态区区分未初始化、运行、已完成、暂停、停止、升级和预算停止;全部 packet 可见,不会只展示前几个。未知 usage/cost 明确保留,原始 JSON 默认折叠。配置显示引擎实际选择的文件及阶段绑定;已有用户配置不会因安装或切换 DSH 对话模型而自动迁移。
|
|
145
147
|
|
|
@@ -165,9 +167,9 @@ flywheel control doctor --task-dir /repo/docs/my-task --cwd /repo --probe
|
|
|
165
167
|
|
|
166
168
|
每个调用使用新的 SDK Session;provider/model/effort 在该进程中固定。v1 不支持 resume、图像/附件输入或 SDK 原生 output schema。文本与报告文件由 Go 的冻结合同和 JSON Schema 验收,不能把 SDK idle 当成验收完成。
|
|
167
169
|
|
|
168
|
-
worker
|
|
170
|
+
worker 支持 `read-only`、`workspace-write` 和显式 `danger-full-access`。新订阅预设通过 `permissions.mode:full-access` 为全部阶段选择完全权限,approval 固定为 `never`;Kimi print 使用原生 auto,不逐步请求用户点击允许。已有文件缺省仍保留 role-based,需通过[配置迁移](../../docs/configuration.md#将旧配置切换到已有的三种资源)显式采用新方案。受限模式由 Mac 上 DSH 的 Seatbelt bash runner 和 native fs fence 执行,workspace-write 范围是**实际工作目录加 DSH 规定的系统临时区**。full-access 如实记录无 OS 沙箱限制;宿主静态拒绝、账号与操作系统权限不被绕过。
|
|
169
171
|
|
|
170
|
-
|
|
172
|
+
执行报告仍先写到 worktree 内的任务报告路径,由 Go 校验后归档到主任务目录。权限模式来自明确配置,不能因为报告写入失败而暗中扩大它;任务边界、冻结验收和候选身份检查保持有效。
|
|
171
173
|
|
|
172
174
|
默认覆盖会关闭 DSH 自主委派、workflow、goal 驱动、模型生成标题及自动模型压缩,避免在一个 Go 工作单元之外再启动辅助模型调用。需要压缩/继续执行时由 Go 显式决定下一次任务;超出上下文或输出 token 限制不会伪装成成功。SDK profile 应只装所需的可信 provider /工具,第三方 Host 插件本身是进程内代码,不能由本包替它提供任意代码隔离。
|
|
173
175
|
|
package/dist/client.js
CHANGED
|
@@ -96,26 +96,28 @@ function createViewStore() {
|
|
|
96
96
|
|
|
97
97
|
const zh = {
|
|
98
98
|
view: 'Flywheel',
|
|
99
|
-
title: '
|
|
100
|
-
description: '
|
|
99
|
+
title: 'Flywheel 任务',
|
|
100
|
+
description: '准备任务后,在这里查看进度、启动、继续或停止',
|
|
101
101
|
lifecycle: '任务在后台独立运行。切换视图或会话不会停止任务;需要停止时请使用这里的“停止”',
|
|
102
|
-
selection: '
|
|
103
|
-
taskDir: '
|
|
104
|
-
taskHint: '
|
|
105
|
-
cwd: '
|
|
106
|
-
cwdHint: '
|
|
107
|
-
pathInvalid: '
|
|
102
|
+
selection: '打开已有任务',
|
|
103
|
+
taskDir: '任务资料目录',
|
|
104
|
+
taskHint: '填写助手准备任务时给出的完整路径,里面保存任务说明和验收清单;只查看配置或做检查时可留空',
|
|
105
|
+
cwd: '项目目录(可选)',
|
|
106
|
+
cwdHint: '默认使用当前 DSH 会话的项目目录,可留空。任务在其他项目或工作区时,填写对应的完整路径',
|
|
107
|
+
pathInvalid: '请填写以 / 开头的完整路径',
|
|
108
108
|
pathControl: '路径不能包含换行或空字符',
|
|
109
|
-
taskRequired: '
|
|
109
|
+
taskRequired: '填写任务资料目录后,可刷新状态、启动或停止任务',
|
|
110
110
|
status: '刷新状态',
|
|
111
111
|
start: '启动',
|
|
112
112
|
stop: '停止',
|
|
113
113
|
config: '查看模型配置',
|
|
114
|
-
doctor: '
|
|
115
|
-
probe: '
|
|
114
|
+
doctor: '检查安装与配置',
|
|
115
|
+
probe: '检查 DSH 接入',
|
|
116
|
+
doctorHelp: '检查安装与配置:核对本机工具、模型配置和所选任务文件,不会启动任务',
|
|
117
|
+
probeHelp: '检查 DSH 接入:临时启动独立的 DSH 运行环境,检查配置、项目目录和权限后退出;可能联网读取模型目录',
|
|
116
118
|
busy: '正在等待“{action}”确认…',
|
|
117
119
|
busyOther: '正在等待上一个目录的“{action}”结束;其结果不会覆盖当前选择',
|
|
118
|
-
mutationLock: '
|
|
120
|
+
mutationLock: '启动或停止确认前,暂时不能切换任务资料目录或项目目录',
|
|
119
121
|
unknownOutcome: '“{action}”结果未确认',
|
|
120
122
|
unknownHelp: '请求可能已经送达引擎。先刷新这个任务的状态,再决定下一步操作',
|
|
121
123
|
reconcile: '刷新待确认任务',
|
|
@@ -127,7 +129,7 @@ const zh = {
|
|
|
127
129
|
feedback: '操作反馈',
|
|
128
130
|
statusHeading: '任务状态',
|
|
129
131
|
emptyHeading: '还没有任务状态',
|
|
130
|
-
emptyBody: '
|
|
132
|
+
emptyBody: '先在“对话”里让助手按 flywheel-new 准备任务,审阅任务说明和验收标准,再填写助手给出的任务资料目录。已有任务可直接使用原目录',
|
|
131
133
|
emptySelected: '点击“刷新状态”查看这个任务;打开本页不会自动启动任务',
|
|
132
134
|
lastChecked: '上次确认:{time}',
|
|
133
135
|
stale: '本次状态未确认,下方保留上次结果',
|
|
@@ -171,12 +173,13 @@ const zh = {
|
|
|
171
173
|
tokensMissing: ';{count} 次调用用量未知,合计不完整',
|
|
172
174
|
tokenLimit: '每日 Token 限额:{limit} · 预算策略:{policy}',
|
|
173
175
|
budgetWarning: '预算提示:{message}',
|
|
174
|
-
advanced: '
|
|
175
|
-
advancedHelp: '
|
|
176
|
+
advanced: '配置与排查(按需)',
|
|
177
|
+
advancedHelp: '首次安装、升级或运行失败时可用,平时不必每次点击;两项检查都不向模型发送消息,也不能验证账号、额度或实际回答能力',
|
|
176
178
|
configuration: '实际模型配置',
|
|
179
|
+
configurationHelp: '这里显示 Flywheel 从下方来源读取的实际生效配置;DSH 对话输入框当前选择的模型不会替代它',
|
|
177
180
|
source: '配置来源:{source}',
|
|
178
181
|
embedded: '内嵌模板',
|
|
179
|
-
scope: '
|
|
182
|
+
scope: '配置层级:{scope}',
|
|
180
183
|
outOfScope: '未用于本次检查',
|
|
181
184
|
model: '模型',
|
|
182
185
|
profile: 'Profile',
|
|
@@ -185,6 +188,9 @@ const zh = {
|
|
|
185
188
|
channel: '渠道',
|
|
186
189
|
thinking: '推理',
|
|
187
190
|
permission: '阶段权限要求',
|
|
191
|
+
permissionFull: '完全权限(无需逐步确认)',
|
|
192
|
+
permissionWorkspace: '项目内写入',
|
|
193
|
+
permissionReadOnly: '只读',
|
|
188
194
|
session: '会话恢复',
|
|
189
195
|
enabled: '启用',
|
|
190
196
|
disabled: '关闭',
|
|
@@ -202,9 +208,9 @@ const zh = {
|
|
|
202
208
|
diagnostic: '环境检查结果',
|
|
203
209
|
doctorPassed: '检查结束:未发现阻断项',
|
|
204
210
|
doctorFailed: '检查结束:存在阻断项',
|
|
205
|
-
localOnly: '
|
|
206
|
-
probeRequested: '已请求
|
|
207
|
-
inferenceBoundary: '
|
|
211
|
+
localOnly: '本次检查本机工具、配置和所选任务文件,未启动 DSH 接入检查',
|
|
212
|
+
probeRequested: '已请求 DSH 接入检查(不调用模型)',
|
|
213
|
+
inferenceBoundary: '没有向模型发送消息;账号授权、可用额度和实际回答能力仍未验证',
|
|
208
214
|
pass: '通过',
|
|
209
215
|
fail: '失败',
|
|
210
216
|
warn: '注意',
|
|
@@ -216,26 +222,28 @@ const zh = {
|
|
|
216
222
|
};
|
|
217
223
|
const en = {
|
|
218
224
|
view: 'Flywheel',
|
|
219
|
-
title: '
|
|
220
|
-
description: '
|
|
225
|
+
title: 'Flywheel tasks',
|
|
226
|
+
description: 'After preparing a task, inspect its progress, start, resume or stop it here',
|
|
221
227
|
lifecycle: 'Tasks run independently in the background. Switching views or sessions does not stop a task; use Stop here when needed.',
|
|
222
|
-
selection: '
|
|
223
|
-
taskDir: 'Task directory',
|
|
224
|
-
taskHint: '
|
|
225
|
-
cwd: '
|
|
226
|
-
cwdHint: 'Leave blank to use this DSH session
|
|
227
|
-
pathInvalid: 'Enter
|
|
228
|
+
selection: 'Open an existing task',
|
|
229
|
+
taskDir: 'Task files directory',
|
|
230
|
+
taskHint: 'Use the full path supplied by the assistant when it prepared the task instructions and acceptance checks; leave blank to inspect configuration or run diagnostics',
|
|
231
|
+
cwd: 'Project directory (optional)',
|
|
232
|
+
cwdHint: 'Leave blank to use the project directory of this DSH session. If the task belongs to another project or worktree, enter that full path.',
|
|
233
|
+
pathInvalid: 'Enter a full path beginning with /',
|
|
228
234
|
pathControl: 'Paths cannot contain line breaks or null characters',
|
|
229
|
-
taskRequired: 'Enter
|
|
235
|
+
taskRequired: 'Enter the task files directory to refresh status, start or stop the task',
|
|
230
236
|
status: 'Refresh status',
|
|
231
237
|
start: 'Start',
|
|
232
238
|
stop: 'Stop',
|
|
233
239
|
config: 'Inspect model configuration',
|
|
234
|
-
doctor: 'Check
|
|
235
|
-
probe: '
|
|
240
|
+
doctor: 'Check installation and configuration',
|
|
241
|
+
probe: 'Check DSH integration',
|
|
242
|
+
doctorHelp: 'Installation and configuration: check local tools, model settings and selected task files without starting the task.',
|
|
243
|
+
probeHelp: 'DSH integration: briefly start a separate DSH runtime, verify its configuration, project directory and permissions, then close it. It may fetch a model catalog online.',
|
|
236
244
|
busy: 'Waiting for “{action}” confirmation…',
|
|
237
245
|
busyOther: 'Waiting for “{action}” for the previous directory; its result will not replace the current selection',
|
|
238
|
-
mutationLock: 'Task directories are locked until start or stop is confirmed',
|
|
246
|
+
mutationLock: 'Task and project directories are locked until start or stop is confirmed',
|
|
239
247
|
unknownOutcome: '“{action}” outcome is unconfirmed',
|
|
240
248
|
unknownHelp: 'The request may have reached the engine. Refresh this task before choosing the next action.',
|
|
241
249
|
reconcile: 'Refresh unconfirmed task',
|
|
@@ -247,7 +255,7 @@ const en = {
|
|
|
247
255
|
feedback: 'Operation feedback',
|
|
248
256
|
statusHeading: 'Task status',
|
|
249
257
|
emptyHeading: 'No task status yet',
|
|
250
|
-
emptyBody: '
|
|
258
|
+
emptyBody: 'In Chat, ask the assistant to prepare a task with flywheel-new. Review its instructions and acceptance checks, then enter the task files directory it provides. For an existing task, use its original directory.',
|
|
251
259
|
emptySelected: 'Refresh status to inspect this task. Opening this view does not start it.',
|
|
252
260
|
lastChecked: 'Last confirmed: {time}',
|
|
253
261
|
stale: 'Current status is unconfirmed; the previous result is retained below',
|
|
@@ -291,12 +299,13 @@ const en = {
|
|
|
291
299
|
tokensMissing: '; usage is unknown for {count} calls, so this total is incomplete',
|
|
292
300
|
tokenLimit: 'Daily token limit: {limit} · Budget policy: {policy}',
|
|
293
301
|
budgetWarning: 'Budget notice: {message}',
|
|
294
|
-
advanced: '
|
|
295
|
-
advancedHelp: '
|
|
302
|
+
advanced: 'Configuration and troubleshooting (optional)',
|
|
303
|
+
advancedHelp: 'Useful after installation, upgrades or a failed run; neither check is required before every task. Neither sends messages to a model or verifies account access, quota or real model responses.',
|
|
296
304
|
configuration: 'Effective model configuration',
|
|
305
|
+
configurationHelp: 'These are the effective Flywheel settings loaded from the source below. The model selected in the DSH chat composer does not replace them.',
|
|
297
306
|
source: 'Configuration source: {source}',
|
|
298
307
|
embedded: 'Embedded template',
|
|
299
|
-
scope: '
|
|
308
|
+
scope: 'Configuration level: {scope}',
|
|
300
309
|
outOfScope: 'Not used for this check',
|
|
301
310
|
model: 'Model',
|
|
302
311
|
profile: 'Profile',
|
|
@@ -305,6 +314,9 @@ const en = {
|
|
|
305
314
|
channel: 'Channel',
|
|
306
315
|
thinking: 'Reasoning',
|
|
307
316
|
permission: 'Required permission',
|
|
317
|
+
permissionFull: 'Full access (no step-by-step approval)',
|
|
318
|
+
permissionWorkspace: 'Write within the project',
|
|
319
|
+
permissionReadOnly: 'Read only',
|
|
308
320
|
session: 'Session resume',
|
|
309
321
|
enabled: 'Enabled',
|
|
310
322
|
disabled: 'Disabled',
|
|
@@ -322,9 +334,9 @@ const en = {
|
|
|
322
334
|
diagnostic: 'Environment check results',
|
|
323
335
|
doctorPassed: 'Check complete: no blockers found',
|
|
324
336
|
doctorFailed: 'Check complete: blockers found',
|
|
325
|
-
localOnly: '
|
|
326
|
-
probeRequested: '
|
|
327
|
-
inferenceBoundary: 'No
|
|
337
|
+
localOnly: 'Local tools, configuration and selected task files were checked; no DSH integration check ran.',
|
|
338
|
+
probeRequested: 'A DSH integration check was requested (no model call)',
|
|
339
|
+
inferenceBoundary: 'No message was sent to a model. Account access, available quota and real model responses remain unverified.',
|
|
328
340
|
pass: 'Passed',
|
|
329
341
|
fail: 'Failed',
|
|
330
342
|
warn: 'Notice',
|
|
@@ -348,6 +360,11 @@ const packetLabels = {
|
|
|
348
360
|
const roleLabels = { plan: 'planRole', execute: 'executeRole', review: 'reviewRole', judge: 'judgeRole',
|
|
349
361
|
intake: 'intakeRole', garden: 'gardenRole', 'judge-panel': 'panelRole' };
|
|
350
362
|
const checkLabels = { pass: 'pass', fail: 'fail', warn: 'warn', unknown: 'unverified', skipped: 'skipped' };
|
|
363
|
+
const permissionLabels = {
|
|
364
|
+
'danger-full-access': 'permissionFull',
|
|
365
|
+
'workspace-write': 'permissionWorkspace',
|
|
366
|
+
'read-only': 'permissionReadOnly',
|
|
367
|
+
};
|
|
351
368
|
const record = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
352
369
|
const textValue = (value, fallback = '—') => typeof value === 'string' || typeof value === 'number' ? String(value) : fallback;
|
|
353
370
|
const selectionOf = value => ({ taskDir: value.taskDir.trim(), cwd: value.cwd.trim() });
|
|
@@ -506,6 +523,7 @@ function Configuration({ value, t }) {
|
|
|
506
523
|
h('dt', null, t(name)), h('dd', null, code ? h('code', null, value) : value));
|
|
507
524
|
return h('section', { className: css.section, 'aria-label': t('configuration') },
|
|
508
525
|
h('h3', { className: css.heading }, t('configuration')),
|
|
526
|
+
h('p', { className: css.meta }, t('configurationHelp')),
|
|
509
527
|
h('p', { className: css.meta }, t('source', { source: textValue(value.source, '') || t('embedded') })),
|
|
510
528
|
h('p', { className: css.meta }, t('scope', { scope: textValue(value.scope) })),
|
|
511
529
|
h('div', { className: css.bindings }, ...(value.bindings ?? []).map((binding, index) =>
|
|
@@ -521,7 +539,8 @@ function Configuration({ value, t }) {
|
|
|
521
539
|
field('provider', textValue(binding?.provider, '') || (binding?.harness === 'dsh' ? t('notConfigured') : t('nativeProvider'))),
|
|
522
540
|
field('channel', textValue(binding?.channel, '') || t('notConfigured')),
|
|
523
541
|
field('thinking', textValue(binding?.thinking, '') || t('default')),
|
|
524
|
-
field('permission',
|
|
542
|
+
field('permission', Object.hasOwn(permissionLabels, binding?.permissionMode) ?
|
|
543
|
+
t(permissionLabels[binding.permissionMode]) : textValue(binding?.permissionMode, '') || t('undeclared')),
|
|
525
544
|
field('session', t(binding?.residentSession ? 'enabled' : 'disabled')))))));
|
|
526
545
|
}
|
|
527
546
|
|
|
@@ -639,6 +658,7 @@ function TaskView({ useStore, actions, useControl, changeSelection, run, reconci
|
|
|
639
658
|
const invalidCwd = pathProblem(selection.cwd);
|
|
640
659
|
const button = (action, labelKey, needsTask, probe = false, variant = 'outline') => h(Button, {
|
|
641
660
|
key: labelKey, variant, size: 'sm',
|
|
661
|
+
'aria-describedby': ['doctor', 'probe'].includes(labelKey) ? formId + '-' + labelKey + '-help' : undefined,
|
|
642
662
|
disabled: busy || !!invalidTask || !!invalidCwd || (needsTask && !selection.taskDir.trim()) ||
|
|
643
663
|
(mutates(action) && control.uncertainty !== null),
|
|
644
664
|
onClick: () => run(action, selection, probe),
|
|
@@ -650,7 +670,7 @@ function TaskView({ useStore, actions, useControl, changeSelection, run, reconci
|
|
|
650
670
|
'aria-invalid': !!problem, 'aria-describedby': formId + '-' + name + '-help',
|
|
651
671
|
onChange: event => changeSelection({ taskDir: selection.taskDir, cwd: selection.cwd, [name]: event.target.value }) }),
|
|
652
672
|
h('p', { id: formId + '-' + name + '-help', className: problem ? css.errorText : css.meta }, t(problem ?? hint)));
|
|
653
|
-
return h('section', { className: css.page, 'aria-label':
|
|
673
|
+
return h('section', { className: css.page, 'aria-label': t('title') },
|
|
654
674
|
h('style', { 'data-plugin': '@ckanner/dsh-plugin-flywheel',
|
|
655
675
|
'data-plugin-css': '@ckanner/dsh-plugin-flywheel/client.module.css',
|
|
656
676
|
}, stylesheet),
|
|
@@ -659,8 +679,8 @@ function TaskView({ useStore, actions, useControl, changeSelection, run, reconci
|
|
|
659
679
|
h('p', { className: css.meta }, t('description'))),
|
|
660
680
|
h('section', { className: css.selection, 'aria-label': t('selection') },
|
|
661
681
|
h('div', { className: css.fields },
|
|
662
|
-
field('taskDir', 'taskHint', '/
|
|
663
|
-
field('cwd', 'cwdHint', '/
|
|
682
|
+
field('taskDir', 'taskHint', '/Users/me/project/docs/fix-login', invalidTask),
|
|
683
|
+
field('cwd', 'cwdHint', '/Users/me/project', invalidCwd)),
|
|
664
684
|
h('div', { className: css.actions },
|
|
665
685
|
button('status', 'status', true, false, 'primary'),
|
|
666
686
|
button('start', 'start', true), button('stop', 'stop', true))),
|
|
@@ -674,6 +694,8 @@ function TaskView({ useStore, actions, useControl, changeSelection, run, reconci
|
|
|
674
694
|
h('div', { className: css.actions },
|
|
675
695
|
button('config', 'config', false), button('doctor', 'doctor', false),
|
|
676
696
|
button('doctor', 'probe', false, true)),
|
|
697
|
+
h('p', { className: css.meta, id: formId + '-doctor-help' }, t('doctorHelp')),
|
|
698
|
+
h('p', { className: css.meta, id: formId + '-probe-help' }, t('probeHelp')),
|
|
677
699
|
h(Diagnostic, { value: control.diagnostic, t }),
|
|
678
700
|
h(Configuration, { value: control.configuration, t }))),
|
|
679
701
|
control.raw && h('details', { className: css.details },
|
package/dist/codex-pro-bridge.js
CHANGED
|
@@ -38,7 +38,7 @@ export function machineRequest(value, api, env) {
|
|
|
38
38
|
const keys = new Set(['type', 'protocol', 'requestId', 'cwd', 'model', 'reasoningEffort', 'permissionMode', 'prompt', 'timeoutMs']);
|
|
39
39
|
if (value === null || typeof value !== 'object' || Array.isArray(value) || value.type !== 'start' || value.protocol !== 1 ||
|
|
40
40
|
Object.keys(value).some(key => !keys.has(key)) || typeof value.requestId !== 'string' || value.requestId.length > 256 ||
|
|
41
|
-
!['read-only', 'workspace-write'].includes(value.permissionMode)) {
|
|
41
|
+
!['read-only', 'workspace-write', 'danger-full-access'].includes(value.permissionMode)) {
|
|
42
42
|
throw new WorkerError('INVALID_REQUEST', 'Expected one protocol=1 start request with an explicit supported permission mode');
|
|
43
43
|
}
|
|
44
44
|
if (value.timeoutMs !== undefined && (!Number.isSafeInteger(value.timeoutMs) || value.timeoutMs < 1 || value.timeoutMs > 2147483647)) {
|
|
@@ -46,7 +46,9 @@ export function machineRequest(value, api, env) {
|
|
|
46
46
|
}
|
|
47
47
|
try {
|
|
48
48
|
return api.parseMachineRequest({ protocol: 1, requestId: value.requestId, cwd: value.cwd, model: value.model,
|
|
49
|
-
effort: value.reasoningEffort,
|
|
49
|
+
effort: value.reasoningEffort,
|
|
50
|
+
permissionMode: value.permissionMode === 'danger-full-access' ? 'dangerously-bypass-approvals-and-sandbox' : value.permissionMode,
|
|
51
|
+
prompt: value.prompt,
|
|
50
52
|
...(value.timeoutMs === undefined ? {} : { timeoutMs: value.timeoutMs }),
|
|
51
53
|
command: env.FLYWHEEL_CODEX_BIN || 'codex', threadMode: 'ephemeral', maxOutputBytes: LIMITS.machineOutputBytes });
|
|
52
54
|
} catch (error) {
|
package/dist/policy.js
CHANGED
|
@@ -4,20 +4,22 @@ export const inject = ['sandboxPolicy', 'approval', 'shell'];
|
|
|
4
4
|
|
|
5
5
|
export async function apply(ctx, config) {
|
|
6
6
|
const { permissionMode, cwd } = config;
|
|
7
|
-
|
|
7
|
+
const fullAccess = permissionMode === 'danger-full-access';
|
|
8
|
+
if (!['read-only', 'workspace-write', 'danger-full-access'].includes(permissionMode) || typeof cwd !== 'string' ||
|
|
8
9
|
ctx.shell.sandboxMode === undefined) {
|
|
9
|
-
throw new Error('Flywheel requires a
|
|
10
|
+
throw new Error('Flywheel requires a sandbox-capable DSH shell and explicit permission policy');
|
|
10
11
|
}
|
|
11
|
-
//
|
|
12
|
-
//
|
|
12
|
+
// Probe the official runner. Restricted modes must prove full enforcement;
|
|
13
|
+
// full access must truthfully report that no confinement was applied.
|
|
13
14
|
const probe = await ctx.shell.execute(ctx.shell.resolve({
|
|
14
15
|
command: ':', workdir: cwd, timeoutMs: 5000,
|
|
15
16
|
sandboxPolicy: { mode: permissionMode, workspaceRoot: cwd },
|
|
16
17
|
}));
|
|
17
18
|
const result = await probe.result();
|
|
18
19
|
if (result.exitCode !== 0 || result.timedOut || result.aborted ||
|
|
19
|
-
result.sandbox?.mode !== permissionMode ||
|
|
20
|
-
|
|
20
|
+
result.sandbox?.mode !== permissionMode ||
|
|
21
|
+
(fullAccess ? result.sandbox.denied !== false || result.sandbox.enforcement !== undefined : result.sandbox.enforcement !== 'full')) {
|
|
22
|
+
throw new Error(fullAccess ? 'Flywheel readiness probe did not confirm explicit full access' : 'Flywheel sandbox readiness probe did not prove full enforcement');
|
|
21
23
|
}
|
|
22
24
|
ctx.on('session/created', session => {
|
|
23
25
|
// A fresh SDK session has no inherited permission facts. Pin both canonical
|
|
@@ -30,8 +32,8 @@ export async function apply(ctx, config) {
|
|
|
30
32
|
cwd: policy.workspaceRoot,
|
|
31
33
|
permissionMode: policy.mode,
|
|
32
34
|
approvalPolicy: ctx.approval.effectivePolicy(session),
|
|
33
|
-
confinedShell:
|
|
34
|
-
enforcement: result.sandbox.enforcement,
|
|
35
|
+
confinedShell: !fullAccess,
|
|
36
|
+
...(fullAccess ? {} : { enforcement: result.sandbox.enforcement }),
|
|
35
37
|
});
|
|
36
38
|
});
|
|
37
39
|
}
|
package/dist/protocol.js
CHANGED
|
@@ -49,8 +49,8 @@ export async function validateStart(value) {
|
|
|
49
49
|
const provider = nonempty(value.provider, 'provider');
|
|
50
50
|
const model = nonempty(value.model, 'model');
|
|
51
51
|
const prompt = nonempty(value.prompt, 'prompt', MAX_FRAME_BYTES);
|
|
52
|
-
if (!['read-only', 'workspace-write'].includes(value.permissionMode)) {
|
|
53
|
-
throw new BridgeError('INVALID_REQUEST', 'permissionMode must be read-only or
|
|
52
|
+
if (!['read-only', 'workspace-write', 'danger-full-access'].includes(value.permissionMode)) {
|
|
53
|
+
throw new BridgeError('INVALID_REQUEST', 'permissionMode must be read-only, workspace-write or danger-full-access');
|
|
54
54
|
}
|
|
55
55
|
if (value.reasoningEffort !== undefined) nonempty(value.reasoningEffort, 'reasoningEffort', 64);
|
|
56
56
|
if (value.maxTokens !== undefined && (!Number.isSafeInteger(value.maxTokens) || value.maxTokens < 1)) {
|
|
@@ -151,10 +151,12 @@ export class SessionFold {
|
|
|
151
151
|
|
|
152
152
|
verifiedPolicy(request) {
|
|
153
153
|
const policy = this.policy;
|
|
154
|
+
const fullAccess = request.permissionMode === 'danger-full-access';
|
|
154
155
|
if (!policy || policy.protocol !== PROTOCOL || policy.permissionMode !== request.permissionMode ||
|
|
155
156
|
policy.cwd !== request.cwd || policy.approvalPolicy !== 'never' ||
|
|
156
|
-
policy.confinedShell !==
|
|
157
|
-
|
|
157
|
+
policy.confinedShell !== !fullAccess ||
|
|
158
|
+
(fullAccess ? policy.enforcement !== undefined : policy.enforcement !== 'full')) {
|
|
159
|
+
throw new BridgeError('INVALID_REQUEST', 'DSH did not attest the requested cwd and permission policy; prompt withheld');
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
|