@stofone/dsh-agent-roster 0.0.0-stage → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,157 @@
1
+ # Changelog
2
+
3
+ 遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 与 [SemVer](https://semver.org/lang/zh-CN/)。
4
+
5
+ ## [0.5.1] - 2026-10-29
6
+
7
+ ### Fixed
8
+
9
+ - **配置视图漏了两个字段**:`configView` 只转发了 `rosterDirs`/`knownToolPolicies`/`agentTeamsStateDir`,
10
+ 面板与路由读不到 `indexes` 与 `region`——于是设置里关掉远端(`indexes: false`)不生效、
11
+ `region: china` 也不生效。现在四个字段都转发,`indexes` 的三种取值(`undefined`/空数组 = 官方源、
12
+ `false` = 关掉远端、数组 = 自定义源)原样保留。
13
+ - **缺省档案库目录退回旧的相对路径**:`rosterDirs` 未配置时曾退回 `["rosters"]`(相对当前工作区),
14
+ 与 0.3.0 起的“留空 = 全局库 `$DSH_HOME/agent-roster`”不一致;一键拉取会把档案写进工作区而不是全局库。
15
+ 现在缺省统一为空串(全局库)。测试 162 → 163。
16
+
17
+ ### Added
18
+
19
+ - 官方索引 `index/registry.json` 新增三个迁移自上游的 collection:
20
+ `agent-teams`(wshobson/agents,MIT,4.2 万星)、`metagpt-company`(MetaGPT,MIT)、
21
+ `openmanus`(OpenManus,MIT)。三份档案在各自仓库的 `rosters/<id>/roster.yaml`,可在面板“档案源”页一键拉取。
22
+
23
+ ## [0.5.0] - 2026-10-29
24
+
25
+ ### Added
26
+
27
+ - **公共档案源(registry)**:`indexes` 配置订阅 `https` 的 `registry.json` 或 `npm:pkg`;默认只有官方源
28
+ `STofone/dsh-agent-roster` 的 `index/registry.json`。索引按 **TTL 一小时** 缓存(等于未认证 GitHub 的
29
+ 60 次/小时配额),`refresh=1` 才绕过缓存且有 60 秒最短间隔;网络失败沿用上次缓存并说明原因,
30
+ 不会把「没拉到」显示成「没有档案」。
31
+ - **一键拉取到本机**:面板新增「档案源」页签,列出每个 collection 的标题、仓库、登记份数与上次拉取时间,
32
+ 点「拉取到本机」把整目录写进全局档案库。写入是幂等的:内容相同跳过、本地改过默认**不覆盖**并报冲突数,
33
+ 只有显式 `overwrite` 才替换。对应 `POST /plugins/dsh-agent-roster/install`。
34
+ - 第 5 个工具 `roster_index`:列出索引源状态与 collection;给定 `collection` 时只读地把那套档案拉下来看。
35
+ - `lib/serialize.js`:把已校验档案还原成落盘 JSON(只写语义字段,不带 `inherited`/`origin`/`file` 之类副产物)。
36
+ - `.github/workflows/registry.yml`:校验 `index/registry.json`、比对仓库真实文件、跑测试。
37
+
38
+ ### Fixed
39
+
40
+ - `indexes: []` 现在明确表示「用官方源」——此前空数组会被当成「没有源」,装上插件后档案源页签就是空的。
41
+
42
+ ## [0.4.0] - 2026-10-29
43
+
44
+ ### Added
45
+
46
+ - 成员声明字段 `description`、`aliases`、`skills`、`examples`:文档与派活用的信息,明确标注为声明层。
47
+ `aliases` 参与核对匹配,改名后不再出现「未部署 + 无档案」的假报警;`skills` 与宿主技能目录交叉核对。
48
+ - 档案级 `captain` 段:记录队长(=创建团队的那个会话)应该是什么样,给出可复制的 profile 片段。
49
+ agent-teams 没有 captain 成员行,档案只声明、不改写会话。
50
+ - `schema/roster.v1.schema.json`:编辑器与外部工具用的 JSON Schema,由 `test/schema.test.js` 与运行时常量对齐。
51
+ - 测试:声明字段校验、别名冲突、队长段、别名核对、技能核对、schema 对齐(共 153 例)。
52
+
53
+ ## [0.3.0] - 2026-10-29
54
+
55
+ ### Changed
56
+
57
+ - **档案库默认全局**:`rosterDirs` 留空时使用 `<DSH_HOME>/agent-roster`(默认 `~/.dsh/agent-roster`),
58
+ 不再按工作区展开。docker 部署下 `$DSH_HOME` 就是被挂载的数据卷,档案天然落在持久化位置。
59
+ 只有显式写相对路径(如 `examples`)才按工作区解析——那是显式例外。
60
+ - 四个模型可见工具的默认档案库、面板快照、写路由白名单统一走同一套解析(`lib/home.js`),
61
+ 「工具读的库」和「面板写的库」不再可能指向不同目录。
62
+ - 面板配置区新增「数据目录」一行;档案库目录留空时显示全局默认路径,而不是空白。
63
+
64
+ ### Added
65
+
66
+ - `lib/home.js`:`$DSH_HOME` > `~/.dsh` 的解析、`~` 展开、全局/工作区目录判定。
67
+ - 首次向不存在的全局目录写入时自动创建目录树(面板新建/导入即可用,无需先手动建目录)。
68
+ - 测试:全局库解析、空配置只有一个全局根目录、首次写入自动建目录(共 135 例)。
69
+
70
+ ### Fixed
71
+
72
+ - 空档案库不再产出任何诊断或体检提示:目录还没建(首次保存会自动建)、目录里还没有档案、
73
+ 以及「没配 knownToolPolicies」都不再各发一条 info/warn。面板「体检」页签的角标只统计
74
+ 真正需要处理的问题(warn/error),info 不再挂数字。
75
+
76
+ ## [0.2.1] - 2026-10-29
77
+
78
+ ### Fixed
79
+
80
+ - 面板样式只用宿主语义 token(`--dsw-alias-*`),删掉全部硬编码颜色和浅色兜底——暗色主题下不再
81
+ 「白底白字」。正文、徽标、按钮、提示条、模态框在两个主题下都按 WCAG AA 复核过(脚本对真实
82
+ token 求对比度,两主题 0 违规)。
83
+ - 徽标不再用「深色文字压浅色底」的语义色当正文色(宿主语义色本身是给图标用的,小字压不住),
84
+ 改为语义色描边 + 正文色文字;分段控件的选中态同理。
85
+ - 模态框改用 `bg-layer-1`,`bg-overlay` 在暗色下是中间灰,压不住次级文字。
86
+ - `README.zh.md` 的用例数从 122 更新为 123。
87
+
88
+ ### Added
89
+
90
+ - 回归测试:`client.js` 的 CSS 不允许出现硬编码颜色,且只能引用 `--dsw-alias-*`。
91
+
92
+ ## [0.2.0] - 2026-10-29
93
+
94
+ ### Added
95
+
96
+ - 面板从「只看」变成「能管」:三个页签(档案 / 团队 / 体检)。档案页支持新建、编辑、复制、
97
+ 删除、导入、导出,编辑器有表单与源码两种模式,覆盖前留 `.bak`;团队页只读状态目录,列出每个
98
+ 团队的成员、模型路由、是否与档案一致,以及团队任务;体检页集中回答「配置有没有指错」。
99
+ - 写路由 `POST /plugins/dsh-agent-roster/rosters`:同源(`connection` 围栏)、`application/json`
100
+ 校验、请求体上限、档案库根目录白名单;`create` / `update` / `delete` / `import` / `export`
101
+ 五个动作,宿主侧校验失败原样返回诊断,客户端不直接写文件。
102
+ - 只读路由 `GET /plugins/dsh-agent-roster/document`:单份档案原文 + 解析结果,供编辑器源码模式。
103
+ - 只读路由 `GET /plugins/dsh-agent-roster/locale`:面板词典(`en` / `zh`)。
104
+ - 中英双语:面板与宿主文本都走词典(`locale/*.json`),设置页新增「语言」小节
105
+ (`auto` / `zh` / `en`),默认跟随 DSH;语言变化时面板实时重渲染。
106
+ - 设置页 `settings.section` 小节:语言选择 + 只读配置摘要(目录、状态目录、已声明策略数)。
107
+ - 面板顶部一条「需要处理的事」徽标,配置类提示集中在体检页,不再把噪声堆到首屏。
108
+ - `scripts/check-locale.mjs`:静态 `t()` 键必须两本词典都存在,避免界面漏词。
109
+ - 面板快照扩展:`teams`(团队与成员核对)、`hints`(配置体检)、`counts`、`deployments`。
110
+ - `lib/http.js`、`lib/write.js`、`lib/service.js`、`lib/teams-state.js`:路由工具、写入计划与
111
+ 执行、面板快照投影、状态目录只读解析。
112
+ - 单测 111 个用例(新增写入计划/执行、快照与提示、路由契约)。
113
+
114
+ ### Changed
115
+
116
+ - 面板改为整页布局(`main` 键控槽 + `sidebar.panellist`),设置页只保留语言与配置摘要。
117
+ - `package.json` `dsh.client.inject` 增加 `@deepseek-ai/dsh-client-ui-settings`。
118
+ - 版本 0.1.0 → 0.2.0。
119
+
120
+ ## [0.1.0]
121
+
122
+ ### Added
123
+
124
+ - 档案格式 `dsh-agent-roster/v1`:`<root>/<roster-id>/roster.{yaml,yml,json}`,一份档案一个目录。
125
+ - 校验器:结构、取值、上限、安全(保留字符 / 提示注入特征)四类检查;未知键直接报错。
126
+ - 档案库扫描:按目录探测档案文件,重复文件、散落文件、不可读目录都出诊断,不静默跳过。
127
+ - 桥接层:把成员翻译成 `agent_teams_add_member` 参数与 `agent_teams_create` 的 `plan.members`,
128
+ 每个字段标注 `direct` / `config` / `declaration` 归宿。
129
+ - 核对层:只读 `dsh-agent-teams` 的 `team.json`,逐字段比对并判为 `applied` / `overridden` /
130
+ `omitted` / `unconfirmed`;状态缺失时降级而不是假装通过。
131
+ - 四个工具:`roster_list`、`roster_show`、`roster_create`、`roster_verify`。
132
+ - 左侧栏面板:`sidebar.panellist` 一行图标 + 同 id 的 `main` 键控槽整页内容;点击后在主区域
133
+ 打开工作区 × 档案库快照(只读路由 `/plugins/dsh-agent-roster/library`),档案卡片与成员表;
134
+ 取不到工作区注册表时标注回退到进程工作目录。
135
+ - CLI `dsh-agent-roster`:`validate` / `list` / `version`,`--dir`、`--known-policy`、`--json`;
136
+ 退出码 0 = 通过,1 = 至少一个 error,2 = 用法错误。
137
+ - 两座示例档案库(`examples/core-review`、`examples/research`)。
138
+ - 单测 88 个用例,覆盖格式、解析、校验、扫描、路径解析、桥接、核对、无损 JSON、工具契约(状态行/无损输出)、客户端加载器契约(两个槽位 + 身份一致)、面板快照与包清单自洽。
139
+ - 宿主半边冒烟脚本 `scripts/host-smoke.mjs`:用真实 `defineTool` 注册并调用四个工具,断言输出可无损往返。
140
+
141
+ ### Changed
142
+
143
+ - 「没配 `knownToolPolicies`」改为档案库级只发一条 `tool-policy-cross-check-skipped`(info),
144
+ 不再每份档案重复一条 `tool-policy-unverified` 警告——一个库级事实不该放大成 n 行噪音。
145
+ - 四个工具的渲染文本首行改为 `k=v` 状态行(如 `[roster_list] rootDir=… total=2 ok=2`):
146
+ 模型读到的是渲染文本,结构化字段不会自动出现,状态行让"成功没有/命中没有"不必靠中文措辞去猜。
147
+
148
+ ### Fixed
149
+
150
+ - `volatile` 配置字段在 `apply()` 里是 `{ get, write }` 引用对象,原来被当成普通值读,导致面板与工具
151
+ 永远读默认值。改为经 `lib/config-view.js` 每次访问取当前值。
152
+ - `roster_list` / `roster_show` / `roster_create` 忽略 `path` 参数、只扫配置目录;现在显式 `path` 即覆盖。
153
+ - 工具返回值含 `undefined` 键,运行时按 `value is not lossless JSON` 整次拒绝;缺失值统一写 `null`,
154
+ 且所有返回值先过 `lib/json.js#lossless`。
155
+
156
+ [Unreleased]: https://github.com/STofone/dsh-agent-roster/compare/v0.2.0...main
157
+ [0.2.0]: https://github.com/STofone/dsh-agent-roster/releases/tag/v0.2.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 STofone
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,3 +1,290 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @stofone/dsh-agent-roster
2
+
3
+ Static member rosters for multi-agent teams in the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
4
+
5
+ A roster is a file on disk: who is on the team, what each member is for, which model route they use, and which tools they are allowed to touch. This plugin defines that file format, validates it, and translates it into `agent_teams_*` call arguments — nothing more.
6
+
7
+ ## What it is / what it is not
8
+
9
+ | | |
10
+ |---|---|
11
+ | **It is** a definition and validation layer | rosters are versioned, reviewable files; mistakes fail loudly with a file and field path |
12
+ | **It is** a bridge to `dsh-agent-teams` | `roster_create` emits ready-to-use `agent_teams_add_member` arguments and marks, field by field, what the runtime actually accepts |
13
+ | **It is** a verification aid | `roster_verify` reads `team.json` and compares persisted values against the roster |
14
+ | **It is not** a team runner | it never creates members, schedules tasks, or writes `.agent-teams/` state |
15
+ | **It is not** a scheduler or a runtime | dispatch, retries and review loops belong to `dsh-agent-teams` |
16
+
17
+ ## Install
18
+
19
+ Requires DSH with the `tools` service (any recent build) and `yaml` — both are declared as dependencies.
20
+
21
+ From the plugin manager, point it at this repository (absolute path) or at the package name after publication:
22
+
23
+ ```
24
+ @stofone/dsh-agent-roster
25
+ ```
26
+
27
+ The bundle ships `cordis.patch.yml`, so installing it inserts the plugin into the profile automatically. After install you should see five tools (`roster_list`, `roster_show`, `roster_create`, `roster_verify`, `roster_index`) and an **Agent Roster** entry in the left sidebar (a full main panel, not a Settings section).
28
+
29
+ ## Quick start
30
+
31
+ The library is **global**: one per machine, not per workspace, and it lives under the DSH data
32
+ directory (`$DSH_HOME/agent-roster`, which is `~/.dsh/agent-roster` by default — in Docker that is
33
+ the mounted volume). It is created on first save; you do not have to make it yourself.
34
+
35
+ ```
36
+ ~/.dsh/agent-roster/
37
+ └── core-review/
38
+ └── roster.yaml
39
+ ```
40
+
41
+ ```yaml
42
+ kind: dsh-agent-roster/v1
43
+ roster: core-review # must equal the directory name
44
+ version: 1.0.0 # exact SemVer
45
+ title: Core review team
46
+ description: Fixed members for code review and release gatekeeping.
47
+ license: MIT
48
+ maintainers: [STofone]
49
+ tags: [review, release]
50
+
51
+ defaults: # team-wide fallback for route fields
52
+ provider: deepseek
53
+ model: deepseek-v4.1-flash
54
+ reasoningEffort: high
55
+
56
+ members:
57
+ - name: reviewer # kebab-case, unique in the team
58
+ role: Code review
59
+ description: Reviews only, never releases # one-line responsibility, used for delegation
60
+ aliases: [code-reviewer] # former names: still matched after a rename
61
+ skills: [frontend-skill] # declared skills (never auto-loaded)
62
+ examples: # typical requests, used as delegation hints
63
+ - Review this change for semantic bugs
64
+ systemPrompt: |
65
+ You review for semantic bugs first, style second.
66
+ toolPolicy: readonly
67
+ - name: release-manager
68
+ role: Release gatekeeper
69
+ systemPrompt: |
70
+ You check the diff, the version and the rollback plan.
71
+ model: deepseek-v4.1-pro
72
+ reasoningEffort: low
73
+
74
+ captain: # optional: what the captain (the session that creates a team) should be
75
+ name: lead
76
+ description: The main session is the captain; it splits work and accepts results
77
+ provider: tsingmicro
78
+ model: deepseek-v4.1-flash
79
+ personaPrefix: |
80
+ Restate the goal and acceptance criteria before delegating.
81
+ ```
82
+
83
+ JSON works too (`roster.json`); YAML and JSON are probed in the order `roster.yaml`, `roster.yml`, `roster.json`. Exactly one file per directory — a second one is reported, never silently picked.
84
+
85
+ This package ships two example libraries in [`examples/`](./examples). `rosterDirs` is empty by
86
+ default, meaning the global library above, which is empty in a fresh install — so the panel shows
87
+ "0 rosters" until you create one in the UI. To look at the examples instead, point the CLI at them:
88
+
89
+ ```bash
90
+ # see what the example libraries look like
91
+ npx dsh-agent-roster list --dir examples
92
+ ```
93
+
94
+ Then ask the model to run `roster_list`, or call the CLI (it reads the same global library by
95
+ default):
96
+
97
+ ```bash
98
+ npx dsh-agent-roster validate
99
+ npx dsh-agent-roster list
100
+ ```
101
+
102
+ ## File reference
103
+
104
+ Top level: only these keys are allowed — unknown keys are errors, not warnings.
105
+
106
+ | Key | Required | Notes |
107
+ |---|---|---|
108
+ | `kind` | yes | must be `dsh-agent-roster/v1` |
109
+ | `roster` | yes | kebab-case id, must equal the containing directory name |
110
+ | `version` | yes | exact SemVer (`1.2.0`, not `^1.2.0`) |
111
+ | `title` | no | single line, at most 120 chars (defaults to the id) |
112
+ | `description` | no | at most 500 chars |
113
+ | `license` | no | SPDX-ish single line |
114
+ | `maintainers` | no | non-empty strings if present |
115
+ | `tags` | no | at most 16 tags, each lowercase kebab-case |
116
+ | `defaults` | no | route fields inherited by members |
117
+ | `members` | yes | 1 to 32 entries |
118
+ | `captain` | no | captain block: describes, never creates (see below) |
119
+
120
+ Member keys: `name`, `role`, `systemPrompt` (all required), plus `provider`, `model`, `reasoningEffort`, `toolPolicy`, `fallback`, `toolScope`, `maxTokens`, `title`, `tags`, `notes`, `description`, `aliases`, `skills`, `examples`.
121
+
122
+ `systemPrompt` is capped at 8000 characters. Members that omit a route field inherit it from `defaults`; the resolved value is recorded in the member's `inherited` list so you can see where it came from.
123
+
124
+ A machine-readable JSON Schema lives in [`schema/roster.v1.schema.json`](./schema/roster.v1.schema.json) for editor completion and external tools. `lib/validate.js` stays authoritative; `test/schema.test.js` keeps the two aligned.
125
+
126
+ ### Where each field actually lands
127
+
128
+ The bridge never pretends a field took effect. Every member field is classified:
129
+
130
+ | Class | Fields | Meaning |
131
+ |---|---|---|
132
+ | `direct` | `name`, `role`, `systemPrompt`, `provider`, `model`, `reasoningEffort`, `toolPolicy` | passed to `agent_teams_add_member` and stored by the runtime |
133
+ | `config` | `fallback` | no runtime argument; needs owner-side configuration |
134
+ | `declaration` | `toolScope`, `maxTokens`, `title`, `tags`, `notes`, `description`, `aliases`, `skills`, `examples` | documentation and validation only — explicitly reported as not applied |
135
+
136
+ `roster_create` emits `agent_teams_add_member` arguments (`systemPrompt` → `executionPrompt`, `reasoningEffort` → `reasoning_effort`, `toolPolicy` → `tool_policy`) and a per-field summary; `roster_verify` then reports each field as `applied`, `overridden`, `omitted` or `unconfirmed`.
137
+
138
+ ### Declared fields and the captain
139
+
140
+ These four declared fields carry information the runtime does not act on, and each has one job:
141
+
142
+ - `description` — one line on what the member owns. CrewAI's `backstory`, Claude's `description` and the OpenAI Agents SDK's `handoff_description` occupy this slot; it is what you read when handing out work.
143
+ - `aliases` — former names. After a rename the team state still holds the old name; verification matches by alias (reported as `matchedAs`) instead of raising "not deployed" plus "unmanaged" on every rename. Aliases may not collide with a member name or with each other.
144
+ - `skills` — skill names the member is expected to use, matching the DSH skill catalog. **Declaration only**: nothing is loaded because a roster says so; when the host can list skills, `roster_verify` answers just one question — declared but not installed.
145
+ - `examples` — typical requests, one per line, useful as delegation hints; never executed as instructions.
146
+
147
+ The `captain` block records the captain, which is **not a member**. In `dsh-agent-teams` the captain is the session that creates the team: `team.json` has no captain member row and no argument can change its model. So a roster only does three things for it:
148
+
149
+ 1. describe what it should be (`name`, `description`, `systemPrompt`);
150
+ 2. declare its route and persona prefix, and print a copy-pasteable profile snippet (`agent-default-model`, `@deepseek-ai/dsh-persona`) from `roster_show`;
151
+ 3. validate that the declarations pair up (`provider` and `model` come together).
152
+
153
+ Making it real means editing the profile's `cordis.patch.yml` and the agent preset. Editing the captain block in the panel records intent; the plugin neither can nor will change your session.
154
+
155
+ ## Tools
156
+
157
+ | Tool | Purpose |
158
+ |---|---|
159
+ | `roster_list` | list rosters in a library with per-roster validation results |
160
+ | `roster_show` | show one roster (or one member) in full, including bridge field classes |
161
+ | `roster_create` | translate members into `agent_teams_add_member` arguments / `plan.members` |
162
+ | `roster_verify` | compare a roster against `dsh-agent-teams`' `team.json`; degrades to "cannot verify" when state is absent |
163
+ | `roster_index` | list registry sources and their collections; given a collection, fetch it read-only for inspection |
164
+
165
+ All five are read-only with respect to team state. `roster_create` only produces arguments — the model still has to call `agent_teams_add_member` itself.
166
+
167
+ `roster_list`, `roster_show`, `roster_create` and `roster_verify` all accept a `path` argument: when given, it is the library root, with no silent fallback to the configured directory.
168
+
169
+ ## Sidebar panel
170
+
171
+ The client plugin adds an **Agent Roster** entry to the left sidebar: one row in the `sidebar.panellist` slot plus the matching key in the `main` slot. Clicking the row opens a full-width page with three tabs — the panel is not only a viewer:
172
+
173
+ | Tab | What you can do |
174
+ |---|---|
175
+ | Rosters | browse every workspace × library, expand members, and **create / edit / duplicate / delete / import / export** rosters. The editor has a form mode and a source mode (the file as-is); the host validates before writing and keeps a `.bak` next to an overwritten file |
176
+ | Teams | read one configured state directory (`.agent-teams` by default) and see every team the runtime created: profile, member profiles and model routes, whether each persisted member really matches its roster, plus the team's tasks |
177
+ | Sources | the subscribed registry directories; each collection has a **Pull to this machine** button, and can be expanded to inspect its rosters first |
178
+ | Health | the "configuration points somewhere wrong" questions in one place: which directories resolved, whether the state directory exists, how many declarations are unconfirmed, and what to change |
179
+
180
+ A badge at the top summarises how many items need attention; the Health tab explains each one. All write operations go through `POST /plugins/dsh-agent-roster/rosters`, which enforces same-origin (`connection`), a JSON content type, a body-size cap and a library-root whitelist — the client never writes files itself.
181
+
182
+ The panel is bilingual. Its language defaults to the DSH language and can be pinned in **Settings → Plugins → Agent Roster** (the same section shows a read-only summary of the configuration). Both the panel and the host-side texts are translated; dictionaries live in `locale/` and are served by the host, so switching the DSH language re-renders the panel without a reload.
183
+
184
+ ## Public registry and one-click pull
185
+
186
+ Rosters can be shared: keep one or more of them in a GitHub repository, register them as a **collection** in that
187
+ repository's `index/registry.json`, and anyone who subscribes can see them in the panel and pull them locally.
188
+
189
+ - **Subscribing**: set `indexes` to an `https://…/registry.json` URL or `npm:pkg` (reads `registry.json` from the
190
+ package). Empty means the official source, `STofone/dsh-agent-roster`'s `index/registry.json`; an explicit `false`
191
+ is the only way to disable remote sources entirely.
192
+ - **How often it refreshes**: TTL **one hour** — that is the unauthenticated GitHub budget of 60 requests/hour.
193
+ `?refresh=1` (or the tools' `refresh`) bypasses the cache, throttled to one try per 60 seconds so the quota is not
194
+ burned. On failure the last cached index is reused and labelled "cached (fetch failed)" in the panel.
195
+ - **Pulling**: writes are idempotent and report one of `created`, `updated` (explicit overwrite), `unchanged`
196
+ (same content, nothing written) or `conflict` (the local copy differs and is **not** overwritten by default).
197
+ Conflicts are yours to resolve: merge in the editor, or pull again with `overwrite` in the request body.
198
+ - **Publishing your own**: add an entry to `collections` in `index/registry.json` (`id`, `repo: github:owner/repo`,
199
+ `ref`, `path`, `rosters[]`) and open a PR. `.github/workflows/registry.yml` checks the index against the real files.
200
+
201
+ ## Routes
202
+
203
+ | Route | Method | Purpose |
204
+ |---|---|---|
205
+ | `/plugins/dsh-agent-roster/library` | GET | panel snapshot: libraries, rosters, teams, hints, counts, remote sources |
206
+ | `/plugins/dsh-agent-roster/document` | GET | one roster's raw text plus its parsed form (editor) |
207
+ | `/plugins/dsh-agent-roster/rosters` | POST | create / update / delete / import / export one roster |
208
+ | `/plugins/dsh-agent-roster/collection` | GET | fetch one collection's rosters read-only (nothing is written) |
209
+ | `/plugins/dsh-agent-roster/install` | POST | write one collection into the local library (idempotent, no overwrite by default) |
210
+ | `/plugins/dsh-agent-roster/locale` | GET | `en` / `zh` dictionary for the panel |
211
+
212
+ All six are `exact` routes behind the host's `connection` fence and answer `405` for other methods.
213
+
214
+ ## Configuration
215
+
216
+ | Key | Default | Meaning |
217
+ |---|---|---|
218
+ | `rosterDirs` | `[]` | library directories. Empty means the global library `$DSH_HOME/agent-roster`; an absolute or `~` path is global too; only a relative path is resolved per workspace. The first entry is what the tools read by default |
219
+ | `knownToolPolicies` | `[]` | tool-policy ids declared by your deployment; enables cross-checks |
220
+ | `agentTeamsStateDir` | `.agent-teams` | read only: where `roster_verify` and the Teams tab look for team state |
221
+ | `language` | `"auto"` | panel language: `auto` follows DSH, or pin `zh` / `en` |
222
+ | `indexes` | `[]` | public registry sources: an `https` `registry.json` or `npm:pkg`. Empty means the official source; `false` means local-only |
223
+ | `region` | `"auto"` | download region: `china` reads npm-style index packages from the Tencent mirror; `auto` equals `global` (no probing, one less request) |
224
+
225
+ All six are `volatile`: changes apply immediately, no host restart.
226
+
227
+ ## Diagnostics
228
+
229
+ Validation reports errors, warnings and infos with `file`, `path` and `reason`. Families:
230
+
231
+ - structure: `missing-field`, `unknown-key`, `invalid-type`, `invalid-kind`, `pair-required`, `invalid-value`
232
+ - values: `invalid-id`, `invalid-provider`, `invalid-model`, `invalid-semver`, `invalid-tag`, `invalid-tool-name`, `invalid-tool-policy-id`, `invalid-number`, `empty-tool-scope`
233
+ - safety: `reserved-character` (`^`, `$`, `|`, braces), `forbidden-content` (prompt-injection patterns), `single-line-required`
234
+ - limits: `string-too-long`, `too-many-members`, `too-many-values`
235
+ - library level: `library-missing`, `library-unusable`, `missing-roster-file`, `multiple-roster-files`, `unexpected-file`, `unreadable-file`, `unreadable-dir`, `duplicate-roster-id`, `tool-policy-cross-check-skipped`
236
+ - remote sources: `index-unreachable`, `index-invalid`, `index-refresh-throttled`, `index-source-invalid`, `collection-fetch-failed`, `collection-not-found`
237
+
238
+ An empty library is not an error. A missing one is reported as info, not error. `tool-policy-cross-check-skipped` is emitted **once per library** (not once per roster) when no `knownToolPolicies` are configured — a per-roster warning would turn one fact into n lines of noise.
239
+
240
+ ## Relationship to `dsh-agent-teams`
241
+
242
+ `dsh-agent-teams` owns team lifecycles, task dispatch and member persistence. This plugin owns the *definition* of members and the honesty of the handoff: it reads `.agent-teams/<team>/team.json` (read-only) to verify what the runtime actually stored, and it degrades to "unconfirmed" when it cannot read it. No state is ever written.
243
+
244
+ ## Development
245
+
246
+ ```bash
247
+ npm install
248
+ npm test # node --test, 163 cases
249
+ npm run smoke # registers the tools against a real DSH bundle, asserts lossless output
250
+ node scripts/check-locale.mjs # static t() keys must exist in both dictionaries
251
+ ```
252
+
253
+ Layout:
254
+
255
+ ```
256
+ index.js host plugin: Config, apply, six routes
257
+ lib/ format, parse, validate, registry, bridge, verify, render, library,
258
+ config-view, json, http, write, service, teams-state, tools
259
+ client.js client plugin: sidebar entry, main panel (rosters/teams/health), settings section
260
+ locale/ panel + listing dictionaries (en/zh)
261
+ schema/ roster.v1.schema.json (editor completion; kept aligned by tests)
262
+ bin/cli.mjs validate/list/version for CI
263
+ scripts/ host-smoke.mjs, client-smoke.mjs, check-locale.mjs (dev only, not shipped)
264
+ examples/ two example libraries
265
+ test/ unit tests
266
+ ```
267
+
268
+ ## Verification status
269
+
270
+ Verified on this machine:
271
+
272
+ - `npm test` — 163/163 passing (format, parse, validate, registry, paths, bridge, verify, lossless JSON, write plan/apply, all routes, panel snapshot and hints, dictionary consistency, tool contracts, client loader contract, library snapshot, config view, package manifest, declared fields and captain block, schema/constant alignment, remote registry and one-click install).
273
+ - `npm run smoke` — `apply()` registers all five tools through a real `defineTool` from a DSH bundle; `roster_list` / `roster_show` / `roster_create` / `roster_verify` execute, every output survives `lossless` checks, `path` overrides are honored, effect disposal runs cleanly. The same command then loads `client.js` against a stubbed module table and asserts the three slot registrations, style injection and effect disposal.
274
+ - CLI: `validate --dir examples` returns exit 0 over both example libraries.
275
+ - Installed into a real profile with the plugin manager (`plugin_manager install_bundle` against this directory → `link:` dependency + bundle entry); the profile re-reads the linked files on restart.
276
+ - The read-only route answered from the running host: `GET /plugins/dsh-agent-roster/library` → 200 with the plugin version and resolved rosters.
277
+ - A second agent session saw all five tools in its tool list and called them.
278
+ - The sidebar panel was confirmed visible with real roster data.
279
+
280
+ Not verified in this pass: the 0.2.0 management UI (tabs, editors, Teams view, language switch, `document` / `rosters` routes) inside a live page — those need a host restart to load the new modules, and are verified after it.
281
+
282
+ Three real defects were found by this verification and fixed:
283
+
284
+ 1. `volatile()` config fields reach `apply()` as `{ get, write }` reference objects, so the panel and every tool were reading the defaults no matter what the settings form said. Reads now go through `lib/config-view.js`, which resolves references per access and falls back on type mismatch.
285
+ 2. `roster_list` ignored its `path` argument and always scanned the configured directory. `resolvePaths` now treats an explicit `path` as an override.
286
+ 3. Tool outputs carried `undefined`-valued keys, which the runtime rejects as `value is not lossless JSON`. Absent values are now `null` (or omitted), and every return value goes through `lib/json.js#lossless` first.
287
+
288
+ ## License
289
+
290
+ MIT — see [LICENSE](./LICENSE).