@stofone/dsh-agent-roster 0.6.1 → 0.7.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/CHANGELOG.md +76 -1
- package/LICENSE +21 -21
- package/README.md +370 -319
- package/README.zh.md +57 -8
- package/index.js +2 -2
- package/lib/bridge.js +89 -10
- package/lib/capabilities.js +434 -0
- package/lib/diff.js +216 -0
- package/lib/digest.js +65 -0
- package/lib/library.js +5 -0
- package/lib/registry.js +127 -4
- package/lib/render.js +238 -7
- package/lib/tools.js +248 -23
- package/lib/verify.js +135 -10
- package/package.json +5 -2
- package/schema/capabilities.v1.schema.json +124 -0
package/README.md
CHANGED
|
@@ -1,320 +1,371 @@
|
|
|
1
|
-
# @stofone/dsh-agent-roster
|
|
2
|
-
|
|
3
|
-
**English** | [简体中文](README.zh.md)
|
|
4
|
-
|
|
5
|
-
Static member rosters for multi-agent teams in the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
|
|
6
|
-
|
|
7
|
-
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.
|
|
8
|
-
|
|
9
|
-
## What it is / what it is not
|
|
10
|
-
|
|
11
|
-
| | |
|
|
12
|
-
|---|---|
|
|
13
|
-
| **It is** a definition and validation layer | rosters are versioned, reviewable files; mistakes fail loudly with a file and field path |
|
|
14
|
-
| **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 |
|
|
15
|
-
| **It is** a verification aid | `roster_verify` reads `team.json` and compares persisted values against the roster |
|
|
16
|
-
| **It is not** a team runner | it never creates members, schedules tasks, or writes `.agent-teams/` state |
|
|
17
|
-
| **It is not** a scheduler or a runtime | dispatch, retries and review loops belong to `dsh-agent-teams` |
|
|
18
|
-
|
|
19
|
-
## Install
|
|
20
|
-
|
|
21
|
-
Requires DSH with the `tools` service (any recent build) and `yaml` — both are declared as dependencies.
|
|
22
|
-
|
|
23
|
-
From the plugin manager, point it at this repository (absolute path) or at the package name after publication:
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
@stofone/dsh-agent-roster
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
The bundle ships `cordis.patch.yml`, so installing it inserts the plugin into the profile automatically. After install you should see
|
|
30
|
-
|
|
31
|
-
## Quick start
|
|
32
|
-
|
|
33
|
-
The library is **global**: one per machine, not per workspace, and it lives under the DSH data
|
|
34
|
-
directory (`$DSH_HOME/agent-roster`, which is `~/.dsh/agent-roster` by default — in Docker that is
|
|
35
|
-
the mounted volume). It is created on first save; you do not have to make it yourself.
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
~/.dsh/agent-roster/
|
|
39
|
-
└── core-review/
|
|
40
|
-
└── roster.yaml
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
```yaml
|
|
44
|
-
kind: dsh-agent-roster/v1
|
|
45
|
-
roster: core-review # must equal the directory name
|
|
46
|
-
version: 1.0.0 # exact SemVer
|
|
47
|
-
title: Core review team
|
|
48
|
-
description: Fixed members for code review and release gatekeeping.
|
|
49
|
-
license: MIT
|
|
50
|
-
maintainers: [STofone]
|
|
51
|
-
tags: [review, release]
|
|
52
|
-
|
|
53
|
-
defaults: # team-wide fallback for route fields
|
|
54
|
-
provider: deepseek
|
|
55
|
-
model: deepseek-v4.1-flash
|
|
56
|
-
reasoningEffort: high
|
|
57
|
-
|
|
58
|
-
members:
|
|
59
|
-
- name: reviewer # kebab-case, unique in the team
|
|
60
|
-
role: Code review
|
|
61
|
-
description: Reviews only, never releases # one-line responsibility, used for delegation
|
|
62
|
-
aliases: [code-reviewer] # former names: still matched after a rename
|
|
63
|
-
skills: [frontend-skill] # declared skills (never auto-loaded)
|
|
64
|
-
examples: # typical requests, used as delegation hints
|
|
65
|
-
- Review this change for semantic bugs
|
|
66
|
-
systemPrompt: |
|
|
67
|
-
You review for semantic bugs first, style second.
|
|
68
|
-
toolPolicy: readonly
|
|
69
|
-
- name: release-manager
|
|
70
|
-
role: Release gatekeeper
|
|
71
|
-
systemPrompt: |
|
|
72
|
-
You check the diff, the version and the rollback plan.
|
|
73
|
-
model: deepseek-v4.1-pro
|
|
74
|
-
reasoningEffort: low
|
|
75
|
-
|
|
76
|
-
captain: # optional: what the captain (the session that creates a team) should be
|
|
77
|
-
name: lead
|
|
78
|
-
description: The main session is the captain; it splits work and accepts results
|
|
79
|
-
provider: tsingmicro
|
|
80
|
-
model: deepseek-v4.1-flash
|
|
81
|
-
personaPrefix: |
|
|
82
|
-
Restate the goal and acceptance criteria before delegating.
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
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.
|
|
86
|
-
|
|
87
|
-
This package ships two example libraries in [`examples/`](./examples). `rosterDirs` is empty by
|
|
88
|
-
default, meaning the global library above, which is empty in a fresh install — so the panel shows
|
|
89
|
-
"0 rosters" until you create one in the UI. To look at the examples instead, point the CLI at them:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
# see what the example libraries look like
|
|
93
|
-
npx dsh-agent-roster list --dir examples
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Then ask the model to run `roster_list`, or call the CLI (it reads the same global library by
|
|
97
|
-
default):
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
npx dsh-agent-roster validate
|
|
101
|
-
npx dsh-agent-roster list
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
## File reference
|
|
105
|
-
|
|
106
|
-
Top level: only these keys are allowed — unknown keys are errors, not warnings.
|
|
107
|
-
|
|
108
|
-
| Key | Required | Notes |
|
|
109
|
-
|---|---|---|
|
|
110
|
-
| `kind` | yes | must be `dsh-agent-roster/v1` |
|
|
111
|
-
| `roster` | yes | kebab-case id, must equal the containing directory name |
|
|
112
|
-
| `version` | yes | exact SemVer (`1.2.0`, not `^1.2.0`) |
|
|
113
|
-
| `title` | no | single line, at most 120 chars (defaults to the id) |
|
|
114
|
-
| `description` | no | at most 500 chars |
|
|
115
|
-
| `license` | no | SPDX-ish single line |
|
|
116
|
-
| `maintainers` | no | non-empty strings if present |
|
|
117
|
-
| `tags` | no | at most 16 tags, each lowercase kebab-case |
|
|
118
|
-
| `defaults` | no | route fields inherited by members |
|
|
119
|
-
| `members` | yes | 1 to 32 entries |
|
|
120
|
-
| `captain` | no | captain block: describes, never creates (see below) |
|
|
121
|
-
|
|
122
|
-
Member keys: `name`, `role`, `systemPrompt` (all required), plus `provider`, `model`, `reasoningEffort`, `toolPolicy`, `fallback`, `toolScope`, `maxTokens`, `title`, `tags`, `notes`, `description`, `aliases`, `skills`, `examples`.
|
|
123
|
-
|
|
124
|
-
`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.
|
|
125
|
-
|
|
126
|
-
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.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
|
135
|
-
|
|
136
|
-
| `
|
|
137
|
-
|
|
138
|
-
`
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
- `
|
|
147
|
-
- `
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
The panel
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
1
|
+
# @stofone/dsh-agent-roster
|
|
2
|
+
|
|
3
|
+
**English** | [简体中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
Static member rosters for multi-agent teams in the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
## What it is / what it is not
|
|
10
|
+
|
|
11
|
+
| | |
|
|
12
|
+
|---|---|
|
|
13
|
+
| **It is** a definition and validation layer | rosters are versioned, reviewable files; mistakes fail loudly with a file and field path |
|
|
14
|
+
| **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 |
|
|
15
|
+
| **It is** a verification aid | `roster_verify` reads `team.json` and compares persisted values against the roster |
|
|
16
|
+
| **It is not** a team runner | it never creates members, schedules tasks, or writes `.agent-teams/` state |
|
|
17
|
+
| **It is not** a scheduler or a runtime | dispatch, retries and review loops belong to `dsh-agent-teams` |
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
Requires DSH with the `tools` service (any recent build) and `yaml` — both are declared as dependencies.
|
|
22
|
+
|
|
23
|
+
From the plugin manager, point it at this repository (absolute path) or at the package name after publication:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
@stofone/dsh-agent-roster
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The bundle ships `cordis.patch.yml`, so installing it inserts the plugin into the profile automatically. After install you should see six tools (`roster_list`, `roster_show`, `roster_create`, `roster_verify`, `roster_diff`, `roster_index`) and an **Agent Roster** entry in the left sidebar (a full main panel, not a Settings section).
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
The library is **global**: one per machine, not per workspace, and it lives under the DSH data
|
|
34
|
+
directory (`$DSH_HOME/agent-roster`, which is `~/.dsh/agent-roster` by default — in Docker that is
|
|
35
|
+
the mounted volume). It is created on first save; you do not have to make it yourself.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
~/.dsh/agent-roster/
|
|
39
|
+
└── core-review/
|
|
40
|
+
└── roster.yaml
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```yaml
|
|
44
|
+
kind: dsh-agent-roster/v1
|
|
45
|
+
roster: core-review # must equal the directory name
|
|
46
|
+
version: 1.0.0 # exact SemVer
|
|
47
|
+
title: Core review team
|
|
48
|
+
description: Fixed members for code review and release gatekeeping.
|
|
49
|
+
license: MIT
|
|
50
|
+
maintainers: [STofone]
|
|
51
|
+
tags: [review, release]
|
|
52
|
+
|
|
53
|
+
defaults: # team-wide fallback for route fields
|
|
54
|
+
provider: deepseek
|
|
55
|
+
model: deepseek-v4.1-flash
|
|
56
|
+
reasoningEffort: high
|
|
57
|
+
|
|
58
|
+
members:
|
|
59
|
+
- name: reviewer # kebab-case, unique in the team
|
|
60
|
+
role: Code review
|
|
61
|
+
description: Reviews only, never releases # one-line responsibility, used for delegation
|
|
62
|
+
aliases: [code-reviewer] # former names: still matched after a rename
|
|
63
|
+
skills: [frontend-skill] # declared skills (never auto-loaded)
|
|
64
|
+
examples: # typical requests, used as delegation hints
|
|
65
|
+
- Review this change for semantic bugs
|
|
66
|
+
systemPrompt: |
|
|
67
|
+
You review for semantic bugs first, style second.
|
|
68
|
+
toolPolicy: readonly
|
|
69
|
+
- name: release-manager
|
|
70
|
+
role: Release gatekeeper
|
|
71
|
+
systemPrompt: |
|
|
72
|
+
You check the diff, the version and the rollback plan.
|
|
73
|
+
model: deepseek-v4.1-pro
|
|
74
|
+
reasoningEffort: low
|
|
75
|
+
|
|
76
|
+
captain: # optional: what the captain (the session that creates a team) should be
|
|
77
|
+
name: lead
|
|
78
|
+
description: The main session is the captain; it splits work and accepts results
|
|
79
|
+
provider: tsingmicro
|
|
80
|
+
model: deepseek-v4.1-flash
|
|
81
|
+
personaPrefix: |
|
|
82
|
+
Restate the goal and acceptance criteria before delegating.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
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.
|
|
86
|
+
|
|
87
|
+
This package ships two example libraries in [`examples/`](./examples). `rosterDirs` is empty by
|
|
88
|
+
default, meaning the global library above, which is empty in a fresh install — so the panel shows
|
|
89
|
+
"0 rosters" until you create one in the UI. To look at the examples instead, point the CLI at them:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# see what the example libraries look like
|
|
93
|
+
npx dsh-agent-roster list --dir examples
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then ask the model to run `roster_list`, or call the CLI (it reads the same global library by
|
|
97
|
+
default):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx dsh-agent-roster validate
|
|
101
|
+
npx dsh-agent-roster list
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## File reference
|
|
105
|
+
|
|
106
|
+
Top level: only these keys are allowed — unknown keys are errors, not warnings.
|
|
107
|
+
|
|
108
|
+
| Key | Required | Notes |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `kind` | yes | must be `dsh-agent-roster/v1` |
|
|
111
|
+
| `roster` | yes | kebab-case id, must equal the containing directory name |
|
|
112
|
+
| `version` | yes | exact SemVer (`1.2.0`, not `^1.2.0`) |
|
|
113
|
+
| `title` | no | single line, at most 120 chars (defaults to the id) |
|
|
114
|
+
| `description` | no | at most 500 chars |
|
|
115
|
+
| `license` | no | SPDX-ish single line |
|
|
116
|
+
| `maintainers` | no | non-empty strings if present |
|
|
117
|
+
| `tags` | no | at most 16 tags, each lowercase kebab-case |
|
|
118
|
+
| `defaults` | no | route fields inherited by members |
|
|
119
|
+
| `members` | yes | 1 to 32 entries |
|
|
120
|
+
| `captain` | no | captain block: describes, never creates (see below) |
|
|
121
|
+
|
|
122
|
+
Member keys: `name`, `role`, `systemPrompt` (all required), plus `provider`, `model`, `reasoningEffort`, `toolPolicy`, `fallback`, `toolScope`, `maxTokens`, `title`, `tags`, `notes`, `description`, `aliases`, `skills`, `examples`.
|
|
123
|
+
|
|
124
|
+
`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.
|
|
125
|
+
|
|
126
|
+
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.
|
|
127
|
+
|
|
128
|
+
An **optional sibling file** `capabilities.yaml` in the same directory declares what the deployment must provide before this roster can be built — see [Capability requirements](#capability-requirements-capabilitiesyaml).
|
|
129
|
+
|
|
130
|
+
### Where each field actually lands
|
|
131
|
+
|
|
132
|
+
The bridge never pretends a field took effect. Every member field is classified:
|
|
133
|
+
|
|
134
|
+
| Class | Fields | Meaning |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `direct` | `name`, `role`, `systemPrompt`, `provider`, `model`, `reasoningEffort`, `toolPolicy` | passed to `agent_teams_add_member` and stored by the runtime |
|
|
137
|
+
| `config` | `fallback` | no runtime argument; needs owner-side configuration |
|
|
138
|
+
| `declaration` | `toolScope`, `maxTokens`, `title`, `tags`, `notes`, `description`, `aliases`, `skills`, `examples` | documentation and validation only — explicitly reported as not applied |
|
|
139
|
+
|
|
140
|
+
`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`.
|
|
141
|
+
|
|
142
|
+
### Declared fields and the captain
|
|
143
|
+
|
|
144
|
+
These four declared fields carry information the runtime does not act on, and each has one job:
|
|
145
|
+
|
|
146
|
+
- `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.
|
|
147
|
+
- `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.
|
|
148
|
+
- `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.
|
|
149
|
+
- `examples` — typical requests, one per line, useful as delegation hints; never executed as instructions.
|
|
150
|
+
|
|
151
|
+
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:
|
|
152
|
+
|
|
153
|
+
1. describe what it should be (`name`, `description`, `systemPrompt`);
|
|
154
|
+
2. declare its route and persona prefix, and print a copy-pasteable profile snippet (`agent-default-model`, `@deepseek-ai/dsh-persona`) from `roster_show`;
|
|
155
|
+
3. validate that the declarations pair up (`provider` and `model` come together).
|
|
156
|
+
|
|
157
|
+
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.
|
|
158
|
+
|
|
159
|
+
## Capability requirements (`capabilities.yaml`)
|
|
160
|
+
|
|
161
|
+
A roster says *who the members are*. It cannot say *whether this machine can actually run them* — the skill catalogue and the tool-policy definitions live in the deployment, not in the archive. An optional sibling file in the roster directory declares exactly that, and nothing else:
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
kind: dsh-agent-roster-capabilities/v1
|
|
165
|
+
roster: money-lab # must equal the directory name
|
|
166
|
+
onMissing: refuse # refuse (default) | degraded
|
|
167
|
+
asOf: 2026-11-02 # date only
|
|
168
|
+
requires:
|
|
169
|
+
skills: [frontend-skill, {name: dsh-chat-import, reason: 会话导出需要}]
|
|
170
|
+
toolPolicies: [member-safe]
|
|
171
|
+
notes: 建队前先确认成员会话可用。
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Every requirement lands in one of **three** states — never two:
|
|
175
|
+
|
|
176
|
+
| State | Meaning | Effect |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `present` | the evidence source has it | nothing to do |
|
|
179
|
+
| `missing` | the evidence source says it is not there | the **only** state that trips the gate |
|
|
180
|
+
| `unknown` | there is no evidence source at all | neither pass nor failure — written down so silence is never read as approval |
|
|
181
|
+
|
|
182
|
+
There are exactly two evidence sources, because they are the only two the host can answer: `skills` from the skill catalogue, `toolPolicies` from this plugin's `knownToolPolicies` configuration. Everything else — model-routing availability, plugin versions, credential pointers, business preconditions — is rejected (`unknown-field`): a requirement nobody can check is just another way to look green.
|
|
183
|
+
|
|
184
|
+
Behaviour: under `onMissing: refuse` (default) `roster_create` emits **no** `agent_teams_add_member` argument while anything is `missing`, and prints where to install each one. `roster_verify` reports `requires=<missing>missing/<declared>` plus `capabilityBlocked` in its status line, and `ok` is false when a declared requirement blocks a build. `onMissing: degraded` still hands you the arguments, with the debt recorded on every verification. `roster_show` prints the declaration block itself.
|
|
185
|
+
|
|
186
|
+
**The panel shows it too.** Each roster card lists these requirements as diagnostics: a missing `toolPolicies` requirement is an `error` under `refuse` (so the sidebar badge goes red) and a `warn` under `degraded`. Skill requirements are deliberately reported as `capability-skills-unverified` / `info` — the panel snapshot is synchronous and cannot enumerate the skill catalogue, so it says *not checked here* instead of guessing; run `roster_verify` for the real answer. When `knownToolPolicies` is unset, a single `capability-cross-check-skipped` entry names the affected rosters rather than repeating once per roster.
|
|
187
|
+
|
|
188
|
+
The sibling file has its own JSON Schema: [`schema/capabilities.v1.schema.json`](./schema/capabilities.v1.schema.json), kept aligned with `lib/capabilities.js` by `test/capabilities.test.js`.
|
|
189
|
+
|
|
190
|
+
## Tools
|
|
191
|
+
|
|
192
|
+
| Tool | Purpose |
|
|
193
|
+
|---|---|
|
|
194
|
+
| `roster_list` | list rosters in a library with per-roster validation results |
|
|
195
|
+
| `roster_show` | show one roster (or one member) in full, including bridge field classes |
|
|
196
|
+
| `roster_create` | translate members into `agent_teams_add_member` arguments / `plan.members`; refuses when a declared capability is missing |
|
|
197
|
+
| `roster_verify` | compare a roster against `dsh-agent-teams`' `team.json`; reports faithful / unverified / drift / missing, plus materialised tool-filter digests, policy drift, provenance and capability requirements |
|
|
198
|
+
| `roster_diff` | diff two rosters (generation N vs N+1): members matched by name, effective values compared, persona compared by length + hash |
|
|
199
|
+
| `roster_index` | list registry sources and their collections; given a collection, fetch it read-only for inspection |
|
|
200
|
+
|
|
201
|
+
All six are read-only with respect to team state. `roster_create` only produces arguments — the model still has to call `agent_teams_add_member` itself.
|
|
202
|
+
|
|
203
|
+
`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.
|
|
204
|
+
|
|
205
|
+
### One roster per directory, two generations per machine
|
|
206
|
+
|
|
207
|
+
`roster` must equal the directory name, and a library holds one document per directory. Two
|
|
208
|
+
generations of the same roster therefore live in **two libraries** — that is intended, not a
|
|
209
|
+
gap: it keeps "position is identity" intact (a later `load` can never pick the wrong generation
|
|
210
|
+
by accident) and makes the switch an explicit `path` argument instead of a silent side effect.
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
~/.dsh/agent-roster/money-lab/roster.yaml # one generation
|
|
214
|
+
/workspace/money/.dsh/rosters/money-lab/roster.yaml # the next generation
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`roster_diff({ from, to })` answers "what changed between the generations"; `roster_create`
|
|
218
|
+
prints a provenance line (`roster:money-lab@9.0.0#ab12cd34`) to put into the created team's
|
|
219
|
+
description, and `roster_verify` compares that line against the current file — so a roster
|
|
220
|
+
edited after the team was created shows up as `differ` instead of silently passing.
|
|
221
|
+
|
|
222
|
+
### Captain verification
|
|
223
|
+
|
|
224
|
+
An archive's `captain` block is a declaration. The captain is the session that creates the team — it
|
|
225
|
+
has no member row, its model comes from the deployment (`agent-default-model`) and its persona from
|
|
226
|
+
the agent preset. The panel therefore **compares** instead of pretending to write: the Settings row
|
|
227
|
+
shows the deployed provider / model per profile, and every archive with a captain block reports
|
|
228
|
+
`match` / `differ` / `undeclared` / `unknown` per field. When the deployment cannot be read the
|
|
229
|
+
state is `unknown`, never "passed".
|
|
230
|
+
|
|
231
|
+
The **apply** action is the one write the panel offers here: it switches the chosen session to the
|
|
232
|
+
declared model and queues a prompt that calls `agent_teams_create`, `agent_teams_add_member` per
|
|
233
|
+
member and finally `roster_verify`. Creating the team stays with the runtime.
|
|
234
|
+
|
|
235
|
+
## Sidebar panel
|
|
236
|
+
|
|
237
|
+
The panel takes the full page width; nothing constrains it to a reading column. The archive list has
|
|
238
|
+
a **tag pool** on the left: every tag present in the library, with how many archives carry it. Clicking
|
|
239
|
+
tags narrows the list — multiple selections are ANDed — and one button clears the selection. The tag
|
|
240
|
+
pool is a filtered view only; it never writes tags back to files.
|
|
241
|
+
|
|
242
|
+
Header actions follow the place they act on: **Import** and **New archive** sit in the archive tab
|
|
243
|
+
next to the search box, **Refresh sources** sits in the sources tab (it bypasses the one-hour cache and
|
|
244
|
+
re-reads the public index), and the header keeps a single **gear button** that opens the settings dialog.
|
|
245
|
+
The dialog edits the language, the download region, the mirror address and the roster directories, and shows the resolved
|
|
246
|
+
data directory, the team state directory and the version.
|
|
247
|
+
|
|
248
|
+
A badge at the top summarises how many items need attention
|
|
249
|
+
|
|
250
|
+
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:
|
|
251
|
+
|
|
252
|
+
| Tab | What you can do |
|
|
253
|
+
|---|---|
|
|
254
|
+
| 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 |
|
|
255
|
+
| 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 |
|
|
256
|
+
| Sources | the subscribed registry directories; each collection has a **Pull to this machine** button, and can be expanded to inspect its rosters first |
|
|
257
|
+
| 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 |
|
|
258
|
+
|
|
259
|
+
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.
|
|
260
|
+
|
|
261
|
+
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.
|
|
262
|
+
|
|
263
|
+
## Public registry and one-click pull
|
|
264
|
+
|
|
265
|
+
Rosters can be shared: keep one or more of them in a GitHub repository, register them as a **collection** in that
|
|
266
|
+
repository's `index/registry.json`, and anyone who subscribes can see them in the panel and pull them locally.
|
|
267
|
+
|
|
268
|
+
- **Subscribing**: set `indexes` to an `https://…/registry.json` URL or `npm:pkg` (reads `registry.json` from the
|
|
269
|
+
package). Empty means the official source, `STofone/dsh-agent-roster`'s `index/registry.json`; an explicit `false`
|
|
270
|
+
is the only way to disable remote sources entirely.
|
|
271
|
+
- **How often it refreshes**: TTL **one hour** — that is the unauthenticated GitHub budget of 60 requests/hour.
|
|
272
|
+
`?refresh=1` (or the tools' `refresh`) bypasses the cache, throttled to one try per 60 seconds so the quota is not
|
|
273
|
+
burned. On failure the last cached index is reused and labelled "cached (fetch failed)" in the panel.
|
|
274
|
+
- **Pulling**: writes are idempotent and report one of `created`, `updated` (explicit overwrite), `unchanged`
|
|
275
|
+
(same content, nothing written) or `conflict` (the local copy differs and is **not** overwritten by default).
|
|
276
|
+
Conflicts are yours to resolve: merge in the editor, or pull again with `overwrite` in the request body.
|
|
277
|
+
- **Publishing your own**: add an entry to `collections` in `index/registry.json` (`id`, `repo: github:owner/repo`,
|
|
278
|
+
`ref`, `path`, `rosters[]`) and open a PR. `.github/workflows/registry.yml` checks the index against the real files.
|
|
279
|
+
|
|
280
|
+
## Routes
|
|
281
|
+
|
|
282
|
+
| Route | Method | Purpose |
|
|
283
|
+
|---|---|---|
|
|
284
|
+
| `/plugins/dsh-agent-roster/library` | GET | panel snapshot: libraries, rosters, teams, hints, counts, remote sources |
|
|
285
|
+
| `/plugins/dsh-agent-roster/document` | GET | one roster's raw text plus its parsed form (editor) |
|
|
286
|
+
| `/plugins/dsh-agent-roster/rosters` | POST | create / update / delete / import / export one roster |
|
|
287
|
+
| `/plugins/dsh-agent-roster/apply` | POST | apply an archive's captain to an existing or new session and queue the team-building prompt |
|
|
288
|
+
| `/plugins/dsh-agent-roster/collection` | GET | fetch one collection's rosters read-only (nothing is written) |
|
|
289
|
+
| `/plugins/dsh-agent-roster/install` | POST | write one collection into the local library (idempotent, no overwrite by default) |
|
|
290
|
+
| `/plugins/dsh-agent-roster/locale` | GET | `en` / `zh` dictionary for the panel |
|
|
291
|
+
|
|
292
|
+
All six are `exact` routes behind the host's `connection` fence and answer `405` for other methods.
|
|
293
|
+
|
|
294
|
+
## Configuration
|
|
295
|
+
|
|
296
|
+
| Key | Default | Meaning |
|
|
297
|
+
|---|---|---|
|
|
298
|
+
| `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 |
|
|
299
|
+
| `knownToolPolicies` | `[]` | tool-policy ids declared by your deployment; enables cross-checks |
|
|
300
|
+
| `agentTeamsStateDir` | `.agent-teams` | read only: where `roster_verify` and the Teams tab look for team state |
|
|
301
|
+
| `language` | `"auto"` | panel language: `auto` follows DSH, or pin `zh` / `en` |
|
|
302
|
+
| `indexes` | `[]` | public registry sources: an `https` `registry.json` or `npm:pkg`. Empty means the official source; `false` means local-only |
|
|
252
303
|
| `region` | `"auto"` | download region: `china` reads npm-style index packages from the Tencent mirror; `auto` equals `global` (no probing, one less request) |
|
|
253
|
-
| `registryMirror` | `""` | custom npm registry for download sources, e.g. `https://registry.npmmirror.com` or `http://nexus.corp.local/repository/npm`. Set it and it beats `region`; a registry pinned inside a source still beats both. Empty means no custom mirror |
|
|
254
|
-
|
|
255
|
-
All six are `volatile`: changes apply immediately, no host restart.
|
|
256
|
-
|
|
257
|
-
## Diagnostics
|
|
258
|
-
|
|
259
|
-
Validation reports errors, warnings and infos with `file`, `path` and `reason`. Families:
|
|
260
|
-
|
|
261
|
-
- structure: `missing-field`, `unknown-key`, `invalid-type`, `invalid-kind`, `pair-required`, `invalid-value`
|
|
262
|
-
- values: `invalid-id`, `invalid-provider`, `invalid-model`, `invalid-semver`, `invalid-tag`, `invalid-tool-name`, `invalid-tool-policy-id`, `invalid-number`, `empty-tool-scope`
|
|
263
|
-
- safety: `reserved-character` (`^`, `$`, `|`, braces), `forbidden-content` (prompt-injection patterns), `single-line-required`
|
|
264
|
-
- limits: `string-too-long`, `too-many-members`, `too-many-values`
|
|
265
|
-
- 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`
|
|
266
|
-
- remote sources: `index-unreachable`, `index-invalid`, `index-refresh-throttled`, `index-source-invalid`, `collection-fetch-failed`, `collection-not-found`
|
|
267
|
-
|
|
268
|
-
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.
|
|
269
|
-
|
|
270
|
-
## Relationship to `dsh-agent-teams`
|
|
271
|
-
|
|
272
|
-
`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.
|
|
273
|
-
|
|
274
|
-
## Development
|
|
275
|
-
|
|
276
|
-
```bash
|
|
277
|
-
npm install
|
|
278
|
-
npm test # node --test, 191 cases
|
|
279
|
-
npm run smoke # registers the tools against a real DSH bundle, asserts lossless output
|
|
280
|
-
node scripts/check-locale.mjs # static t() keys must exist in both dictionaries
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
Layout:
|
|
284
|
-
|
|
285
|
-
```
|
|
286
|
-
index.js host plugin: Config, apply, six routes
|
|
287
|
-
lib/ format, parse, validate, registry, bridge, verify, render, library,
|
|
288
|
-
config-view, json, http, write, service, teams-state, tools
|
|
289
|
-
client.js client plugin: sidebar entry, main panel (rosters/teams/health), settings section
|
|
290
|
-
locale/ panel + listing dictionaries (en/zh)
|
|
291
|
-
schema/ roster.v1.schema.json (editor completion; kept aligned by tests)
|
|
292
|
-
bin/cli.mjs validate/list/version for CI
|
|
293
|
-
scripts/ host-smoke.mjs, client-smoke.mjs, check-locale.mjs (dev only, not shipped)
|
|
294
|
-
examples/ two example libraries
|
|
295
|
-
test/ unit tests
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
## Verification status
|
|
299
|
-
|
|
300
|
-
Verified on this machine:
|
|
301
|
-
|
|
302
|
-
- `npm test` —
|
|
303
|
-
- `npm run smoke` — `apply()` registers all
|
|
304
|
-
- CLI: `validate --dir examples` returns exit 0 over both example libraries.
|
|
305
|
-
- 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.
|
|
306
|
-
- The read-only route answered from the running host: `GET /plugins/dsh-agent-roster/library` → 200 with the plugin version and resolved rosters.
|
|
307
|
-
- A second agent session saw all
|
|
308
|
-
- The sidebar panel was confirmed visible with real roster data.
|
|
309
|
-
|
|
310
|
-
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.
|
|
311
|
-
|
|
312
|
-
Three real defects were found by this verification and fixed:
|
|
313
|
-
|
|
314
|
-
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.
|
|
315
|
-
2. `roster_list` ignored its `path` argument and always scanned the configured directory. `resolvePaths` now treats an explicit `path` as an override.
|
|
316
|
-
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.
|
|
317
|
-
|
|
318
|
-
## License
|
|
319
|
-
|
|
320
|
-
MIT — see [LICENSE](./LICENSE).
|
|
304
|
+
| `registryMirror` | `""` | custom npm registry for download sources, e.g. `https://registry.npmmirror.com` or `http://nexus.corp.local/repository/npm`. Set it and it beats `region`; a registry pinned inside a source still beats both. Empty means no custom mirror |
|
|
305
|
+
|
|
306
|
+
All six are `volatile`: changes apply immediately, no host restart.
|
|
307
|
+
|
|
308
|
+
## Diagnostics
|
|
309
|
+
|
|
310
|
+
Validation reports errors, warnings and infos with `file`, `path` and `reason`. Families:
|
|
311
|
+
|
|
312
|
+
- structure: `missing-field`, `unknown-key`, `invalid-type`, `invalid-kind`, `pair-required`, `invalid-value`
|
|
313
|
+
- values: `invalid-id`, `invalid-provider`, `invalid-model`, `invalid-semver`, `invalid-tag`, `invalid-tool-name`, `invalid-tool-policy-id`, `invalid-number`, `empty-tool-scope`
|
|
314
|
+
- safety: `reserved-character` (`^`, `$`, `|`, braces), `forbidden-content` (prompt-injection patterns), `single-line-required`
|
|
315
|
+
- limits: `string-too-long`, `too-many-members`, `too-many-values`
|
|
316
|
+
- 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`
|
|
317
|
+
- remote sources: `index-unreachable`, `index-invalid`, `index-refresh-throttled`, `index-source-invalid`, `collection-fetch-failed`, `collection-not-found`
|
|
318
|
+
|
|
319
|
+
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.
|
|
320
|
+
|
|
321
|
+
## Relationship to `dsh-agent-teams`
|
|
322
|
+
|
|
323
|
+
`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.
|
|
324
|
+
|
|
325
|
+
## Development
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
npm install
|
|
329
|
+
npm test # node --test, 191 cases
|
|
330
|
+
npm run smoke # registers the tools against a real DSH bundle, asserts lossless output
|
|
331
|
+
node scripts/check-locale.mjs # static t() keys must exist in both dictionaries
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Layout:
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
index.js host plugin: Config, apply, six routes
|
|
338
|
+
lib/ format, parse, validate, registry, bridge, verify, render, library,
|
|
339
|
+
config-view, json, http, write, service, teams-state, tools
|
|
340
|
+
client.js client plugin: sidebar entry, main panel (rosters/teams/health), settings section
|
|
341
|
+
locale/ panel + listing dictionaries (en/zh)
|
|
342
|
+
schema/ roster.v1.schema.json (editor completion; kept aligned by tests)
|
|
343
|
+
bin/cli.mjs validate/list/version for CI
|
|
344
|
+
scripts/ host-smoke.mjs, client-smoke.mjs, check-locale.mjs (dev only, not shipped)
|
|
345
|
+
examples/ two example libraries
|
|
346
|
+
test/ unit tests
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Verification status
|
|
350
|
+
|
|
351
|
+
Verified on this machine:
|
|
352
|
+
|
|
353
|
+
- `npm test` — 223/223 passing (format, parse, validate, registry, paths, bridge, verify buckets, digests and provenance, roster diff, capability requirements and the capability gate, 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).
|
|
354
|
+
- `npm run smoke` — `apply()` registers all six tools through a real `defineTool` from a DSH bundle; `roster_list` / `roster_show` / `roster_create` / `roster_verify` / `roster_diff` 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.
|
|
355
|
+
- CLI: `validate --dir examples` returns exit 0 over both example libraries.
|
|
356
|
+
- 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.
|
|
357
|
+
- The read-only route answered from the running host: `GET /plugins/dsh-agent-roster/library` → 200 with the plugin version and resolved rosters.
|
|
358
|
+
- A second agent session saw all six tools in its tool list and called them.
|
|
359
|
+
- The sidebar panel was confirmed visible with real roster data.
|
|
360
|
+
|
|
361
|
+
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.
|
|
362
|
+
|
|
363
|
+
Three real defects were found by this verification and fixed:
|
|
364
|
+
|
|
365
|
+
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.
|
|
366
|
+
2. `roster_list` ignored its `path` argument and always scanned the configured directory. `resolvePaths` now treats an explicit `path` as an override.
|
|
367
|
+
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.
|
|
368
|
+
|
|
369
|
+
## License
|
|
370
|
+
|
|
371
|
+
MIT — see [LICENSE](./LICENSE).
|