@mars.liu/dsh-cybernetics 0.7.8 → 0.8.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MarchLiu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,194 +1,212 @@
1
1
  # dsh-cybernetics
2
2
 
3
- DSH 的控制论工作协议插件:session goal(`dsh-goal` 持久目标,agent 思考产物、模型动词维护)→
4
- task agent → verify agent → triage agent,controller 在轮间推进,用户通过对话校准。
5
- 构建在 `dsh-workflow`(单轮编排)之上。v0.2 起不再依赖 goal.md 文件。
6
-
7
- - 协议全文:[docs/protocol.md](docs/protocol.md)
8
- - 服务端:`src/index.ts`(`cybernetics` 工具 + 隐式触发提示段)+ `src/controller.ts`(轮间状态机)
9
- - 客户端:`lib/client.js`(「目标 / Goal」+「控制 / Control」两个会话页签,实时推送)
10
- - 「目标」优先读插件自有 session projection `cyberneticsGoals`:本会话**全部** goal
11
- (标题 / 状态徽标 / 第 N 轮 / 当前 subgoal / depth、executor 小标签、被取代/修订来源标注),
12
- 按服务端两段顺序渲染(未完成在前、已完成在后,组内更新时间倒序)并就地更新;
13
- 无多 goal 数据时回退到 dsh-goal 的 `goal` projection 单 goal 渲染;
14
- - 「控制」读 session list store(control stream 实时推送),递归展示本会话全部
15
- agent(主控 + subagent catalog)的运行状态与各自 goal projection
16
- (objective / phase / rounds / 受阻原因),顶部汇总运行中数量;v0.6 起顶部还列出
17
- 每个 goal 的**执行者行**(状态 + 标题 + executor 临时/持久 + 持久 teammate 名称 +
18
- 第 N 轮,未完成在前、已完成在后),数据源与「目标」页签同一份 `cyberneticsGoals` 投影。
19
- - 轮脚本:`workflows/round.js`(每次 run = 一轮;`roles` 表支持按角色覆盖 sub agent 模型)
20
-
21
- ## 状态
22
-
23
- v0.7:**全量 goal 模式(`allGoalsMode`,默认 false)**——开启后注入「ALL-GOALS MODE ACTIVE」
24
- 路由协议:任何用户请求一律先建 goal 再执行(禁止就地分析);简单只读 → quick 单轮;
25
- 复杂分析/写任务 → full 多轮渐进(start 异步 + collect 收敛);revise 缺省 `abortPrevious`
26
- 改为自动决策树(查 goal 板 running 状态:旧轮在跑且 objective/depth 被改写 → 自动中止;
27
- 旧轮在跑但目标不变 → 等待自然结束;旧轮未跑 → 直启),显式传值仍是人工控制。
28
- 每次修订的 `revisions-<新goalId>.jsonl` 审计行新增 `abort: {abort, mode, basis}`;
29
- `revise` 返回值同样携带 `abort`。决策树纯函数在 `src/controller.ts` 的 `decideAbort`。
30
- 关闭模式时提示段与 revise 行为与 v0.6 完全一致(离线测试 48–50 锁死)。`src/` 改动需宿主重载。
31
- v0.7.1:**插件配置面板可视化配置**——`src/index.ts` 导出 schemastery `Config` schema,宿主
32
- 0.1.7 settings model 据此派生 Web 编辑器插件条目的设置页(机制同 `@linxin666/dsh-ssh` /
33
- `@linxin666/dsh-usage`:loader 经 `runtime.Config['~standard'].validate` 解析配置,面板由
34
- dsh-client-ui-settings 的共享配置表单渲染,含每个字段的 `description` 文案)。
35
- `allGoalsMode` 标记 `.volatile()`:在面板上切换保存后 loader 直接把新值提交进运行实例
36
- (不 remount),提示段经 `loader/volatile-update` 重挂即时生效;`hardGate`/`gateMode`/
37
- `gatedTools`/`roles` 等结构性字段为普通字段,保存后触发插件行 remount(需宿主重载完成)。
38
- 注意:schema 解析会把未设置的 `z.array` 字段变成 `[]` 而非 `undefined`,`apply()` 的门
39
- 回退因此按「空数组 = 未设置」处理(离线测试 51 锁死)。此前版本只能手改 profile JSON
40
- 里的 `allGoalsMode: true`;现在在 Web 编辑器「插件」页的本插件卡片上直接切换
41
- 即可(保存 volatile 字段无需重启宿主,刷新浏览器可见最新面板)。
42
- v0.7.2/0.7.3:**插件页设置卡片**——实测仅有服务端 Config schema 不会在「插件」页产生任何
43
- 卡片(本 profile 的 @linxin666/dsh-client-ui-plugin-manager 停用了官方 ui-settings-plugins
44
- 设置分区,schema 自动面板无处落脚)。官方插件页(@deepseek-ai/dsh-client-ui-plugin-manager)
45
- 只通过它声明的 slot 渲染插件配置:`plugins.item`(「内置插件配置」分组列表卡)、
46
- `plugins.bundle.config`(按 bundle 名分发,bundle 详情页配置区)、`plugins.row.config`
47
- (按 `<bundle>#<rowId>` 分发,组件详情页)。`lib/client.js` 因此注册三个席位:
48
- `plugins.item`(id = `dsh-cybernetics`)、`plugins.bundle.config`(key 同时注册
49
- profile 依赖名 `dsh-cybernetics` 与包名 `@mars.liu/dsh-cybernetics`)、`plugins.row.config`
50
- (key `<bundle>#dsh-cybernetics`)。`plugins.item` 无条件注册(v0.7.5 加固):早先的
51
- cut 用 `configForms.whileServed` 门控,宿主尚未服务该命名空间时整张卡消失,与「注册
52
- 从未运行」不可区分;现改为 `ctx.configForms.get()` 绑定一个跟随共享 describe mirror
53
- 的活 ConfigFormController,命名空间就绪的瞬间卡片翻成开关,未就绪时渲染
54
- 「配置不可用」行——可见、可诊断。卡片读写 `ctx.configForms.get('dsh-cybernetics')`
55
- 的 ConfigForm;v0.7.3 修复:详情页的 owner prop `form`(宿主 ConfigPageForm
56
- `{state, mutate}`)在渲染 seam 上覆盖 inject 的 form(renderer renderEntry 先展开
57
- inject 后展开 ownerProps),卡片两种形态都支持(离线测试 52/53 锁死)。
58
- `dsh.client.inject` 增补 `@deepseek-ai/dsh-client-ui-settings` 保证 configForms 提供方
59
- 先到。改动 `lib/client.js` 后需重启宿主(客户端模块图在宿主启动时构建)并刷新浏览器。
60
- v0.7.4:**保存失败可见化**——实机验证(2026-11-03,浏览器实测):卡片注册、summary 文案、开关渲染
61
- 均正常,但切换保存经 `configForms`→`remote.settings.mutate` 到宿主后被**静默拒绝**(写不落盘:
62
- profile `cordis.patch.yml` 未变更,RPC 错误不上控制台,卡片此前 `.catch(() => {})` 吞掉失败,
63
- 第一次点击甚至把 promise 挂死在「保存中」)。离线模拟宿主 `dsh-config-editor.edit` 的文档校验
64
- (`composeEntries([patches])` 含 bundle insert 层 + 新增 override 行)是**通过**的,说明拒绝发生在
65
- 更早的环节(`reconcileProfilePatches` 预检 / loader 热重载路径),宿主日志在本环境不可读,确切
66
- 拒绝原因待宿主侧确认。本轮改动:卡片不再吞错——写入被拒(settles false / reject)或 15 s 未返回
67
- 都显示「保存未生效(宿主拒绝了写入)」并立即把开关还原到宿主确认的快照值;成功路径语义不变
68
- (volatile 即时生效)。根治写路径需要宿主侧 settings.mutate 的拒绝日志(`harness-log` 或
69
- `dsh plugin --profile web` 诊断),超出本轮插件仓库边界。
70
- v0.7.5:**卡片注册无条件化**——见上 v0.7.2/0.7.3 段的 `plugins.item` 说明;
71
- 宿主日志复核(2026-11-03,`~/.dsh/logs/`):未发现 cybernetics settings mutate 的拒绝
72
- 记录(最近一次启动日志是 EADDRINUSE 的失败实例,当前宿主进程无对应落盘日志),
73
- 写路径的宿主侧根因仍待带日志的复现。
74
- v0.7.6:**路径 (b) 落点取证 + 拒绝原因透出**——代码级取证(2026-11-04,宿主 checkout 只读)确认:
75
- `settings.mutate` 的唯一宿主写路径是 `@deepseek-ai/dsh-settings SettingsForms.write` →
76
- `@deepseek-ai/dsh-config-editor ConfigEditor.edit`,后者写入的就是**用户 patch 行**(在 profile
77
- `cordis.patch.yml` 追加/更新 `{id, name, config}` 非 insert 行,再以
78
- `reconcileProfilePatches(root, patches, "dsh", [entry.id])` 应用——见
79
- `dsh-config-editor/lib/index.js` edit() 的 `findLastIndex` + `document.add({id, name, config})`)。
80
- 即插件侧写入落点**从来就是**宿主允许的用户 patch 行,"写到了 bundle insert 行"的假设不成立;
81
- 实测复现(2026-11-04 浏览器点击开关 + `cordis.patch.yml` mtime 前后比对)显示拒绝发生在宿主
82
- `edit()` **落盘之前**(文件未被触碰、快速返回 settings/rejected),确切 throw 行仍需宿主侧
83
- stderr(tty,本环境不可读)。本轮插件侧改动:写入被拒时经插件自有的 `remote.settings.mutate`
84
- (同一宿主写路径)做**一次无条件 revision 回退写**(expectedRevision 省略 = 宿主文档化的
85
- 无条件写),陈旧 revision 冲突可就地自愈;仍被拒时卡片以
86
- 「保存未生效,宿主拒绝原因:{message}」透出宿主 RemoteError 原文(ConfigFormController 原本把
87
- 错误坍缩成裸 `false`)。离线测试增至 54 项,新增写入落点断言(回退写无 revision、主写成功不触
88
- 回退、无 remote 降级不崩)。
89
- v0.7.7:**agent 模型字段 volatile 化(表单可写性根治)**——本轮实机复现(2026-11-04 浏览器点击
90
- 插件详情页开关):写仍被宿主拒绝(快照回弹「全目标模式:关」,profile `cordis.patch.yml` mtime
91
- 21:58:35 未变,拒绝发生在落盘之前),且当前宿主进程 2026-11-03 22:04 启动、早于 v0.7.6 提交
92
- (00:40),运行中的仍是旧客户端模块。新增宿主侧取证:`@deepseek-ai/dsh-settings` 的
93
- `volatileForm(schema)`(lib/index.js:122-131)只保留 `.volatile()` 字段作为整个设置表单,
94
- `write()`(同文件 502-531 行)对每个写入路径做 `isVolatilePath` 检查并抛
95
- 「Config field "…" is not volatile」——即宿主表单**只服务也只接受** volatile 字段;据此把
96
- taskAgentModel / verifyAgentModel 两个扁平字段也改为 `.volatile()`(此前非 volatile =
97
- 在插件页既不可见也不可写),读取点改为 `readConfigField(config.taskAgentModel, '')` 解包
98
- 活引用('' = 与 session agent 一致),语义保持「保存后下一轮 goal 即生效,无需重载」。
99
- 离线测试 55 项全过(测试 55 改为断言两字段 volatile + 活引用解析 + readConfigField 解包布线)。
100
- 生效步骤:重启宿主(客户端模块图 + 服务端 schema 都在启动时装配)+ 强刷浏览器;验证步骤:
101
- 插件详情页拨「全目标模式」→ 开关保持 + 摘要行变「全目标模式:开」+ profile patch 出现
102
- `dsh-cybernetics` 用户行;仍失败时卡片将显示宿主拒绝原文(v0.7.6 面)。
103
- v0.7.8:**写入落点证明测试(测试 56)**——把宿主 `@deepseek-ai/dsh-settings` write() 的全部
104
- 预写闸门(volatileForm / isVolatilePath / projectForm / mutate op reduce / validatePaths /
105
- strip + mergeLayers,lib/index.js 122-179 与 488-534 行)逐行复刻,对**真实导出的 Config
106
- schema** 重放卡片的 toggle 写:开关写通过每一道闸门、经 schemastery 校验
107
- (= cordis `resolveConfig` 的精确检查)后产出持久化 config,最终落点形状恰为
108
- `ConfigEditor.edit`(dsh-config-editor lib/index.js 150-155 行)追加到 profile
109
- `cordis.patch.yml` 的**用户 patch 行** `{id, name, config}`(无 `insert` 键)——即
110
- `reconcileProfilePatches`(dsh-app-boot lib/index.js:3794)唯一接受的落点。
111
- 负向对照:`hardGate` 等非 volatile 路径在写前即被同一闸门以
112
- 「Config field "hardGate" is not volatile」拒绝、文件不被触碰——这正是 v0.7.4/v0.7.5
113
- 实机观测到的拒绝签名(profile patch mtime 不变、快速 rejected):**运行中宿主装配的
114
- 插件 schema 若早于 volatile 标注,宿主即在写前拒绝**;生效手段不变:重启宿主装配
115
- v0.7.7 schema + 强刷浏览器。离线测试增至 56 项。
116
- v0.6:多 goal session 投影(`cyberneticsGoals`)——goal 板每次变化把本 session 全部 goal 的
117
- 整值快照 append 成一条 `plugin:cybernetics/goals` 事件(可跳过的插件记录,宿主持久化读取安全),
118
- 服务端纯折叠、宿主整值推送,「目标」页签逐条渲染并就地更新;空板回退到 dsh-goal 单 goal 渲染。
119
- **执行者适配层**:`executor: 'ephemeral' | 'persistent'`(与 depth 正交)——persistent 用 Agent Teams
120
- 持久 teammate 执行 task 工作(首轮 spawn fresh,后续轮 sendMessage 投递委托书修订,`subagent/end`
121
- 捕获输出);quick 由输出契约派生 verdict,full 把报告作为 `taskReport` 交给 round.js,verify/triage
122
- 仍以临时 agent 独立运行。审计日志记录 `executor`,goal 板记录 `memberName`。
123
- v0.6 追加:**只新增目标模型(append-only goals)**——goal 一经存在不再原地修改。`action: 'revise'`
124
- (必填 `reason`,可选 `objective`/`title`/`depth`/`executor`/`abortPrevious`)创建一个带
125
- `parentGoalId` 的新 goal 并立即启动其第 1 轮,旧 goal 置 `superseded` + `supersededBy` 保留最终状态;
126
- 已完成的 goal 同样走这条路径。`abortPrevious` 默认 false(旧轮跑完由 session agent 决定是否打断),
127
- true 时复用 cancel 路径。每次修订追加 `.cybernetics/revisions-<新goalId>.jsonl`。页签按两段排序
128
- (未完成在前、已完成 met/cancelled/superseded 在后,组内 `updatedAt` 倒序;服务端排序、客户端不重排),
129
- 并显示「已被 <新标题> 取代」/「修订自 <旧标题>」;`GoalStatus` 增加 `superseded`。
130
- v0.6 追加:**板持久化与 rehydrate**——controller 的 goal 板在每个写点整值快照到
131
- `<workspace>/.cybernetics/board-<sessionId>.json`(`{version, sessionId, savedAt, goals}`,
132
- 同目录临时文件 + rename 原子替换,失败不阻断);重启后首次工具调用拿到空内存板时从快照恢复
133
- (缺失/损坏/版本不符/非法记录一律空板,不抛错)并立即重推一次 `cyberneticsGoals` 投影,
134
- 「目标」页签因此不再在重启后清空。同一版「控制」页签新增每个 goal 的执行者行
135
- (状态 + 标题 + executor 临时/持久 + 持久 teammate 名称 + 轮次,未完成在前、已完成在后);
136
- `GoalView` 增加可选 `memberName`,投影折叠保留。快照是当前状态而非事件日志,`updatedAt` 不持久化。
137
- v0.5:双档轮次(depth)——轻量分析 quick 单 agent 一轮直接反馈;复杂分析与变更走完整工作-验证循环。
138
- v0.4.1:硬门增加 allowlist 模式(只保留读+编排工具,挡住 farm_unregister 类长尾变更工具)。
139
- v0.4:可选硬门(`hardGate: true`)——装配期从顶层 session agent 物理摘除执行工具
140
- (默认 bash/edit/write),编排从劝告变成结构;task agent 不受影响。
141
- v0.3:多 goal 板 + 编排者协议 + 异步轮(`start`/`collect`/`cancel`/`list`)+ task agent 工作委托书(brief)。
142
- 轮脚本现读(改 `workflows/round.js` 热生效);`src/` 改动需宿主重载。
143
- v0.2:session-goal 模式 + 隐式触发 + 角色化 sub agent + 客户端 goal 页签。
144
- 服务端已按 dsh 0.2.1-rc.3 真实 API 核对(tools.register / goals service / workingDirectory),
145
- 客户端按 ModuleLoader 包装格式手写(参照 dsh-client-ui-goal / dsh-context)。
146
- **端到端回路尚未实跑**:workflow 轮脚本、页签挂载均待首次真实验证。
147
-
148
- 2026-11-02 触发链路实测:隐式提示注入 ✅、工具注册 ✅;执行原在
149
- `ctx.get('workflowEngine')` 处报 "cannot get property ... without inject"
150
- (宿主 cordis 对未注入服务的属性访问直接抛错,goal 护栏不可达)。
151
- 已改为静态 inject `workflowEngine` / `goals` / `workingDirectory`
152
- (对齐 dsh-tool-workflow / dsh-tool-ralph)。
153
- 追加实测修正:首次重启后插件仍 pending——`workflowEngine` 是在 `delegation`
154
- group 的 isolate 里提供的(`isolate: workflowEngine: true`),顶层 insert 的
155
- 行拿不到它。`cordis.patch.yml` 已改为按组 id 插入(`- id: delegation` +
156
- `insert:`,loader 方言:patch id 指向 group 行时追加进该组 config)。
157
- **待服务端重载后复测**:boot 不再 pending、工具可用、隐式提示段与客户端页签
158
- 在组作用域下仍正常注入。
159
- 第三次实测修正:按组 id 插入也被跳过(dump-config 报 `entry "delegation" not
160
- found`)——`delegation` 组不在顶层 patch 索引,而是嵌在 `preset-standard`
161
- (`@deepseek-ai/dsh-agent-preset`)的 `config.plugins` 里,patch 应用时那只是
162
- 普通 config 对象。最终方案:profile 层 patch 整体重写 `preset-standard` 的
163
- config(Web 编辑器保存走的就是这条路),把 `dsh-cybernetics` 加进其
164
- delegation 组;本插件的 bundle patch 清空为 `[]`。
165
- 第四次实测修正:页签仍未出现——浏览器实测发现 boot graph 的 client combo
166
- 不含本包:客户端扫描只枚举**顶层** loader entries,挂进 preset 的行对它不可
167
- 见。最终方案:插件回到顶层行,`workflowEngine` 不再静态 inject,执行时经
168
- `ctx.agentPresets.serviceFor(agent, 'workflowEngine')` 从 Agent 的 preset
169
- mount 读取(registry 的官方 root-realm 访问器);inject 收窄为五个根级服务
170
- (tools/systemPrompt/goals/workingDirectory/agentPresets)。同轮还修复 goal
171
- 护栏:`goals.get()` 对 completed/paused goal 仍返回视图,现严格要求
172
- `phase === 'active'`,并按 goal id 重置轮次状态。dump 复算确认:顶层行就位、
173
- preset-standard 恢复原样、无 patch 警告。
174
- 页签渲染两连修(浏览器实测定位):① inject 手动传的 `t` 遮蔽了槽位按
175
- `locale: NS` 注入的正确翻译函数(字典注册前 bind 会解析为空串)——inject
176
- 不再传 t;② 宿主 jsx 变体忽略 `jsx(type, props, children)` 的第三参,
177
- children 必须放进 `props.children`(对照 dsh-client-ui-trajectory 编译产物),
178
- 否则所有文本静默丢失、元素塌缩成空胶囊。修复后「目标 / 控制」两页签均完整
179
- 渲染(2026-11-02:goal 徽章/objective/轮次 + 1/11 agent 树与各自 goal 状态)。
180
-
181
- 第五次实测修正(改名后的回归):`@mars.liu` 改名把包名从 `dsh-cybernetics` 变成
182
- `@mars.liu/dsh-cybernetics`,loader 行名同步了,但 `lib/client.js` 里
183
- `__ModuleLoader__.load({ id })` 仍是短名 → boot 报
184
- `could not load "@mars.liu/dsh-cybernetics": … loaded without registering`。
185
- 规则:客户端 bundle 的注册 id 必须等于 package.json 的 `name`(client-modules
186
- boot graph 以包名为行键,`dsh.client.inject` 里的依赖同样按包名解析);短名只用于
187
- loader 行 id(`- id: dsh-cybernetics`)。离线测试已加断言锁死这条不变量。
188
-
189
- ## 与 dsh 现有机制的关系
190
-
191
- - goal:不替代——dsh-goal 仍是 durable setpoint 与续跑驱动;本插件是它的工作回路。
192
- - workflow:每轮就是一次 `ctx.workflowEngine.start()` run;不新增编排机制。
193
- - 完成确认:controller 永不自宣完成、不代改 goal phase;met 也只暂停等用户。
194
- - 多 goal(未来):按 super-agent 模式,sub goal 下沉到 sub session 各自的 dsh-goal。
3
+ [English](README.md) | [简体中文](README.zh-CN.md)
4
+
5
+ ![version](https://img.shields.io/badge/version-0.8.2-blue) ![protocol](https://img.shields.io/badge/license-MIT-lightgrey) ![platform](https://img.shields.io/badge/platform-DSH%20plugin-9b59b6)
6
+
7
+ **dsh-cybernetics is a DSH plugin that explicitly designs LLM agent orchestration as a control loop**: the session goal persists as an artifact of the agent's thinking; the task agent acts, the verify agent measures, the triage agent triages, the controller advances between rounds, and the user calibrates the outer loop through conversation — a negative-feedback loop of goal, action, measurement, and calibration.
8
+
9
+ The protocol takes its name from Wiener's cybernetics: set a goal (setpoint), act, measure the deviation of the output from the goal, and use that deviation to correct the next action. A traditional one-shot prompt is an open-loop system — the goal lives only inside the current prompt, and the burden of measurement falls entirely on the human; this project moves the loop into the system itself, transforming the human from "the author of prompts, one by one" into "the setter of goals and the calibrator of the outer loop." The design rationale is described in the blog post "[Cybernetics Agent — A New Paradigm for AI Applications](https://marchliu.github.io/2026/11/03/cybernetics-agent-new-paradigm)" (in Chinese).
10
+
11
+ - Full protocol: [docs/protocol.md](docs/protocol.md) (Chinese)
12
+ - Evolution history: [docs/CHANGELOG.md](docs/CHANGELOG.md) (Chinese)
13
+
14
+ ## Core Concepts
15
+
16
+ ```mermaid
17
+ flowchart TB
18
+ user["User<br/>(outer-loop calibrator)"] -- "dialog calibration / confirm completion" --> sa
19
+ sa["session agent<br/>(orchestrator / controller)"]
20
+ subgraph loop["inner loop: one round (workflows/round.js)"]
21
+ ta["task agent<br/>executor"] --> va["verify agent<br/>independent measurement"] --> ga["triage agent<br/>four-way triage"]
22
+ end
23
+ sa -- "work-order brief" --> ta
24
+ ga -- "verdict decision" --> sa
25
+ sa -- "maintain / advance" --> goal[("goal board<br/>append-only")]
26
+ ```
27
+
28
+ | Role | Responsibility |
29
+ |---|---|
30
+ | **session agent** | Orchestrator (controller): recognizes user input, maintains the goal board, starts/collects/cancels rounds, relays results to the user; never executes tasks itself |
31
+ | **controller** | The inter-round state machine in `src/controller.ts`: `startRound`/`foldRound`/`runRound`, advancing the subgoal round by round (task → verify → triage), maintaining the audit log |
32
+ | **task agent** | Executor: an independent sub agent with no conversation history, actually executing against the work-order brief, producing a structured delivery report |
33
+ | **verify agent** | Measuring instrument: accepts/rejects independently of the executor, producing a structured verdict `{met, criteria, gaps, goalItselfQuestionable}` |
34
+ | **triage agent** | Triage: sorts the follow-up leads exposed by the verdict into four categories — auto / defer / reject / human |
35
+ | **User** | Outer-loop calibrator: calibrates through conversation (goal revisions go through the session agent's `revise`), adjudicates human-class items, confirms completion — the controller never declares completion by itself |
36
+
37
+ **goal** is a first-class, persistent state (objective + current subgoal + full work-order brief), built on top of the dsh-goal service; since v0.2 it no longer depends on a goal.md file. The inner loop (task → verify → triage → subgoal correction) iterates automatically while the goal is unchanged, with a per-round cap on consecutive iterations as a backstop; the outer loop (user ↔ session agent) intervenes only when the goal itself is in question — a verdict with `goalItselfQuestionable=true` forbids automatic iteration and forces a return to conversation.
38
+
39
+ ## Features
40
+
41
+ ### All-goals mode (`allGoalsMode`, default false)
42
+
43
+ When enabled, injects the "ALL-GOALS MODE ACTIVE" routing protocol: **every user request first creates a goal, then executes**; the session agent is forbidden from completing tasks in place with read tools — even a single-file lookup or one calculation goes through the loop. Simple read-only requests → a single `quick` round; complex analysis / write tasks → progressive multi-round `full` (async `start` + round-by-round `collect` convergence). When the mode is off, the prompt segments and revise behavior are exactly the same as v0.6.
44
+
45
+ ### Hard gate (`hardGate`, default false)
46
+
47
+ At assembly time, execution tools (default bash/edit/write, overridable via `gatedTools`) are **physically removed** from the top-level session agent's tool list — the model cannot see these tools and therefore cannot call them; orchestration turns from exhortation into structure. `gateMode: 'allowlist'` inverts the semantics: the top-level agent keeps only read + conversation + presentation + goal/orchestration verbs (a built-in DEFAULT_GATE_ALLOWLIST, overridable via `gateAllowlist`/`gateAllowlistExtra`), targeting the long tail of change-class tools (farm_*/taskboard_*/schedule_*…). The task/verify/triage sub agents are never subject to the gate and keep all tools.
48
+
49
+ ### Append-only goals and the revise decision tree
50
+
51
+ Once a goal exists, it is never modified in place. `revise` is the only goal-changing verb (with required `reason`): it creates a new goal with `parentGoalId` and immediately starts its first round, marks the old goal `superseded` + records `supersededBy`, and appends an audit line per revision to `.cybernetics/revisions-<newGoalId>.jsonl`. Since v0.7, when `abortPrevious` is omitted, an automatic decision tree (the pure function `decideAbort`, based on the goal board's running state) decides:
52
+
53
+ - (a) An old round is still running and the revision rewrites the goal itself (objective/depth changed) → **automatically abort the old round**;
54
+ - (b) An old round is still running but the goal is unchanged (only brief/title/role corrections) → **wait for the old round to finish naturally**;
55
+ - (c) The old task agent has already finished → **just start the new goal**.
56
+
57
+ Passing `abortPrevious: true/false` explicitly keeps manual control; every revision's audit line and the `revise` return value carry `abort: {abort, mode: auto|explicit, basis}`.
58
+
59
+ ### quick / full round depths (depth)
60
+
61
+ - `quick`: a single task-agent round whose result is fed straight back, skipping verify/triage, at roughly one-third the cost; the verdict is derived from the task report;
62
+ - `full` (default): the complete work → verify → triage loop; delivery only after acceptance passes. The protocol explicitly forbids using quick as a shortcut for changes.
63
+
64
+ ### Persistent executor
65
+
66
+ `executor: 'ephemeral' | 'persistent'`, orthogonal to depth. persistent uses a persistent teammate from Agent Teams to perform task work: the first round spawns it via `spawnTeammate(context:'fresh')`, and subsequent rounds deliver only **brief revisions** (added constraints / changed acceptance criteria / narrowed scope; open-ended conversation is forbidden); in a full round the teammate's report serves directly as the task report, while verify/triage still run independently as ephemeral agents — verification never inherits the task narrative. Agent Teams is integrated softly (no injected reflection + exception-safe probing), falling back to ephemeral when unavailable. The goal board persists to `<workspace>/.cybernetics/board-<sessionId>.json` and automatically rehydrates after host restarts, so the "Goals" tab is never emptied.
67
+
68
+ ### Multiple goal boards and the "Goals / Control" client tabs
69
+
70
+ One session supports multiple concurrent goals (the plugin's own goal board; dsh-goal's single slot demotes to the focus goal). The client `lib/client.js` provides two session tabs, pushed live and updated in place via the plugin's own session projection `cyberneticsGoals`:
71
+
72
+ - **"Goals"**: all goals of this session (title / status badge / round N / current subgoal / depth and executor chips / superseded and revision-source annotations), sorted in two segments — unfinished first, completed last; an empty board falls back to dsh-goal's single-goal rendering.
73
+ - **"Control"**: recursively shows the run state of every agent in this session (main controller + subagent catalog) with each one's goal view, a running-count summary at the top, and an executor row per goal (status + title + executor ephemeral/persistent + persistent teammate name + round count), sharing the same data source as the "Goals" tab.
74
+
75
+ ### Plugin-page config card
76
+
77
+ `src/index.ts` exports a schemastery `Config` schema that decides which fields the host settings transport serves and accepts (only `.volatile()` fields); the card UI itself is drawn by `lib/client.js`, which registers three seats — `plugins.item` / `plugins.bundle.config` / `plugins.row.config` (both the bundle detail page and the component detail page can configure). **The card is the ONLY GUI surface for the plugin's settings**: the host Plugins page renders a plugin's configuration exclusively through its slot cards and draws no fields of its own, and `@deepseek-ai/dsh-client-ui-settings` ships the `configForms` service (transport), not a generic field renderer — so since v0.8.1 the card renders editable controls for ALL volatile fields (six since v0.8.4): the all-goals-mode toggle, task/verify/triage agent model text fields, and the rounds-per-subgoal and concurrent-round-cap number fields. The `roles` table is **not** on the card (v0.8.4): hand-editing a JSON object in the GUI is unfriendly, so roles became non-volatile — the panel neither serves nor writes it — and is configured directly in the profile config yaml instead (see "Advanced configuration: roles" below). Saving a volatile field takes effect immediately (the loader commits it straight into the running instance, no remount); saving a non-volatile field triggers a plugin-row remount (requires a host reload). The write lands in the user's patch row (a `{id, name, config}` row in the profile's `cordis.patch.yml`).
78
+
79
+ ### Agent model selection
80
+
81
+ - **Flat fields**: `taskAgentModel` / `verifyAgentModel` / `triageAgentModel` ("provider/model" or a bare model name, volatile); **empty = same as the session agent** (inheritance semantics). Takes effect on the next goal round after saving, no reload needed. The triage role reads the task/verify results and emits the round's convergence decision (met/continue/human-input/...); quick-depth rounds skip verify+triage entirely, so `triageAgentModel` only fires on full rounds. Since v0.8.3 the three fields are dropdown-backed: a `<datalist>` fed by the host's live model catalog (`remote.session.modelCatalog` — the same directory the official 子智能体 settings page renders, refreshed on `llm/adapters-updated`), with free manual entry preserved (a datalist input accepts any text). A configured value that is no longer in the catalog (e.g. the model's subscription was deleted) renders an "unavailable — runtime will fall back to the session agent model" warning plus a one-click **Clear** (an explicit unset write — stored config is never silently rewritten); the runtime fallback itself is v0.8.0's `callRoleAgent` (AGENT_START `no subagent provider registered` covers a removed provider; model-level "model not found / unknown model" messages were added to the availability patterns in v0.8.3).
82
+ - **roles table**: per-role model overrides for sub agents (fixed `default`/`verify`/`triage` slots + any named task roles). Since v0.8.4 this is an advanced configuration edited in the profile config yaml, not on the settings card — see the section below:
83
+
84
+ ```yaml
85
+ - id: dsh-cybernetics
86
+ config:
87
+ roles:
88
+ default: { provider: zai-coding-cn, model: glm-5.3 }
89
+ verify: { provider: zai-coding-cn, model: glm-5.3 }
90
+ writer: { provider: zai-coding-cn, model: glm-5-turbo }
91
+ ```
92
+
93
+ - **Call-level fallback (v0.8)**: when a role's configured model cannot be called — an *availability* failure such as exhausted quota/rate limit, unreachable network, or a provider error — the round no longer fails as a whole: `workflows/round.js` drops that role's provider/model override and retries the role's sub-agent once on the **session agent's** model configuration. The decision signal comes from the workflow runtime's (`@deepseek-ai/dsh-workflow-ptc`) `agent()` hook error type/message: a thrown `WorkflowError` (`AGENT_START` always counts; `AGENT_RESULT`/uncoded errors count when the message matches quota/auth/5xx/network/provider patterns), or a `null` return (the runtime flattens a child run ending with `stopReason ≠ "completed"` — the most common shape of a mid-run LLM call failure — into null without the cause; retried only when an override is in effect). **Logic failures never swap models**: a task agent that delivers a `done=false` report or a verify agent returning an unmet verdict flows through unchanged, and non-availability errors (schema/option/cap guard errors) propagate as before. Every fallback is recorded on the round result's `fallbacks` array (`{role, from, trigger, reason}`), narrated into `workflow/log`, persisted in the `.cybernetics/round-*.json` audit log, and surfaced in the decision note of the `cybernetics` tool result. Configuration stays backward compatible: empty = inherit the session agent (no fallback needed); configured = the configured model wins, with fallback on availability failure. Changes to `workflows/round.js` take effect hot.
94
+
95
+ ## Installation and Usage
96
+
97
+ ### Installation
98
+
99
+ npm package name: [`@mars.liu/dsh-cybernetics`](https://www.npmjs.com/package/@mars.liu/dsh-cybernetics). In a DSH profile, install this directory via `link:` — the plugin has zero runtime dependency on host packages (tool registration uses bare JSON Schema + inline validation) and doesn't need its own node_modules; requires Node >= 23.6.
100
+
101
+ Install from source:
102
+
103
+ ```bash
104
+ git clone https://github.com/MarchLiu/dsh-cybernetics.git
105
+ cd dsh-cybernetics
106
+ ```
107
+
108
+ Link-install in your DSH profile (e.g. `~/.dsh/profiles/web/`):
109
+
110
+ ```bash
111
+ # Add to dependencies in profile/package.json:
112
+ # "dsh-cybernetics": "link:/path/to/dsh-cybernetics"
113
+ # Then run in the profile directory:
114
+ pnpm install
115
+ ```
116
+
117
+ After installation:
118
+
119
+ - Server side: `src/index.ts` (the `cybernetics` tool + implicit trigger prompt segments) + `src/controller.ts` (inter-round state machine) + `src/executor.ts` (executor adapter layer);
120
+ - Client side: `lib/client.js` (the "Goals / Control" tabs + plugin-page config card), **requires a host restart after changes** (the client module graph is built at host startup) plus a browser refresh; `src/` changes require a host reload; changes to `workflows/round.js` take effect hot.
121
+
122
+ ### Plugin-page configuration
123
+
124
+ Configure directly on this plugin's card on the Web editor's "Plugins" page — since v0.8.1 the card itself renders every volatile field (field descriptions in the `Config` schema):
125
+
126
+ - **All-goals mode** (`allGoalsMode`, volatile): toggle, takes effect immediately on save, prompt segments remount instantly;
127
+ - **task/verify/triage agent models** (`taskAgentModel` / `verifyAgentModel` / `triageAgentModel`, volatile): dropdown-backed text fields — pick from the live system model list (shown as `providerName · provider/model`) or type "provider/model" / a bare model name; Enter or blur saves, empty inherits from the session agent (triage = the convergence-decision role, full rounds only). A configured value missing from the live model list shows an unavailable warning (runtime falls back to the session agent model) with a one-click Clear;
128
+ - **Recursion depth / parallel cap** (`roundsPerSubgoal` integer ≥1 / `maxConcurrentRounds` integer ≥0, volatile): number fields, Enter or blur saves, blank restores the default (an `unset` write);
129
+ - **Hard gate fields** (`hardGate`/`gateMode`/`gatedTools`/`gateAllowlist`/`gateAllowlistExtra`, non-volatile): not served to the card; edit them in the profile config document (saving triggers a plugin reload).
130
+
131
+ ### Advanced configuration: roles (profile yaml, agent-guided)
132
+
133
+ The `roles` field maps sub-agent roles to `{provider, model}` overrides (fixed `default`/`verify`/`triage` slots plus any named task role, e.g. `frontend`). **When do you need it?** The flat card fields (`taskAgentModel` / `verifyAgentModel` / `triageAgentModel`) already cover the three fixed roles; reach for `roles` only when you want per-role overrides for *named task roles* (roles passed via the round's `role` argument, e.g. a `writer` or `frontend` role on a different model) or need provider+model pairs beyond what the panel exposes.
134
+
135
+ Since v0.8.4 this field is **not editable in the GUI** — hand-editing a JSON object in a settings card is unfriendly. Instead, edit the profile config yaml directly (the user patch row in the profile's `cordis.patch.yml`, or the profile's own config document):
136
+
137
+ ```yaml
138
+ # In the profile's cordis.patch.yml — a user patch row:
139
+ - id: dsh-cybernetics
140
+ name: '@mars.liu/dsh-cybernetics'
141
+ config:
142
+ roles:
143
+ default: { provider: zai-coding-cn, model: glm-5.3 }
144
+ verify: { provider: zai-coding-cn, model: glm-5.3 }
145
+ frontend: { provider: zai-coding-cn, model: glm-5-turbo }
146
+ ```
147
+
148
+ After saving, **the host must be reloaded** (restart the host) for a non-volatile field change to take effect — roles is no longer volatile, so there is no live panel write path. The session agent can guide you through this: ask it to edit the yaml row, then restart the host. Runtime behavior is unchanged — flat panel fields merge over `roles.default` / `roles.verify` / `roles.triage`, named roles pass through untouched, and every role keeps the v0.8 availability fallback.
149
+
150
+ > **GUI verification steps**: (1) restart the host and hard-refresh the browser — `lib/client.js` changes load only with the client module graph built at host startup (without the restart the card keeps its old field set, e.g. just "All-goals mode"), and the `link:`-installed `src/` schema is likewise imported only at startup; (2) open the Web editor's "Plugins" page → this plugin's card/detail page, and confirm all six card fields are present (all-goals mode / task model / verify model / triage model / recursion depth / parallel cap) with no roles JSON textarea; (3) change any field (Enter/blur), confirm the control doesn't snap back and that the profile's `cordis.patch.yml` gains/updates the `{id: dsh-cybernetics, name: '@mars.liu/dsh-cybernetics', config: …}` user patch row; (4) run a goal round in a session to confirm the new value takes effect (e.g. with roundsPerSubgoal set to 2, the same subgoal pauses at its 2nd round).
151
+
152
+ > **Known host defects (root-caused)**: ① *Fields missing from the card* had TWO stacked causes: the card only rendered `allGoalsMode` before v0.8.1 (the host has no generic field renderer — fixed by this version, effective after a host restart + browser refresh), and on a host booted before v0.7.7 the served schema itself only carries `allGoalsMode` as volatile (stale runtime — the `link:`-installed schema is cached at host startup; restarting the host fixes it, see [docs/CHANGELOG.md](docs/CHANGELOG.md), v0.7.10 item 1). ② Saving rejected with "dsh: profile reload requires the root Include entry" = host dsh-app-boot dual module instances — the global CLI (npm tree) and the profile-side dsh-config-editor (pnpm tree) each hold their own module copy, so the module-level WeakMap guard (`bootstrapIncludes`) in `recompose` always misses, and **any** plugin's panel save is rejected before reaching disk; this cannot be fixed inside the plugin repo — either converge both sides onto the same module instance (e.g. symlink the profile's `node_modules/@deepseek-ai/dsh-app-boot` to the copy the CLI bootstraps with) or wait for an upstream fix (CHANGELOG v0.7.10 item 2). Workaround: manually maintain user patch rows in the profile's `cordis.patch.yml` (e.g. `- id: dsh-cybernetics` + `config: { roundsPerSubgoal: 3, allGoalsMode: true }`) and restart the host. Since v0.7.6 the card no longer swallows errors: if still rejected, it shows the host's rejection verbatim and snaps the control back.
153
+
154
+ ### In-session usage: the `cybernetics` tool
155
+
156
+ The session agent drives the loop in-session via the `cybernetics` tool (`toolName` is configurable); the implicit trigger prompt segments (`implicitTrigger: false` to disable) instruct it to classify input and call the tool per protocol without waiting for an explicit request:
157
+
158
+ | action | Semantics |
159
+ |---|---|
160
+ | `start` | New task: `{action:'start', title, brief}` creates a goal and (by default, in the background) starts its first round |
161
+ | `round` | Drives one round, blocking (on an existing goal or a newly created one) |
162
+ | `collect` | Folds a completed background round, yielding the controller decision (continue/pause) |
163
+ | `revise` | Revises the goal: creates a new goal (append-only), `reason` required, optional objective/title/depth/executor/abortPrevious |
164
+ | `cancel` | Aborts a running round (does not change the goal; use when only aborting, not revising) |
165
+ | `list` | Shows the state, round counts, and run handles of all goals on this session's goal board |
166
+
167
+ Companion auditing: each round's structured result lands in `.cybernetics/round-<goalId>-<N>.json`; revisions land in `.cybernetics/revisions-<newGoalId>.jsonl`; goal-board snapshots land in `.cybernetics/board-<sessionId>.json`.
168
+
169
+ ## Design Motivation and Rationale
170
+
171
+ (Distilled from the blog post; see the original for the full narrative; protocol details in [docs/protocol.md](docs/protocol.md).)
172
+
173
+ - **Why "the goal is an artifact of the agent's thinking"**: in the traditional paradigm the human is the controller and the model is the actuator, and the loop exists only in the human's head. Letting the session agent itself organize user input into a persistent goal (the user calibrates the agent through conversation, the agent revises the goal) is what makes the goal a trackable first-class state in the system — the first building block of the feedback loop.
174
+ - **Why append-only**: quietly rewriting a goal destroys all the value of "tracking an agent via a goal" — the goal on the tab is no longer the goal you originally set. Identity and history are expressed by the goal chain, just as git expresses history with immutable commits; each revision's reason + audit line is the explicit change record of calibration.
175
+ - **Why verify is independent of task**: if the verifier sees the executor's narrative, it degenerates into "checking whether the self-report is self-consistent" instead of "checking whether facts satisfy the goal." The measuring instrument must be independent of the thing measured — the protocol's core invariant: **context is rationed, not inherited** (task gets only the brief, verify only goal + report + evidence, triage only the verdict; `context: 'fork'` is never used). What persists is identity and the goal boundary, not the conversation history.
176
+ - **Why a hard gate**: early on, "the session agent does not execute tasks itself" was merely an exhortation in the prompt, and the model would inline-finish trivial tasks the moment it saw them. The hard gate physically removes execution tools at assembly time, turning exhortation into structure — the orchestrator's entire output is goals and briefs (information), the executor's entire output is changes to the world (action); the boundary of observability and controllability is drawn on roles, not on model self-discipline.
177
+ - **Oscillation defense and escalation path**: the per-round cap on consecutive iterations is an engineering defense against "oscillation caused by excessive feedback delay"; triage's human category is the controller's reserved escalation path for "disturbances that cannot be handled automatically"; `goalItselfQuestionable` structurally distinguishes "the direction is wrong" from "the execution is poor."
178
+
179
+ ## Relationship to Existing DSH Mechanisms
180
+
181
+ - **goal**: not a replacement — dsh-goal remains the durable setpoint and resume driver; this plugin is its working loop.
182
+ - **workflow**: each round is one `ctx.workflowEngine.start()` run; no new orchestration mechanism is added (built on top of `dsh-workflow`).
183
+ - **Completion confirmation**: the controller never declares completion by itself and never changes a goal's phase on the user's behalf; even `met` merely pauses and waits for the user.
184
+
185
+ ## Known Limitations
186
+
187
+ 1. **Host defect in settings writes**: as described in the "Plugin-page configuration" section, on this runtime, card saves may be rejected by the host before reaching disk (upstream [#12045](https://github.com/deepseek-ai/dsh/discussions/12045)); workarounds: restart the host + hard-refresh the browser, or manual patch rows + restart.
188
+ 2. **Save-semantics difference between volatile and non-volatile fields**: volatile fields (`allGoalsMode`, `taskAgentModel`, `verifyAgentModel`, `triageAgentModel`, `roundsPerSubgoal`, `maxConcurrentRounds`) take effect immediately on save without remount; non-volatile fields (`roles` since v0.8.4, `hardGate`, `gateMode`, `gatedTools`, `gateAllowlist*`, `toolName`, `implicitTrigger`) trigger a plugin-row remount on save, requiring a host reload to complete. Moreover, the host settings form only serves and only accepts volatile paths — if the running host's schema is too old, even volatile writes are rejected (a restart fixes it).
189
+ 3. **`link:` installation caveats**: the client bundle's registration id must equal the package.json `name` (`@mars.liu/dsh-cybernetics`); the loader row id may use the short name `dsh-cybernetics`. The plugin must hang at the **top level** of the profile's loader entries (the client scan enumerates only top-level rows); `src/` changes require a host reload, `lib/` changes require a host restart + browser refresh.
190
+ 4. Other protocol boundaries (the hard gate operating at the assembly layer rather than the execution layer, dsh-goal's focus slot not migrating on supersede, etc.) are covered in the "Known Boundaries" subsections of [docs/protocol.md](docs/protocol.md).
191
+
192
+ ## Evolution History
193
+
194
+ | Version | Key milestones |
195
+ |---|---|
196
+ | v0.2 | session-goal mode + implicit trigger + role-based sub agents + client Goals tab |
197
+ | v0.3 | multi-goal board + orchestrator protocol + async rounds (start/collect/cancel/list) + work-order briefs |
198
+ | v0.4 / v0.4.1 | hard gate; allowlist gate mode (sealing the change-tool long tail) |
199
+ | v0.5 | dual round depth (quick / full) |
200
+ | v0.6 | append-only goals, persistent executor, multi-goal projection, board persistence & rehydrate, Control-tab executor rows |
201
+ | v0.7–v0.7.8 | all-goals mode + revise auto decision tree; plugin-page config card; volatile model fields; write-landing forensics and save-failure visibility (56 offline tests) |
202
+ | v0.7.9–v0.7.10 | roles / roundsPerSubgoal / maxConcurrentRounds made volatile; dual root causes (stale-runtime panel rendering; dsh-app-boot dual-module-instance WeakMap miss on save rejection) located against real host source (58 offline tests) |
203
+ | v0.8.0 | call-level model fallback: a role's configured model failing with an availability error (quota/network/provider — signaled by the workflow runtime agent() hook's error type/message) retries once on the session agent's model; logic failures never swap models; fallbacks recorded on the round result, workflow/log, audit log and decision note (64 offline tests) |
204
+ | v0.8.1 | settings card covers all six volatile fields — the card is the only GUI surface (the host Plugins page draws no fields itself; dsh-client-ui-settings is the transport service, not a renderer); text/number/roles-JSON controls with validation, blank = restore default (unset), same write paths and failure fallback as the toggle (66 offline tests) |
205
+ | v0.8.2 | new `triageAgentModel` flat volatile field (isomorphic to task/verify: parsed into `roles.triage`, empty = inherit; triage = convergence-decision role, full rounds only; v0.8.0 fallback semantics pinned: no retry without an override); card covers all seven volatile fields (67 offline tests) |
206
+ | v0.8.3 | model dropdown + invalid-model handling: the three model fields are datalist-backed by the host's live model catalog (`remote.session.modelCatalog`, refreshed on `llm/adapters-updated`) with manual entry kept; a configured model deleted from the model settings renders an unavailable warning + one-click Clear (explicit unset — never a silent auto-unset, the trade-off is recorded in docs/CHANGELOG.md) and the runtime falls back to the session agent model (model-level deletion messages joined the v0.8.0 availability patterns; provider deletion was already covered by `no subagent provider registered` / AGENT_START) (68 offline tests) |
207
+
208
+ For the complete version-by-version record and dogfooding-verified corrections (including the four startup-chain corrections of 2026-11-02, two consecutive tab-rendering fixes, etc.), see [docs/CHANGELOG.md](docs/CHANGELOG.md).
209
+
210
+ ## License
211
+
212
+ MIT License, Copyright (c) 2026 MarchLiu. See [LICENSE](LICENSE) for the full license text.