@mars.liu/dsh-cybernetics 0.0.0-stage → 0.2.0
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/README.md +62 -2
- package/cordis.patch.yml +24 -0
- package/docs/protocol.md +88 -0
- package/lib/client.js +241 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +31 -4
- package/src/controller.ts +196 -0
- package/src/index.ts +393 -0
- package/src/schemas.ts +114 -0
- package/test/v03-offline.mjs +203 -0
- package/tsconfig.json +16 -0
- package/workflows/round.js +145 -0
package/README.md
CHANGED
|
@@ -1,3 +1,63 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-cybernetics
|
|
2
2
|
|
|
3
|
-
|
|
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 的 goal projection;
|
|
11
|
+
- 「控制」读 session list store(control stream 实时推送),递归展示本会话全部
|
|
12
|
+
agent(主控 + subagent catalog)的运行状态与各自 goal projection
|
|
13
|
+
(objective / phase / rounds / 受阻原因),顶部汇总运行中数量。
|
|
14
|
+
- 轮脚本:`workflows/round.js`(每次 run = 一轮;`roles` 表支持按角色覆盖 sub agent 模型)
|
|
15
|
+
|
|
16
|
+
## 状态
|
|
17
|
+
|
|
18
|
+
v0.3:多 goal 板 + 编排者协议 + 异步轮(`start`/`collect`/`cancel`/`list`)+ task agent 工作委托书(brief)。
|
|
19
|
+
轮脚本现读(改 `workflows/round.js` 热生效);`src/` 改动需宿主重载。
|
|
20
|
+
v0.2:session-goal 模式 + 隐式触发 + 角色化 sub agent + 客户端 goal 页签。
|
|
21
|
+
服务端已按 dsh 0.2.1-rc.3 真实 API 核对(tools.register / goals service / workingDirectory),
|
|
22
|
+
客户端按 ModuleLoader 包装格式手写(参照 dsh-client-ui-goal / dsh-context)。
|
|
23
|
+
**端到端回路尚未实跑**:workflow 轮脚本、页签挂载均待首次真实验证。
|
|
24
|
+
|
|
25
|
+
2026-11-02 触发链路实测:隐式提示注入 ✅、工具注册 ✅;执行原在
|
|
26
|
+
`ctx.get('workflowEngine')` 处报 "cannot get property ... without inject"
|
|
27
|
+
(宿主 cordis 对未注入服务的属性访问直接抛错,goal 护栏不可达)。
|
|
28
|
+
已改为静态 inject `workflowEngine` / `goals` / `workingDirectory`
|
|
29
|
+
(对齐 dsh-tool-workflow / dsh-tool-ralph)。
|
|
30
|
+
追加实测修正:首次重启后插件仍 pending——`workflowEngine` 是在 `delegation`
|
|
31
|
+
group 的 isolate 里提供的(`isolate: workflowEngine: true`),顶层 insert 的
|
|
32
|
+
行拿不到它。`cordis.patch.yml` 已改为按组 id 插入(`- id: delegation` +
|
|
33
|
+
`insert:`,loader 方言:patch id 指向 group 行时追加进该组 config)。
|
|
34
|
+
**待服务端重载后复测**:boot 不再 pending、工具可用、隐式提示段与客户端页签
|
|
35
|
+
在组作用域下仍正常注入。
|
|
36
|
+
第三次实测修正:按组 id 插入也被跳过(dump-config 报 `entry "delegation" not
|
|
37
|
+
found`)——`delegation` 组不在顶层 patch 索引,而是嵌在 `preset-standard`
|
|
38
|
+
(`@deepseek-ai/dsh-agent-preset`)的 `config.plugins` 里,patch 应用时那只是
|
|
39
|
+
普通 config 对象。最终方案:profile 层 patch 整体重写 `preset-standard` 的
|
|
40
|
+
config(Web 编辑器保存走的就是这条路),把 `dsh-cybernetics` 加进其
|
|
41
|
+
delegation 组;本插件的 bundle patch 清空为 `[]`。
|
|
42
|
+
第四次实测修正:页签仍未出现——浏览器实测发现 boot graph 的 client combo
|
|
43
|
+
不含本包:客户端扫描只枚举**顶层** loader entries,挂进 preset 的行对它不可
|
|
44
|
+
见。最终方案:插件回到顶层行,`workflowEngine` 不再静态 inject,执行时经
|
|
45
|
+
`ctx.agentPresets.serviceFor(agent, 'workflowEngine')` 从 Agent 的 preset
|
|
46
|
+
mount 读取(registry 的官方 root-realm 访问器);inject 收窄为五个根级服务
|
|
47
|
+
(tools/systemPrompt/goals/workingDirectory/agentPresets)。同轮还修复 goal
|
|
48
|
+
护栏:`goals.get()` 对 completed/paused goal 仍返回视图,现严格要求
|
|
49
|
+
`phase === 'active'`,并按 goal id 重置轮次状态。dump 复算确认:顶层行就位、
|
|
50
|
+
preset-standard 恢复原样、无 patch 警告。
|
|
51
|
+
页签渲染两连修(浏览器实测定位):① inject 手动传的 `t` 遮蔽了槽位按
|
|
52
|
+
`locale: NS` 注入的正确翻译函数(字典注册前 bind 会解析为空串)——inject
|
|
53
|
+
不再传 t;② 宿主 jsx 变体忽略 `jsx(type, props, children)` 的第三参,
|
|
54
|
+
children 必须放进 `props.children`(对照 dsh-client-ui-trajectory 编译产物),
|
|
55
|
+
否则所有文本静默丢失、元素塌缩成空胶囊。修复后「目标 / 控制」两页签均完整
|
|
56
|
+
渲染(2026-11-02:goal 徽章/objective/轮次 + 1/11 agent 树与各自 goal 状态)。
|
|
57
|
+
|
|
58
|
+
## 与 dsh 现有机制的关系
|
|
59
|
+
|
|
60
|
+
- goal:不替代——dsh-goal 仍是 durable setpoint 与续跑驱动;本插件是它的工作回路。
|
|
61
|
+
- workflow:每轮就是一次 `ctx.workflowEngine.start()` run;不新增编排机制。
|
|
62
|
+
- 完成确认:controller 永不自宣完成、不代改 goal phase;met 也只暂停等用户。
|
|
63
|
+
- 多 goal(未来):按 super-agent 模式,sub goal 下沉到 sub session 各自的 dsh-goal。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# dsh-cybernetics bundle patch: inserts the plugin row into the profile roster.
|
|
2
|
+
# Dual-face plugin by package name: the Node half (src/index.ts) provides the
|
|
3
|
+
# cybernetics tool and the system-prompt guidance; the browser half
|
|
4
|
+
# (exports["./client"], declared via dsh.client) renders the「目标 / Goal」tab.
|
|
5
|
+
#
|
|
6
|
+
# Optional config (override from a later layer, e.g. the profile patch):
|
|
7
|
+
# - id: dsh-cybernetics
|
|
8
|
+
# config:
|
|
9
|
+
# roundsPerSubgoal: 3
|
|
10
|
+
# implicitTrigger: true
|
|
11
|
+
# roles:
|
|
12
|
+
# default: { provider: zai-coding-cn, model: glm-5.3 }
|
|
13
|
+
# verify: { provider: zai-coding-cn, model: glm-5.3 }
|
|
14
|
+
# writer: { provider: zai-coding-cn, model: glm-5-turbo }
|
|
15
|
+
# Top-level insert: the client-module boot graph enumerates only top-level
|
|
16
|
+
# loader entries, so the Node half must load at the root for the「目标」tab to
|
|
17
|
+
# register. The workflowEngine service (published inside each Agent's preset
|
|
18
|
+
# delegation group) is NOT statically injected — apply() reads it per call via
|
|
19
|
+
# `ctx.agentPresets.serviceFor(agent, 'workflowEngine')`; see src/index.ts.
|
|
20
|
+
# History 2026-11-02: a top-level static inject of workflowEngine hung boot;
|
|
21
|
+
# mounting the whole plugin inside preset-standard silenced the client scan.
|
|
22
|
+
- insert:
|
|
23
|
+
- id: dsh-cybernetics
|
|
24
|
+
name: dsh-cybernetics
|
package/docs/protocol.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Cybernetics 工作协议 v0.3
|
|
2
|
+
|
|
3
|
+
目标-行动-测量-校准的迭代回路,构建在 DSH 的 `dsh-workflow` 编排与 `dsh-goal` 持久目标之上。
|
|
4
|
+
|
|
5
|
+
## v0.3 变化:多 goal + 编排者 session agent
|
|
6
|
+
|
|
7
|
+
- **同一 session 支持多个并发 goal**:插件维护自己的 goal 板(`boards: session -> goalId -> loop`)。
|
|
8
|
+
dsh-goal 仍是每 session 单槽,降级为**焦点 goal**(GUI 页签 + goal-round-driver 自动续跑);
|
|
9
|
+
新建 registry goal 时若 dsh-goal 空槽则尽力同步进去。
|
|
10
|
+
- **session agent 是编排者,从不亲自执行任务**:它识别用户输入、提取工作委托书(brief)、
|
|
11
|
+
启动/收取/取消 task agent 轮、向用户转述反馈。
|
|
12
|
+
- **异步轮**:`action: 'start'` 后台启动一轮并立即返回;轮结束时插件经 `agent.followup()`
|
|
13
|
+
向 session agent 收件箱排队一条完成通知(与 dsh-goal-round-driver 同一机制),
|
|
14
|
+
session agent 随后 `action: 'collect'` 折叠结果得到 continue/pause 决策。
|
|
15
|
+
- **修正路径**:用户对已有任务的修正由 session agent 裁决——`cancel`(中止在跑的轮,
|
|
16
|
+
底层 `WorkflowRun.cancel()`)后带修正 brief 重启一轮,或等 `collect` 后开修正轮。
|
|
17
|
+
- v0.2 的单 goal 阻塞调用保持兼容:无 `action`/`goalId`/`title` 时驱动当前 dsh-goal 焦点 goal。
|
|
18
|
+
|
|
19
|
+
## v0.2 变化:goal 回到 session
|
|
20
|
+
|
|
21
|
+
- **goal 是 session agent 的思考产物**,通过 dsh-goal 服务持久化(create/edit 是模型动词)。
|
|
22
|
+
用户不精确编辑任何目标文件——**用户通过对话校准 agent,agent 修订 goal**。
|
|
23
|
+
- 同一工作区的每个 session 可有自己的 goal(dsh-goal 每 session 单槽)。
|
|
24
|
+
- 未来多 goal:按 super-agent 模式(DeepSeek Harness《Super-agent 设计探索》,2026-11-01,
|
|
25
|
+
开源原型 dsh-superagent-prototype),super agent session 持有各 sub agent 的 goal 并据此调度;
|
|
26
|
+
每条 sub goal 下沉到 sub session 各自的 dsh-goal。v0.2 先做单 session 单 goal。
|
|
27
|
+
|
|
28
|
+
## 角色
|
|
29
|
+
|
|
30
|
+
| 角色 | 实现 | 职责 |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| Session Agent | 调用方 | **编排者**:识别输入(新任务/修正/反馈),维护 goal 板,启动/收取/取消轮,向用户转述;从不亲自执行任务 |
|
|
33
|
+
| Controller | `src/controller.ts` | 轮间状态机:`startRound`/`foldRound`/`runRound`,推进/暂停/终止,维护轮审计日志 |
|
|
34
|
+
| Task Agent | workflow `agent()` | 独立 sub agent(无对话历史),面向工作委托书(brief)实际执行,产出结构化交付报告 |
|
|
35
|
+
| Verify Agent | workflow `agent()` | 把总目标拆成可判定方面逐条验收,产出结构化 verdict |
|
|
36
|
+
| Triage Agent | workflow `agent()` | 对后续线索四分拣:auto / defer / reject / human |
|
|
37
|
+
| 用户 | 外环 | 对话校准(goal 修订由 session agent 执行)、裁定 human 类目、确认完成 |
|
|
38
|
+
|
|
39
|
+
## 输入分类协议(session agent 职责)
|
|
40
|
+
|
|
41
|
+
1. **新任务** → `cybernetics { action:'start', title, brief }` 建 goal 并后台启动 task agent;
|
|
42
|
+
`brief` 是完整工作委托书(背景、路径、约束、验收标准),详细到 subagent 无需对话历史即可完成。
|
|
43
|
+
2. **已有任务的修正** → 裁决打断或等待:打断 = `cancel` 该 goalId 后以修正内容重写 brief 重启一轮;
|
|
44
|
+
等待 = `collect` 收完当轮后,以 verdict gaps + 修正内容构造新一轮。
|
|
45
|
+
3. **反馈到达**(`round` 的返回值,或异步轮完成通知后的 `collect`)→ 三选一或组合:
|
|
46
|
+
向用户总结;启动验证轮;以缺口为 brief 启动修正轮。
|
|
47
|
+
|
|
48
|
+
## 隐式触发
|
|
49
|
+
|
|
50
|
+
插件注入 system prompt 段(`implicitTrigger: false` 可关):session agent 按上述分类协议
|
|
51
|
+
对每个实质性用户输入建 goal / 启动 task agent / 处理反馈,**不等显式要求**。
|
|
52
|
+
无任何可解析 goal(无 goalId、无 title、无活跃焦点 goal)时调用工具会报错并提示。
|
|
53
|
+
|
|
54
|
+
## 结构化判定
|
|
55
|
+
|
|
56
|
+
- Task 报告:`{summary, done, artifacts, evidence, nextStepThoughts}`
|
|
57
|
+
- Verdict:`{met, criteria:[{criterion,pass,evidence}], gaps, goalItselfQuestionable}` —
|
|
58
|
+
`goalItselfQuestionable=true` 时回路禁止自动迭代,回对话重新校准(session agent 修订 goal)。
|
|
59
|
+
- Triage:`{auto, defer, reject, human:[{step,rationale,question}]}` — 方向性改动宁可多给 human。
|
|
60
|
+
|
|
61
|
+
## 回路终止条件
|
|
62
|
+
|
|
63
|
+
1. `met=true` → 报告完成,**等用户确认**;controller 不自行宣告、不代改 goal phase。
|
|
64
|
+
2. `goalItselfQuestionable=true` 或存在 human 类目 → 暂停,回对话。
|
|
65
|
+
3. 连续迭代上限(默认 3 轮/子目标,总轮数由 dsh-goal round cap 兜底)→ 暂停并汇报。
|
|
66
|
+
|
|
67
|
+
## 角色化 sub agent
|
|
68
|
+
|
|
69
|
+
Config `roles` 表把角色名映射到 `{provider, model}` 覆盖,round 脚本按 `role` 参数取用:
|
|
70
|
+
`default`/`verify`/`triage` 固定槽位 + 任意命名任务角色(如 `frontend`/`backend`/`writer`)。
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
- id: dsh-cybernetics
|
|
74
|
+
config:
|
|
75
|
+
roles:
|
|
76
|
+
default: { provider: zai-coding-cn, model: glm-5.3 }
|
|
77
|
+
verify: { provider: zai-coding-cn, model: glm-5.3 }
|
|
78
|
+
frontend: { provider: zai-coding-cn, model: glm-5.3 }
|
|
79
|
+
writer: { provider: zai-coding-cn, model: glm-5-turbo }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 审计与可视化
|
|
83
|
+
|
|
84
|
+
- 每轮结构化结果落 `<workspace>/.cybernetics/round-<goalId>-<N>.json`(追加式审计日志;
|
|
85
|
+
goalId 进文件名,防止多 goal 各自从 1 计轮互相覆盖——v0.3 实机实测发现并修复)。
|
|
86
|
+
- goal 状态由 dsh-goal 的 session projection(`goal` 投影)承载,客户端「目标 / Goal」页签
|
|
87
|
+
(`lib/client.js`,conversation.view slot)实时读取渲染:objective 文本 + phase + rounds。
|
|
88
|
+
v0.2 为纯文本渲染;后续演进:任务分解、状态跟踪、verdict 历史可视化(参照 dsh-context 范式)。
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// dsh-cybernetics client module: the「目标 / Goal」conversation tab.
|
|
2
|
+
// Rendered from the session's `goal` projection (dsh-goal), pushed live by the
|
|
3
|
+
// host — no plugin HTTP route needed. Hand-written ModuleLoader wrapper, same
|
|
4
|
+
// format the host bundler emits (see dsh-context / dsh-maze client.js).
|
|
5
|
+
/* global window */
|
|
6
|
+
window.__ModuleLoader__.load({
|
|
7
|
+
id: 'dsh-cybernetics',
|
|
8
|
+
factory: (require) => {
|
|
9
|
+
var module = { exports: {} };
|
|
10
|
+
var exports = module.exports;
|
|
11
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
12
|
+
const react = require('react');
|
|
13
|
+
const jsx = require('react/jsx-runtime');
|
|
14
|
+
|
|
15
|
+
//#region locale dictionaries (zh is the key-set source of truth)
|
|
16
|
+
const NS = 'cybernetics';
|
|
17
|
+
const zh = {
|
|
18
|
+
'view.goal': '目标',
|
|
19
|
+
'tab.description': '展示当前会话的目标与回路进度。',
|
|
20
|
+
'phase.active': '进行中',
|
|
21
|
+
'phase.paused': '已暂停',
|
|
22
|
+
'phase.blocked': '受阻',
|
|
23
|
+
'phase.complete': '已完成',
|
|
24
|
+
'rounds': '已推进 {round} 轮 · 上限 {max}',
|
|
25
|
+
'noGoal': '当前会话没有目标 — 通过对话描述任务,agent 会建立并维护目标。',
|
|
26
|
+
'blocked': '受阻原因',
|
|
27
|
+
'loopHint': 'agent 通过 cybernetics 工具推进:task → verify → triage 每调用一轮。',
|
|
28
|
+
'view.control': '控制',
|
|
29
|
+
'control.description': '查看当前会话各 agent 的目标与运行状态。',
|
|
30
|
+
'agent.lead': '主控',
|
|
31
|
+
'agent.running': '运行中',
|
|
32
|
+
'agent.idle': '空闲',
|
|
33
|
+
'agent.external': '外部',
|
|
34
|
+
'agent.noGoal': '无目标',
|
|
35
|
+
'agent.noData': '目标数据未同步',
|
|
36
|
+
'agents.empty': '本会话当前没有子 agent。',
|
|
37
|
+
'agents.count': '{running}/{total} 个 agent 运行中',
|
|
38
|
+
};
|
|
39
|
+
const en = {
|
|
40
|
+
'view.goal': 'Goal',
|
|
41
|
+
'tab.description': 'Current session goal and loop progress.',
|
|
42
|
+
'phase.active': 'Active',
|
|
43
|
+
'phase.paused': 'Paused',
|
|
44
|
+
'phase.blocked': 'Blocked',
|
|
45
|
+
'phase.complete': 'Complete',
|
|
46
|
+
'rounds': '{round} rounds started · cap {max}',
|
|
47
|
+
'noGoal': 'No goal in this session — describe the task in conversation; the agent will establish and maintain the goal.',
|
|
48
|
+
'blocked': 'Blocked reason',
|
|
49
|
+
'loopHint': 'The agent advances via the cybernetics tool: one call = one round of task → verify → triage.',
|
|
50
|
+
'view.control': 'Control',
|
|
51
|
+
'control.description': 'Goals and run states of every agent in the current session.',
|
|
52
|
+
'agent.lead': 'Lead',
|
|
53
|
+
'agent.running': 'Running',
|
|
54
|
+
'agent.idle': 'Idle',
|
|
55
|
+
'agent.external': 'External',
|
|
56
|
+
'agent.noGoal': 'No goal',
|
|
57
|
+
'agent.noData': 'Goal data not synced',
|
|
58
|
+
'agents.empty': 'No child agents in this session yet.',
|
|
59
|
+
'agents.count': '{running}/{total} agents running',
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
|
|
63
|
+
const PHASE_KEY = {
|
|
64
|
+
active: 'phase.active',
|
|
65
|
+
paused: 'phase.paused',
|
|
66
|
+
blocked: 'phase.blocked',
|
|
67
|
+
complete: 'phase.complete',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
//#region goal view component
|
|
71
|
+
// props.goal: ObservableSnapshot face of the session `goal` projection.
|
|
72
|
+
// The snapshot may be the raw state ({current: GoalProjection | null, ...})
|
|
73
|
+
// or the projection value itself — normalize both; undefined = not yet arrived.
|
|
74
|
+
function GoalView(props) {
|
|
75
|
+
const t = props.t;
|
|
76
|
+
const raw = react.useSyncExternalStore(props.goal.subscribe, props.goal.getSnapshot);
|
|
77
|
+
if (raw === undefined) return null;
|
|
78
|
+
const goal = raw !== null && typeof raw === 'object' && 'current' in raw ? raw.current : raw;
|
|
79
|
+
if (goal === null || goal === undefined) {
|
|
80
|
+
return jsx.jsx('div', { style: styles.empty, children: t('noGoal') });
|
|
81
|
+
}
|
|
82
|
+
const g = goal.goal ?? goal;
|
|
83
|
+
return jsx.jsxs('div', { style: styles.root, children: [
|
|
84
|
+
jsx.jsx('div', { style: styles.badgeRow, children:
|
|
85
|
+
jsx.jsx('span', { style: { ...styles.badge, ...phaseStyle(g.phase) }, children: t(PHASE_KEY[g.phase] ?? g.phase ?? '') }),
|
|
86
|
+
}),
|
|
87
|
+
jsx.jsx('div', { style: styles.objective, children: g.objective ?? t('noGoal') }),
|
|
88
|
+
g.phase === 'blocked' && g.blockedReason
|
|
89
|
+
? jsx.jsxs('div', { style: styles.blocked, children: [t('blocked') + ':', g.blockedReason.message] })
|
|
90
|
+
: null,
|
|
91
|
+
jsx.jsx('div', { style: styles.meta, children: t('rounds', { round: goal.roundsStarted ?? 0, max: g.maxGoalRounds ?? '—' }) }),
|
|
92
|
+
jsx.jsx('div', { style: styles.hint, children: t('loopHint') }),
|
|
93
|
+
] });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
var styles = {
|
|
97
|
+
root: { display: 'flex', flexDirection: 'column', gap: 8, padding: '12px 16px', overflow: 'auto' },
|
|
98
|
+
empty: { padding: '12px 16px', opacity: 0.6 },
|
|
99
|
+
badgeRow: { display: 'flex', gap: 6 },
|
|
100
|
+
badge: { fontSize: 12, padding: '1px 8px', borderRadius: 999, border: '1px solid currentColor' },
|
|
101
|
+
objective: { whiteSpace: 'pre-wrap', lineHeight: 1.5, fontSize: 13 },
|
|
102
|
+
blocked: { color: '#d29922', fontSize: 12 },
|
|
103
|
+
meta: { opacity: 0.65, fontSize: 12 },
|
|
104
|
+
hint: { opacity: 0.45, fontSize: 11 },
|
|
105
|
+
agentRow: { borderLeft: '2px solid transparent', borderRadius: 4 },
|
|
106
|
+
agentMain: { display: 'flex', flexDirection: 'column', gap: 2 },
|
|
107
|
+
agentHead: { display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 },
|
|
108
|
+
agentName: { fontSize: 12, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
|
|
109
|
+
agentGoal: { fontSize: 11, opacity: 0.7, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
|
|
110
|
+
branch: { opacity: 0.4, fontSize: 11 },
|
|
111
|
+
leadBadge: { fontSize: 10, padding: '0 6px', borderRadius: 999, border: '1px solid currentColor', color: '#58a6ff' },
|
|
112
|
+
runBadge: { fontSize: 10, padding: '0 6px', borderRadius: 999, border: '1px solid currentColor' },
|
|
113
|
+
runOn: { color: '#3fb950' },
|
|
114
|
+
runOff: { color: '#8b949e' },
|
|
115
|
+
};
|
|
116
|
+
function phaseStyle(phase) {
|
|
117
|
+
if (phase === 'active') return { color: '#3fb950' };
|
|
118
|
+
if (phase === 'blocked') return { color: '#d29922' };
|
|
119
|
+
if (phase === 'complete') return { color: '#58a6ff' };
|
|
120
|
+
return { color: '#8b949e' };
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
|
|
124
|
+
//#region control view component
|
|
125
|
+
// The「控制 / Control」tab: every agent of the current session — the lead
|
|
126
|
+
// itself plus the subagent catalog (recursively) — with its run state and
|
|
127
|
+
// its own durable goal (dsh-goal projection values). All reactive input
|
|
128
|
+
// comes from the session list store, which the Session Controller updates
|
|
129
|
+
// live via the control stream (running flips, goal/control frames).
|
|
130
|
+
const MAX_TREE_DEPTH = 3;
|
|
131
|
+
|
|
132
|
+
/** Depth-first agent rows rooted at `rootId`: lead + subagentCatalog, recursed. */
|
|
133
|
+
function collectAgents(list, rootId) {
|
|
134
|
+
const rows = [];
|
|
135
|
+
const walk = (id, depth, catalogLabel, mode) => {
|
|
136
|
+
if (depth > MAX_TREE_DEPTH) return;
|
|
137
|
+
const row = list.byId[id];
|
|
138
|
+
const catalog = list.projectionsBySession[id]?.values?.subagentCatalog ?? [];
|
|
139
|
+
rows.push({
|
|
140
|
+
id,
|
|
141
|
+
depth,
|
|
142
|
+
lead: depth === 0,
|
|
143
|
+
label: catalogLabel ?? row?.projectionValues?.subagent?.label,
|
|
144
|
+
external: mode === 'external',
|
|
145
|
+
running: row?.running === true,
|
|
146
|
+
goal: row?.projectionValues?.goal ?? null,
|
|
147
|
+
hasRow: row !== undefined,
|
|
148
|
+
childCount: catalog.length,
|
|
149
|
+
});
|
|
150
|
+
for (const entry of catalog) walk(entry.id, depth + 1, entry.label, entry.mode);
|
|
151
|
+
};
|
|
152
|
+
walk(rootId, 0);
|
|
153
|
+
return rows;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Render one goal snapshot face shared by both tabs. */
|
|
157
|
+
function goalFace(t, goal, noDataText) {
|
|
158
|
+
if (goal === undefined) return t('agent.noData');
|
|
159
|
+
if (goal === null || goal.goal === undefined) return t('agent.noGoal');
|
|
160
|
+
const g = goal.goal;
|
|
161
|
+
const phase = t(PHASE_KEY[g.phase] ?? g.phase);
|
|
162
|
+
const rounds = ' · ' + t('rounds', { round: goal.roundsStarted ?? 0, max: g.maxGoalRounds ?? '—' });
|
|
163
|
+
return phase + rounds + (g.phase === 'blocked' && g.blockedReason ? ' · ' + t('blocked') + ':' + g.blockedReason.message : '');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function AgentRow(props) {
|
|
167
|
+
const t = props.t;
|
|
168
|
+
const a = props.agent;
|
|
169
|
+
const statusText = a.external ? t('agent.external') : a.running ? t('agent.running') : t('agent.idle');
|
|
170
|
+
return jsx.jsx('div', { style: { ...styles.agentRow, paddingLeft: 16 + a.depth * 18 }, children:
|
|
171
|
+
jsx.jsxs('div', { style: styles.agentMain, children: [
|
|
172
|
+
jsx.jsxs('div', { style: styles.agentHead, children: [
|
|
173
|
+
a.depth > 0 ? jsx.jsx('span', { style: styles.branch, children: '└' }) : null,
|
|
174
|
+
jsx.jsx('span', { style: styles.agentName, children: a.label ?? a.id }),
|
|
175
|
+
a.lead ? jsx.jsx('span', { style: styles.leadBadge, children: t('agent.lead') }) : null,
|
|
176
|
+
jsx.jsx('span', { style: { ...styles.runBadge, ...(a.running ? styles.runOn : styles.runOff) }, children: statusText }),
|
|
177
|
+
] }),
|
|
178
|
+
jsx.jsx('div', { style: styles.agentGoal, children: goalFace(t, a.goal, t('agent.noData')) }),
|
|
179
|
+
] }),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function ControlView(props) {
|
|
184
|
+
const t = props.t;
|
|
185
|
+
const sessions = props.sessions;
|
|
186
|
+
const list = react.useSyncExternalStore(sessions.list.subscribe, sessions.list.getSnapshot);
|
|
187
|
+
const agents = collectAgents(list, props.sessionId);
|
|
188
|
+
const runningCount = agents.filter((a) => a.running).length;
|
|
189
|
+
return jsx.jsxs('div', { style: styles.root, children: [
|
|
190
|
+
jsx.jsxs('div', { style: styles.badgeRow, children: [
|
|
191
|
+
jsx.jsx('span', { style: styles.badge, children: t('agents.count', { total: agents.length, running: runningCount }) }),
|
|
192
|
+
] }),
|
|
193
|
+
...agents.map((a) => jsx.jsx(AgentRow, { agent: a, t }, a.id + ':' + a.depth)),
|
|
194
|
+
agents.length <= 1 ? jsx.jsx('div', { style: styles.hint, children: t('agents.empty') }) : null,
|
|
195
|
+
] });
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
|
|
199
|
+
//#region plugin apply
|
|
200
|
+
const name = 'dsh-cybernetics';
|
|
201
|
+
const inject = ['slots', 'sessions', 'locale'];
|
|
202
|
+
|
|
203
|
+
function apply(ctx) {
|
|
204
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-cybernetics: dictionaries');
|
|
205
|
+
const t = ctx.locale.bind(NS);
|
|
206
|
+
|
|
207
|
+
// Conversation-view tab, same slot the trajectory/context tabs use.
|
|
208
|
+
// `t` reaches the view as a slot-provided prop from `locale: NS`
|
|
209
|
+
// (PropsLocale) — do NOT also pass a hand-bound t through inject:
|
|
210
|
+
// bind() before dictionary registration resolves empty strings and
|
|
211
|
+
// shadows the injected one (verified live 2026-11-02).
|
|
212
|
+
ctx.slots.inject('conversation.view', () => ctx.slots.register({
|
|
213
|
+
name: 'conversation.view',
|
|
214
|
+
id: 'cybernetics-goal',
|
|
215
|
+
order: 15,
|
|
216
|
+
locale: NS,
|
|
217
|
+
label: () => t('view.goal'),
|
|
218
|
+
inject: (sessionId) => {
|
|
219
|
+
const binding = ctx.sessions.binding(sessionId);
|
|
220
|
+
if (binding === undefined) throw new Error('dsh-cybernetics: session "' + sessionId + '" unavailable');
|
|
221
|
+
return { goal: binding.session.projections.faceOf('goal') };
|
|
222
|
+
},
|
|
223
|
+
}, GoalView));
|
|
224
|
+
|
|
225
|
+
// Control tab: goals + run states of every agent in this session,
|
|
226
|
+
// driven by the session list store (live via the control stream).
|
|
227
|
+
ctx.slots.inject('conversation.view', () => ctx.slots.register({
|
|
228
|
+
name: 'conversation.view',
|
|
229
|
+
id: 'cybernetics-control',
|
|
230
|
+
order: 16,
|
|
231
|
+
locale: NS,
|
|
232
|
+
label: () => t('view.control'),
|
|
233
|
+
inject: (sessionId) => ({ sessionId, sessions: ctx.sessions }),
|
|
234
|
+
}, ControlView));
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
|
|
238
|
+
module.exports = { name, inject, apply };
|
|
239
|
+
return module.exports;
|
|
240
|
+
},
|
|
241
|
+
});
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mars.liu/dsh-cybernetics",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Cybernetic work protocol for DSH: the session goal (dsh-goal) driven loop of task -> verify -> triage agents, implicitly advanced via one workflow run per round. Server plugin + client goal tab.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./client": "./lib/client.js",
|
|
11
|
+
"./package.json": "./package.json",
|
|
12
|
+
"./locale/*.json": "./locale/*.json"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=23.6"
|
|
16
|
+
},
|
|
17
|
+
"dsh": {
|
|
18
|
+
"bundle": {
|
|
19
|
+
"patch": "./cordis.patch.yml"
|
|
20
|
+
},
|
|
21
|
+
"client": {
|
|
22
|
+
"inject": [
|
|
23
|
+
"@deepseek-ai/dsh-api-session-controller",
|
|
24
|
+
"@deepseek-ai/dsh-client-locale",
|
|
25
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
26
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
27
|
+
"@deepseek-ai/dsh-client-ui-session"
|
|
28
|
+
],
|
|
29
|
+
"platform": "web",
|
|
30
|
+
"immediately": true
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|