opencode-herdr-orchestration 0.1.5 → 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 +247 -166
- package/bin/orchestration.js +31 -5
- package/hooks/pre-push +8 -8
- package/package.json +2 -2
- package/src/agents.js +196 -53
- package/src/index.js +99 -7
- package/src/installer.js +172 -5
- package/src/prompts.js +103 -42
- package/src/response.js +36 -12
- package/src/state.js +336 -0
package/hooks/pre-push
CHANGED
|
@@ -4,11 +4,11 @@ set -eu
|
|
|
4
4
|
|
|
5
5
|
mode=${SHEPHERD_MODE:-none}
|
|
6
6
|
case "$mode" in
|
|
7
|
-
|
|
8
|
-
echo "opencode-herdr-orchestration: $mode may not push;
|
|
7
|
+
shepherd|sheepdog|grazer|sheep|shearer)
|
|
8
|
+
echo "opencode-herdr-orchestration: $mode may not push; final delivery belongs to the shepherd-governor." >&2
|
|
9
9
|
exit 1
|
|
10
10
|
;;
|
|
11
|
-
|
|
11
|
+
governor)
|
|
12
12
|
;;
|
|
13
13
|
*)
|
|
14
14
|
exit 0
|
|
@@ -17,7 +17,7 @@ esac
|
|
|
17
17
|
|
|
18
18
|
current_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)
|
|
19
19
|
if [ -z "$current_branch" ]; then
|
|
20
|
-
echo "opencode-herdr-orchestration: shepherd-
|
|
20
|
+
echo "opencode-herdr-orchestration: shepherd-governor may not push from detached HEAD." >&2
|
|
21
21
|
exit 1
|
|
22
22
|
fi
|
|
23
23
|
|
|
@@ -32,7 +32,7 @@ is_protected() {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
if is_protected "$current_branch"; then
|
|
35
|
-
echo "opencode-herdr-orchestration: shepherd-
|
|
35
|
+
echo "opencode-herdr-orchestration: shepherd-governor may not push protected branch $current_branch." >&2
|
|
36
36
|
exit 1
|
|
37
37
|
fi
|
|
38
38
|
|
|
@@ -41,20 +41,20 @@ while read -r local_ref local_sha remote_ref remote_sha; do
|
|
|
41
41
|
[ -n "${local_ref:-}" ] || continue
|
|
42
42
|
|
|
43
43
|
if [ "$local_sha" = "$zero" ]; then
|
|
44
|
-
echo "opencode-herdr-orchestration: shepherd-
|
|
44
|
+
echo "opencode-herdr-orchestration: shepherd-governor may not delete remote refs." >&2
|
|
45
45
|
exit 1
|
|
46
46
|
fi
|
|
47
47
|
|
|
48
48
|
expected_local="refs/heads/$current_branch"
|
|
49
49
|
expected_remote="refs/heads/$current_branch"
|
|
50
50
|
if [ "$local_ref" != "$expected_local" ] || [ "$remote_ref" != "$expected_remote" ]; then
|
|
51
|
-
echo "opencode-herdr-orchestration: shepherd-
|
|
51
|
+
echo "opencode-herdr-orchestration: shepherd-governor may push only $expected_local to the same remote branch." >&2
|
|
52
52
|
exit 1
|
|
53
53
|
fi
|
|
54
54
|
|
|
55
55
|
remote_branch=${remote_ref#refs/heads/}
|
|
56
56
|
if is_protected "$remote_branch"; then
|
|
57
|
-
echo "opencode-herdr-orchestration: shepherd-
|
|
57
|
+
echo "opencode-herdr-orchestration: shepherd-governor may not update protected branch $remote_branch." >&2
|
|
58
58
|
exit 1
|
|
59
59
|
fi
|
|
60
60
|
done
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-herdr-orchestration",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Capability-separated Herdr orchestration agents for OpenCode",
|
|
5
5
|
"author": "CodingJinxx",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
30
|
"test": "node --test",
|
|
31
|
-
"check": "node --check src/plugin.js && node --check src/index.js && node --check src/agents.js && node --check src/prompts.js && node --check src/response.js && node --check bin/orchestration.js"
|
|
31
|
+
"check": "node --check src/plugin.js && node --check src/index.js && node --check src/agents.js && node --check src/prompts.js && node --check src/response.js && node --check src/state.js && node --check bin/orchestration.js"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=20"
|
package/src/agents.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
GRAZER_PROMPT,
|
|
3
|
+
SHEEPDOG_PROMPT,
|
|
4
|
+
SHEEP_PROMPT,
|
|
5
|
+
SHEPHERD_GOVERNOR_PROMPT,
|
|
6
|
+
SHEPHERD_PROMPT,
|
|
6
7
|
SHEARER_REVIEW_PROMPT,
|
|
7
8
|
} from "./prompts.js";
|
|
8
9
|
|
|
@@ -66,6 +67,112 @@ const markdownOnly = {
|
|
|
66
67
|
"**/*.md": "allow",
|
|
67
68
|
};
|
|
68
69
|
|
|
70
|
+
// Constrained orchestration state tools. The planning shepherd authors plan
|
|
71
|
+
// artifacts; shepherd-governor and sheepdog read the authoritative plan, and
|
|
72
|
+
// sheepdog records and reads execution artifacts. This matrix is the single
|
|
73
|
+
// source of truth for both the plugin-level tool enforcement (src/index.js)
|
|
74
|
+
// and the per-agent permission entries below.
|
|
75
|
+
export const STATE_TOOLS = Object.freeze({
|
|
76
|
+
planWrite: "herdr_plan_write",
|
|
77
|
+
planRead: "herdr_plan_read",
|
|
78
|
+
executionWrite: "herdr_execution_write",
|
|
79
|
+
executionRead: "herdr_execution_read",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export const STATE_TOOL_ACCESS = Object.freeze(
|
|
83
|
+
new Map([
|
|
84
|
+
[STATE_TOOLS.planWrite, new Set(["shepherd"])],
|
|
85
|
+
[STATE_TOOLS.planRead, new Set(["shepherd", "shepherd-governor", "sheepdog"])],
|
|
86
|
+
[STATE_TOOLS.executionWrite, new Set(["sheepdog"])],
|
|
87
|
+
[STATE_TOOLS.executionRead, new Set(["sheepdog"])],
|
|
88
|
+
]),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const ALL_STATE_TOOLS = Object.freeze(Object.values(STATE_TOOLS));
|
|
92
|
+
|
|
93
|
+
function stateToolPermissions(allowedTools) {
|
|
94
|
+
return Object.fromEntries(
|
|
95
|
+
ALL_STATE_TOOLS.map((name) => [name, allowedTools.includes(name) ? "allow" : "deny"]),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const SHEPHERD_STATE_TOOLS = [STATE_TOOLS.planWrite, STATE_TOOLS.planRead];
|
|
100
|
+
const GOVERNOR_STATE_TOOLS = [STATE_TOOLS.planRead];
|
|
101
|
+
const SHEEPDOG_STATE_TOOLS = [
|
|
102
|
+
STATE_TOOLS.planRead,
|
|
103
|
+
STATE_TOOLS.executionWrite,
|
|
104
|
+
STATE_TOOLS.executionRead,
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const SHEPHERD_SPAWNABLE_AGENTS = ["grazer"];
|
|
108
|
+
const GOVERNOR_SPAWNABLE_AGENTS = ["grazer", "sheepdog"];
|
|
109
|
+
const SHEEPDOG_SPAWNABLE_AGENTS = ["grazer", "sheep", "shearer-low", "shearer-medium"];
|
|
110
|
+
|
|
111
|
+
const SHEEPDOG_LIFECYCLE_ALLOWS = {
|
|
112
|
+
"git merge --ff-only*": "allow",
|
|
113
|
+
"git merge --no-ff --no-edit*": "allow",
|
|
114
|
+
"git merge --continue": "allow",
|
|
115
|
+
"git merge --abort": "allow",
|
|
116
|
+
"git merge --quit": "allow",
|
|
117
|
+
"git cherry-pick*": "allow",
|
|
118
|
+
"git cherry-pick --continue": "allow",
|
|
119
|
+
"git cherry-pick --skip": "allow",
|
|
120
|
+
"git cherry-pick --abort": "allow",
|
|
121
|
+
"git cherry-pick --quit": "allow",
|
|
122
|
+
"git commit*": "allow",
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const SHEEPDOG_DENIALS = {
|
|
126
|
+
"git push*": "deny",
|
|
127
|
+
"git pull*": "deny",
|
|
128
|
+
"git fetch*": "deny",
|
|
129
|
+
"git remote*": "deny",
|
|
130
|
+
"git rebase*": "deny",
|
|
131
|
+
"git reset*": "deny",
|
|
132
|
+
"git revert*": "deny",
|
|
133
|
+
"git checkout*": "deny",
|
|
134
|
+
"git switch*": "deny",
|
|
135
|
+
"git stash*": "deny",
|
|
136
|
+
"git clean*": "deny",
|
|
137
|
+
"git worktree*": "deny",
|
|
138
|
+
"git branch -D*": "deny",
|
|
139
|
+
"git branch -d*": "deny",
|
|
140
|
+
"git commit --no-verify*": "deny",
|
|
141
|
+
"git commit * --no-verify*": "deny",
|
|
142
|
+
"git commit --amend*": "deny",
|
|
143
|
+
"git commit * --amend*": "deny",
|
|
144
|
+
"git merge *--no-verify*": "deny",
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const SHEEP_DENIALS = {
|
|
148
|
+
"herdr*": "deny",
|
|
149
|
+
"gh*": "deny",
|
|
150
|
+
"npm publish*": "deny",
|
|
151
|
+
"git push*": "deny",
|
|
152
|
+
"git pull*": "deny",
|
|
153
|
+
"git fetch*": "deny",
|
|
154
|
+
"git remote*": "deny",
|
|
155
|
+
"git merge*": "deny",
|
|
156
|
+
"git cherry-pick*": "deny",
|
|
157
|
+
"git rebase*": "deny",
|
|
158
|
+
"git reset*": "deny",
|
|
159
|
+
"git revert*": "deny",
|
|
160
|
+
"git checkout*": "deny",
|
|
161
|
+
"git switch*": "deny",
|
|
162
|
+
"git stash*": "deny",
|
|
163
|
+
"git clean*": "deny",
|
|
164
|
+
"git worktree*": "deny",
|
|
165
|
+
"git tag*": "deny",
|
|
166
|
+
"git apply*": "deny",
|
|
167
|
+
"git am*": "deny",
|
|
168
|
+
"git branch -D*": "deny",
|
|
169
|
+
"git branch -d*": "deny",
|
|
170
|
+
"git commit --no-verify*": "deny",
|
|
171
|
+
"git commit *--no-verify*": "deny",
|
|
172
|
+
"git commit --amend*": "deny",
|
|
173
|
+
"git commit *--amend*": "deny",
|
|
174
|
+
};
|
|
175
|
+
|
|
69
176
|
function mergeRecord(base, override) {
|
|
70
177
|
if (!base || typeof base !== "object" || Array.isArray(base)) return override ?? base;
|
|
71
178
|
if (!override || typeof override !== "object" || Array.isArray(override)) return override ?? base;
|
|
@@ -80,29 +187,40 @@ export function mergeAgent(defaults, override) {
|
|
|
80
187
|
return mergeRecord(defaults, override);
|
|
81
188
|
}
|
|
82
189
|
|
|
190
|
+
function spawnMatrix(agents) {
|
|
191
|
+
return Object.fromEntries(
|
|
192
|
+
agents.map((agent) => [`herdr agent start * --kind opencode --pane * -- --agent ${agent}`, "allow"]),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
83
196
|
export function createAgents(options = {}) {
|
|
84
197
|
const shepherdModel = options.shepherdModel;
|
|
85
198
|
const workerModel = options.workerModel ?? "litellm/glm-5.3-flash";
|
|
199
|
+
const workerVariant = options.workerVariant;
|
|
200
|
+
const sheepdogModel = options.sheepdogModel ?? "litellm/glm-5.3-flash";
|
|
201
|
+
const sheepdogVariant = options.sheepdogVariant;
|
|
86
202
|
const reviewerModel = options.reviewerModel ?? "litellm-responses/gpt-5.6-terra";
|
|
87
|
-
const
|
|
88
|
-
const
|
|
203
|
+
const shepherdPermissions = options.shepherdPermissions ?? {};
|
|
204
|
+
const shepherdPrompt = appendPrompt(SHEPHERD_PROMPT, options.shepherdPromptAppend);
|
|
89
205
|
|
|
90
206
|
return {
|
|
91
|
-
|
|
207
|
+
shepherd: {
|
|
92
208
|
mode: "primary",
|
|
93
209
|
...(shepherdModel ? { model: shepherdModel } : {}),
|
|
94
|
-
description:
|
|
95
|
-
|
|
210
|
+
description:
|
|
211
|
+
"Plans through grazer research and presents implementation-ready plans; never implements, integrates, or delivers.",
|
|
212
|
+
prompt: shepherdPrompt,
|
|
96
213
|
permission: {
|
|
97
214
|
grep: "allow",
|
|
98
215
|
todowrite: "allow",
|
|
99
216
|
edit: markdownOnly,
|
|
100
217
|
apply_patch: markdownOnly,
|
|
101
218
|
herdr_agent_response: "allow",
|
|
219
|
+
...stateToolPermissions(SHEPHERD_STATE_TOOLS),
|
|
102
220
|
bash: {
|
|
103
221
|
"*": "deny",
|
|
104
222
|
...herdrInspection,
|
|
105
|
-
|
|
223
|
+
...spawnMatrix(SHEPHERD_SPAWNABLE_AGENTS),
|
|
106
224
|
"git status*": "allow",
|
|
107
225
|
"git diff*": "allow",
|
|
108
226
|
"git log*": "allow",
|
|
@@ -114,44 +232,38 @@ export function createAgents(options = {}) {
|
|
|
114
232
|
"git add *.md": "allow",
|
|
115
233
|
"git add **/*.md": "allow",
|
|
116
234
|
"git commit*": "allow",
|
|
117
|
-
"git push
|
|
118
|
-
"git push -u origin HEAD": "allow",
|
|
119
|
-
"git push --set-upstream origin HEAD": "allow",
|
|
235
|
+
"git push*": "deny",
|
|
120
236
|
"git commit --no-verify*": "deny",
|
|
121
237
|
"git commit * --no-verify*": "deny",
|
|
122
|
-
"git
|
|
123
|
-
"git
|
|
124
|
-
"git push *:*": "deny",
|
|
125
|
-
"git push *--no-verify*": "deny",
|
|
126
|
-
"git push *--force*": "deny",
|
|
127
|
-
"git push *-f*": "deny",
|
|
128
|
-
"git push *--delete*": "deny",
|
|
238
|
+
"git commit --amend*": "deny",
|
|
239
|
+
"git commit * --amend*": "deny",
|
|
129
240
|
"git branch -D*": "deny",
|
|
130
241
|
"git branch -d*": "deny",
|
|
131
242
|
"git worktree remove * --force*": "deny",
|
|
132
243
|
...separatorDenials,
|
|
133
244
|
},
|
|
134
245
|
task: "deny",
|
|
246
|
+
...shepherdPermissions,
|
|
135
247
|
},
|
|
136
248
|
},
|
|
137
249
|
|
|
138
|
-
"shepherd-
|
|
250
|
+
"shepherd-governor": {
|
|
139
251
|
mode: "primary",
|
|
140
252
|
...(shepherdModel ? { model: shepherdModel } : {}),
|
|
141
|
-
description:
|
|
142
|
-
|
|
253
|
+
description:
|
|
254
|
+
"Executes approved plans through sheepdog squads, verifies results, and owns final delivery: pushes, merges, PRs.",
|
|
255
|
+
prompt: SHEPHERD_GOVERNOR_PROMPT,
|
|
143
256
|
permission: {
|
|
144
257
|
grep: "allow",
|
|
258
|
+
todowrite: "allow",
|
|
145
259
|
edit: markdownOnly,
|
|
146
260
|
apply_patch: markdownOnly,
|
|
147
261
|
herdr_agent_response: "allow",
|
|
262
|
+
...stateToolPermissions(GOVERNOR_STATE_TOOLS),
|
|
148
263
|
bash: {
|
|
149
264
|
"*": "deny",
|
|
150
265
|
...herdrInspection,
|
|
151
|
-
|
|
152
|
-
"herdr agent start * --kind opencode --pane * -- --agent sheep-build": "allow",
|
|
153
|
-
"herdr agent start * --kind opencode --pane * -- --agent shearer-review-low": "allow",
|
|
154
|
-
"herdr agent start * --kind opencode --pane * -- --agent shearer-review-medium": "allow",
|
|
266
|
+
...spawnMatrix(GOVERNOR_SPAWNABLE_AGENTS),
|
|
155
267
|
"git status*": "allow",
|
|
156
268
|
"git diff*": "allow",
|
|
157
269
|
"git log*": "allow",
|
|
@@ -159,7 +271,7 @@ export function createAgents(options = {}) {
|
|
|
159
271
|
"git branch*": "allow",
|
|
160
272
|
"git rev-parse*": "allow",
|
|
161
273
|
"git merge-base*": "allow",
|
|
162
|
-
"git worktree*": "allow",
|
|
274
|
+
"git worktree list*": "allow",
|
|
163
275
|
"git add *.md": "allow",
|
|
164
276
|
"git add **/*.md": "allow",
|
|
165
277
|
"git commit*": "allow",
|
|
@@ -174,25 +286,64 @@ export function createAgents(options = {}) {
|
|
|
174
286
|
"git push *--delete*": "deny",
|
|
175
287
|
"git push *--no-verify*": "deny",
|
|
176
288
|
"git commit *--no-verify*": "deny",
|
|
289
|
+
"git commit --amend*": "deny",
|
|
290
|
+
"git commit *--amend*": "deny",
|
|
177
291
|
"git merge *--no-verify*": "deny",
|
|
178
292
|
"git branch -D*": "deny",
|
|
179
293
|
"git branch -d*": "deny",
|
|
294
|
+
"git worktree add*": "deny",
|
|
295
|
+
"git worktree remove*": "deny",
|
|
296
|
+
"git worktree move*": "deny",
|
|
297
|
+
"git worktree prune*": "deny",
|
|
180
298
|
"git worktree remove * --force*": "deny",
|
|
299
|
+
"herdr worktree create*": "deny",
|
|
300
|
+
"herdr worktree open*": "deny",
|
|
301
|
+
"herdr worktree remove*": "deny",
|
|
181
302
|
"gh pr create*": "allow",
|
|
182
303
|
"gh pr view*": "allow",
|
|
183
304
|
"gh pr checks*": "allow",
|
|
184
305
|
...separatorDenials,
|
|
185
306
|
},
|
|
186
307
|
task: "deny",
|
|
187
|
-
...shepherdBuildPermissions,
|
|
188
308
|
},
|
|
189
309
|
},
|
|
190
310
|
|
|
191
|
-
|
|
311
|
+
sheepdog: {
|
|
312
|
+
mode: "primary",
|
|
313
|
+
model: sheepdogModel,
|
|
314
|
+
...(sheepdogVariant ? { variant: sheepdogVariant } : {}),
|
|
315
|
+
description:
|
|
316
|
+
"Leads execution squads of grazer, sheep, and shearers, prepares worker branches and worktrees, owns validation, review tiers, retries, and conflict recovery, and performs clean local integration with merge and cherry-pick lifecycle commands only.",
|
|
317
|
+
prompt: SHEEPDOG_PROMPT,
|
|
318
|
+
permission: {
|
|
319
|
+
read: "allow",
|
|
320
|
+
glob: "allow",
|
|
321
|
+
grep: "allow",
|
|
322
|
+
list: "allow",
|
|
323
|
+
todowrite: "allow",
|
|
324
|
+
edit: "deny",
|
|
325
|
+
apply_patch: "deny",
|
|
326
|
+
herdr_agent_response: "allow",
|
|
327
|
+
...stateToolPermissions(SHEEPDOG_STATE_TOOLS),
|
|
328
|
+
bash: {
|
|
329
|
+
"*": "deny",
|
|
330
|
+
...safeGitInspection,
|
|
331
|
+
...herdrInspection,
|
|
332
|
+
...spawnMatrix(SHEEPDOG_SPAWNABLE_AGENTS),
|
|
333
|
+
...SHEEPDOG_LIFECYCLE_ALLOWS,
|
|
334
|
+
...SHEEPDOG_DENIALS,
|
|
335
|
+
...separatorDenials,
|
|
336
|
+
},
|
|
337
|
+
task: "deny",
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
grazer: {
|
|
192
342
|
mode: "primary",
|
|
193
343
|
model: workerModel,
|
|
194
|
-
|
|
195
|
-
|
|
344
|
+
...(workerVariant ? { variant: workerVariant } : {}),
|
|
345
|
+
description: "Performs read-only repository research for the shepherd, shepherd-governor, or sheepdog.",
|
|
346
|
+
prompt: GRAZER_PROMPT,
|
|
196
347
|
permission: {
|
|
197
348
|
read: "allow",
|
|
198
349
|
glob: "allow",
|
|
@@ -202,50 +353,41 @@ export function createAgents(options = {}) {
|
|
|
202
353
|
edit: "deny",
|
|
203
354
|
apply_patch: "deny",
|
|
204
355
|
herdr_agent_response: "deny",
|
|
356
|
+
...stateToolPermissions([]),
|
|
205
357
|
bash: { "*": "deny", ...safeGitInspection, ...separatorDenials },
|
|
206
358
|
task: "deny",
|
|
207
359
|
},
|
|
208
360
|
},
|
|
209
361
|
|
|
210
|
-
|
|
362
|
+
sheep: {
|
|
211
363
|
mode: "primary",
|
|
212
364
|
model: workerModel,
|
|
213
|
-
|
|
214
|
-
|
|
365
|
+
...(workerVariant ? { variant: workerVariant } : {}),
|
|
366
|
+
description:
|
|
367
|
+
"Implements a bounded task, verifies it, and hands a local commit to sheepdog.",
|
|
368
|
+
prompt: SHEEP_PROMPT,
|
|
215
369
|
permission: {
|
|
216
370
|
grep: "allow",
|
|
217
371
|
herdr_agent_response: "deny",
|
|
372
|
+
...stateToolPermissions([]),
|
|
218
373
|
bash: {
|
|
219
374
|
"*": "allow",
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
"git pull*": "deny",
|
|
223
|
-
"git branch -D*": "deny",
|
|
224
|
-
"git branch -d*": "deny",
|
|
225
|
-
"git rebase*": "deny",
|
|
226
|
-
"git reset*": "deny",
|
|
227
|
-
"git commit *--no-verify*": "deny",
|
|
228
|
-
"git commit --no-verify*": "deny",
|
|
229
|
-
"git *;*": "deny",
|
|
230
|
-
"git *&&*": "deny",
|
|
231
|
-
"git *||*": "deny",
|
|
232
|
-
"git *|*": "deny",
|
|
233
|
-
"git *>*": "deny",
|
|
234
|
-
"git *<*": "deny",
|
|
375
|
+
...SHEEP_DENIALS,
|
|
376
|
+
...separatorDenials,
|
|
235
377
|
},
|
|
236
378
|
task: "deny",
|
|
237
379
|
},
|
|
238
380
|
},
|
|
239
381
|
|
|
240
|
-
"shearer-
|
|
241
|
-
"shearer-
|
|
382
|
+
"shearer-low": reviewerAgent(reviewerModel, "low"),
|
|
383
|
+
"shearer-medium": reviewerAgent(reviewerModel, "medium"),
|
|
242
384
|
};
|
|
243
385
|
}
|
|
244
386
|
|
|
245
387
|
function appendPrompt(prompt, addition) {
|
|
246
388
|
if (addition === undefined || addition === "") return prompt;
|
|
247
389
|
if (typeof addition !== "string") {
|
|
248
|
-
throw new TypeError("
|
|
390
|
+
throw new TypeError("shepherdPromptAppend must be a string.");
|
|
249
391
|
}
|
|
250
392
|
return `${prompt.trimEnd()}\n\n${addition.trim()}`;
|
|
251
393
|
}
|
|
@@ -266,6 +408,7 @@ function reviewerAgent(model, variant) {
|
|
|
266
408
|
apply_patch: "deny",
|
|
267
409
|
todowrite: "deny",
|
|
268
410
|
herdr_agent_response: "deny",
|
|
411
|
+
...stateToolPermissions([]),
|
|
269
412
|
bash: { "*": "deny", ...safeGitInspection, ...separatorDenials },
|
|
270
413
|
task: "deny",
|
|
271
414
|
},
|
package/src/index.js
CHANGED
|
@@ -1,20 +1,112 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
|
|
3
|
+
import { createAgents, mergeAgent, STATE_TOOL_ACCESS, STATE_TOOLS } from "./agents.js";
|
|
2
4
|
import { createResponseTool } from "./response.js";
|
|
5
|
+
import { createStateService } from "./state.js";
|
|
3
6
|
|
|
4
7
|
const SESSION_MODES = new Map();
|
|
5
8
|
|
|
6
9
|
function modeForAgent(agent) {
|
|
7
|
-
if (agent === "shepherd
|
|
8
|
-
if (agent === "shepherd-
|
|
9
|
-
if (agent === "
|
|
10
|
-
if (agent === "
|
|
11
|
-
if (agent === "sheep
|
|
10
|
+
if (agent === "shepherd") return "shepherd";
|
|
11
|
+
if (agent === "shepherd-governor") return "governor";
|
|
12
|
+
if (agent === "sheepdog") return "sheepdog";
|
|
13
|
+
if (agent === "grazer") return "grazer";
|
|
14
|
+
if (agent === "sheep") return "sheep";
|
|
15
|
+
if (agent === "shearer-low" || agent === "shearer-medium") return "shearer";
|
|
12
16
|
return "none";
|
|
13
17
|
}
|
|
14
18
|
|
|
19
|
+
function stateError(code, message, retryable = false) {
|
|
20
|
+
return { ok: false, error: { code, message, retryable } };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Exposes the constrained orchestration state API (src/state.js) as plugin
|
|
24
|
+
// tools. Enforcement mirrors STATE_TOOL_ACCESS in src/agents.js: the planning
|
|
25
|
+
// shepherd writes and reads plan artifacts; shepherd-governor and sheepdog
|
|
26
|
+
// read the authoritative plan; sheepdog writes and reads execution artifacts.
|
|
27
|
+
// Artifacts are durable Markdown files under the repository's shared Git
|
|
28
|
+
// common directory, so linked worktrees share state while separate clones
|
|
29
|
+
// never do. The state service invokes only read-only `git rev-parse`; it
|
|
30
|
+
// never writes arbitrary Git metadata.
|
|
31
|
+
export function createStateTools(stateOptions = {}) {
|
|
32
|
+
const state = createStateService(stateOptions);
|
|
33
|
+
|
|
34
|
+
const definitions = [
|
|
35
|
+
{
|
|
36
|
+
name: STATE_TOOLS.planWrite,
|
|
37
|
+
description:
|
|
38
|
+
"Durably store a plan Markdown artifact under the repository's shared Git common directory, keyed by plan ID. Planning shepherd only.",
|
|
39
|
+
write: true,
|
|
40
|
+
run: (args) => state.writePlan(args),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: STATE_TOOLS.planRead,
|
|
44
|
+
description:
|
|
45
|
+
"Read a stored plan Markdown artifact by plan ID. Shepherd, shepherd-governor, and sheepdog only.",
|
|
46
|
+
write: false,
|
|
47
|
+
run: (args) => state.readPlan(args.planId),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: STATE_TOOLS.executionWrite,
|
|
51
|
+
description:
|
|
52
|
+
"Durably store an execution Markdown artifact for a plan under the repository's shared Git common directory, keyed by plan ID. Sheepdog only.",
|
|
53
|
+
write: true,
|
|
54
|
+
run: (args) => state.writeExecution(args),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: STATE_TOOLS.executionRead,
|
|
58
|
+
description:
|
|
59
|
+
"Read a stored execution Markdown artifact by plan ID. Sheepdog only.",
|
|
60
|
+
write: false,
|
|
61
|
+
run: (args) => state.readExecution(args.planId),
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const tools = {};
|
|
66
|
+
for (const definition of definitions) {
|
|
67
|
+
tools[definition.name] = tool({
|
|
68
|
+
description: definition.description,
|
|
69
|
+
args: {
|
|
70
|
+
planId: tool.schema
|
|
71
|
+
.string()
|
|
72
|
+
.describe("Plan ID keying the artifact; 1-64 characters of letters, digits, dot, underscore, or hyphen."),
|
|
73
|
+
...(definition.write
|
|
74
|
+
? {
|
|
75
|
+
markdown: tool.schema
|
|
76
|
+
.string()
|
|
77
|
+
.min(1)
|
|
78
|
+
.describe("Complete Markdown body to store as the artifact content."),
|
|
79
|
+
}
|
|
80
|
+
: {}),
|
|
81
|
+
},
|
|
82
|
+
async execute(args, context) {
|
|
83
|
+
const allowed = STATE_TOOL_ACCESS.get(definition.name);
|
|
84
|
+
if (!allowed?.has(context?.agent)) {
|
|
85
|
+
return JSON.stringify(
|
|
86
|
+
stateError(
|
|
87
|
+
"UNAUTHORIZED_AGENT",
|
|
88
|
+
`Agent ${context?.agent ?? "unknown"} may not use ${definition.name}.`,
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const result = await definition.run(args);
|
|
93
|
+
context.metadata({
|
|
94
|
+
title: result.ok ? `${definition.name}: ${args.planId}` : `${definition.name}: ${result.error.code}`,
|
|
95
|
+
metadata: result.ok
|
|
96
|
+
? { planId: args.planId, path: result.artifact.path }
|
|
97
|
+
: { error: result.error.code },
|
|
98
|
+
});
|
|
99
|
+
return JSON.stringify(result);
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return tools;
|
|
104
|
+
}
|
|
105
|
+
|
|
15
106
|
export const HerdrOrchestrationPlugin = async (_input, options = {}) => ({
|
|
16
107
|
tool: {
|
|
17
108
|
herdr_agent_response: createResponseTool(options.response),
|
|
109
|
+
...createStateTools(options.state),
|
|
18
110
|
},
|
|
19
111
|
config(config) {
|
|
20
112
|
config.agent ??= {};
|
|
@@ -43,4 +135,4 @@ export const HerdrOrchestrationPlugin = async (_input, options = {}) => ({
|
|
|
43
135
|
});
|
|
44
136
|
|
|
45
137
|
export default HerdrOrchestrationPlugin;
|
|
46
|
-
export { createAgents, modeForAgent };
|
|
138
|
+
export { createAgents, mergeAgent, modeForAgent, STATE_TOOL_ACCESS, STATE_TOOLS };
|