opencode-herdr-orchestration 0.2.0 → 0.3.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 +315 -7
- package/bin/orchestration.js +1 -0
- package/package.json +4 -3
- package/src/agents.js +256 -5
- package/src/diagnostics.js +117 -0
- package/src/index.js +264 -6
- package/src/installer.js +193 -0
- package/src/prompts.js +213 -6
- package/src/response.js +71 -2
- package/src/state.js +2252 -11
package/src/agents.js
CHANGED
|
@@ -38,6 +38,12 @@ const safeGitInspection = {
|
|
|
38
38
|
"git ls-files": "allow",
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
// 20-M1 responsive wait: bounded `herdr agent get` polling uses only the
|
|
42
|
+
// already-permitted prompt plus wait plus get plus read plus list surfaces.
|
|
43
|
+
// No event stream command is evidenced, so no new Herdr command is invented;
|
|
44
|
+
// `herdr agent wait` stays a bounded sleep between `get` state checks and the
|
|
45
|
+
// safety timeout stays the final bound only. Sheepdog owns routine flock waits
|
|
46
|
+
// with no per-transition Shepherd wakeups; governor leaf bans stay untouched.
|
|
41
47
|
const herdrInspection = {
|
|
42
48
|
"Get-Item Env:HERDR_ENV": "allow",
|
|
43
49
|
"herdr --help": "allow",
|
|
@@ -96,6 +102,77 @@ function stateToolPermissions(allowedTools) {
|
|
|
96
102
|
);
|
|
97
103
|
}
|
|
98
104
|
|
|
105
|
+
// Developer steering submission (M2, Option A, trusted Developer only).
|
|
106
|
+
// The explicit non-flock `developer` context is the sole submitter; all
|
|
107
|
+
// seven orchestration roles are denied as defense in depth. The runtime
|
|
108
|
+
// context-agent check in src/index.js stays authoritative over these static
|
|
109
|
+
// entries: even a user override flipping one to "allow" must not bypass the
|
|
110
|
+
// allowlist. No flock role may present as Developer: developer is not a
|
|
111
|
+
// registered orchestration agent and no spawn matrix entry creates it.
|
|
112
|
+
export const DEVELOPER_AGENT = "developer";
|
|
113
|
+
export const ORCHESTRATION_ROLES = Object.freeze([
|
|
114
|
+
"shepherd",
|
|
115
|
+
"shepherd-governor",
|
|
116
|
+
"sheepdog",
|
|
117
|
+
"grazer",
|
|
118
|
+
"sheep",
|
|
119
|
+
"shearer-low",
|
|
120
|
+
"shearer-medium",
|
|
121
|
+
]);
|
|
122
|
+
export const STEERING_TOOLS = Object.freeze({
|
|
123
|
+
submit: "herdr_steering_submit",
|
|
124
|
+
});
|
|
125
|
+
export const STEERING_TOOL_ACCESS = Object.freeze(
|
|
126
|
+
new Map([[STEERING_TOOLS.submit, new Set([DEVELOPER_AGENT])]]),
|
|
127
|
+
);
|
|
128
|
+
const ALL_STEERING_TOOLS = Object.freeze(Object.values(STEERING_TOOLS));
|
|
129
|
+
|
|
130
|
+
function steeringToolPermissions(allowedTools) {
|
|
131
|
+
return Object.fromEntries(
|
|
132
|
+
ALL_STEERING_TOOLS.map((name) => [name, allowedTools.includes(name) ? "allow" : "deny"]),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Shepherd ownership raw steering plus lifecycle tools (M3). Only the two
|
|
137
|
+
// Shepherd phases may check, read, or consume raw steering and only the
|
|
138
|
+
// recorded owner phase plus session plus generation passes the state-level
|
|
139
|
+
// fencing (NOT AUTHORITATIVE PHASE otherwise). Sheepdog and every leaf are
|
|
140
|
+
// explicitly denied in code, not only in prompts; the runtime check in
|
|
141
|
+
// src/index.js stays authoritative over static overrides.
|
|
142
|
+
export const SHEPHERD_PHASES = Object.freeze(["shepherd", "shepherd-governor"]);
|
|
143
|
+
export const RAW_STEERING_TOOLS = Object.freeze({
|
|
144
|
+
check: "herdr_steering_check",
|
|
145
|
+
read: "herdr_steering_read",
|
|
146
|
+
consume: "herdr_steering_consume",
|
|
147
|
+
});
|
|
148
|
+
export const OWNERSHIP_TOOLS = Object.freeze({
|
|
149
|
+
claim: "herdr_ownership_claim",
|
|
150
|
+
read: "herdr_ownership_read",
|
|
151
|
+
sync: "herdr_ownership_sync",
|
|
152
|
+
snapshot: "herdr_ownership_snapshot",
|
|
153
|
+
correct: "herdr_ownership_correct",
|
|
154
|
+
});
|
|
155
|
+
export const RAW_STEERING_TOOL_ACCESS = Object.freeze(
|
|
156
|
+
new Map(Object.values(RAW_STEERING_TOOLS).map((name) => [name, new Set(SHEPHERD_PHASES)])),
|
|
157
|
+
);
|
|
158
|
+
export const OWNERSHIP_TOOL_ACCESS = Object.freeze(
|
|
159
|
+
new Map(Object.values(OWNERSHIP_TOOLS).map((name) => [name, new Set(SHEPHERD_PHASES)])),
|
|
160
|
+
);
|
|
161
|
+
const ALL_RAW_STEERING_TOOLS = Object.freeze(Object.values(RAW_STEERING_TOOLS));
|
|
162
|
+
const ALL_OWNERSHIP_TOOLS = Object.freeze(Object.values(OWNERSHIP_TOOLS));
|
|
163
|
+
|
|
164
|
+
function rawSteeringToolPermissions(allowedTools) {
|
|
165
|
+
return Object.fromEntries(
|
|
166
|
+
ALL_RAW_STEERING_TOOLS.map((name) => [name, allowedTools.includes(name) ? "allow" : "deny"]),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function ownershipToolPermissions(allowedTools) {
|
|
171
|
+
return Object.fromEntries(
|
|
172
|
+
ALL_OWNERSHIP_TOOLS.map((name) => [name, allowedTools.includes(name) ? "allow" : "deny"]),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
99
176
|
const SHEPHERD_STATE_TOOLS = [STATE_TOOLS.planWrite, STATE_TOOLS.planRead];
|
|
100
177
|
const GOVERNOR_STATE_TOOLS = [STATE_TOOLS.planRead];
|
|
101
178
|
const SHEEPDOG_STATE_TOOLS = [
|
|
@@ -103,6 +180,8 @@ const SHEEPDOG_STATE_TOOLS = [
|
|
|
103
180
|
STATE_TOOLS.executionWrite,
|
|
104
181
|
STATE_TOOLS.executionRead,
|
|
105
182
|
];
|
|
183
|
+
const SHEPHERD_RAW_STEERING_TOOLS = Object.values(RAW_STEERING_TOOLS);
|
|
184
|
+
const SHEPHERD_OWNERSHIP_TOOLS = Object.values(OWNERSHIP_TOOLS);
|
|
106
185
|
|
|
107
186
|
const SHEPHERD_SPAWNABLE_AGENTS = ["grazer"];
|
|
108
187
|
const GOVERNOR_SPAWNABLE_AGENTS = ["grazer", "sheepdog"];
|
|
@@ -122,6 +201,148 @@ const SHEEPDOG_LIFECYCLE_ALLOWS = {
|
|
|
122
201
|
"git commit*": "allow",
|
|
123
202
|
};
|
|
124
203
|
|
|
204
|
+
// Sheepdog Herdr lifecycle (21-M1): explicit prompt plus wait plus get plus
|
|
205
|
+
// read allows. These re-assert the shared herdrInspection entries so the four
|
|
206
|
+
// lifecycle operations stay evaluation-effective under OpenCode last-match
|
|
207
|
+
// glob semantics: "*" deny is the fallback first, these allows sit in the
|
|
208
|
+
// middle, separator denies stay global last. A prompt whose task text carries
|
|
209
|
+
// raw "; && || | > <" matches a separator deny after the prompt allow and
|
|
210
|
+
// fails closed to deny even inside quotes, so task text must avoid raw
|
|
211
|
+
// separators (see SHEEPDOG_PROMPT safe rule).
|
|
212
|
+
const SHEEPDOG_HERDR_LIFECYCLE_ALLOWS = {
|
|
213
|
+
"herdr agent prompt*": "allow",
|
|
214
|
+
"herdr agent wait*": "allow",
|
|
215
|
+
"herdr agent get*": "allow",
|
|
216
|
+
"herdr agent read*": "allow",
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Sheepdog interrupt bound (21-M1): replacement for the broad
|
|
220
|
+
// "herdr agent send-keys*" allow in shared herdrInspection. The broad pattern
|
|
221
|
+
// is explicitly denied first so only the narrow Ctrl-C spellings below are
|
|
222
|
+
// evaluation-effective. Matchers are string globs and cannot prove semantic
|
|
223
|
+
// intent, so the prompt inspect-first plus never-type rule stays primary; see
|
|
224
|
+
// SHEEPDOG_PROMPT and README Worker interruption residual.
|
|
225
|
+
const SHEEPDOG_SEND_KEYS_DENY = {
|
|
226
|
+
"herdr agent send-keys*": "deny",
|
|
227
|
+
};
|
|
228
|
+
const SHEEPDOG_SEND_KEYS_CTRL_C_ALLOWS = {
|
|
229
|
+
"herdr agent send-keys * --keys C-c*": "allow",
|
|
230
|
+
"herdr agent send-keys * --keys c-c*": "allow",
|
|
231
|
+
"herdr agent send-keys * --keys ctrl+c*": "allow",
|
|
232
|
+
"herdr agent send-keys * --keys Ctrl+C*": "allow",
|
|
233
|
+
"herdr agent send-keys * C-c*": "allow",
|
|
234
|
+
"herdr agent send-keys * ctrl+c*": "allow",
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Governor Herdr prompt residual (21-M2): `herdr agent prompt` plus wait plus
|
|
238
|
+
// get plus read patterns are name-based and cannot encode worker role, so they
|
|
239
|
+
// stay broad as far as text matchers permit. Prompt bans plus start denial
|
|
240
|
+
// (spawn matrix allows only grazer and sheepdog) plus the response matrix
|
|
241
|
+
// (retrieval allows only grazer and sheepdog) are the load-bearing layers;
|
|
242
|
+
// see SHEPHERD_GOVERNOR_PROMPT and README Governor prompt scoping residual.
|
|
243
|
+
// Governor interrupt bound (21-M2): replacement for the broad
|
|
244
|
+
// "herdr agent send-keys*" allow in shared herdrInspection, mirroring the
|
|
245
|
+
// sheepdog M1 bound. The broad pattern is explicitly denied first so only the
|
|
246
|
+
// narrow Ctrl-C spellings below are evaluation-effective. Matchers are string
|
|
247
|
+
// globs and cannot prove semantic intent, so the prompt inspect-first plus
|
|
248
|
+
// never-type plus never-bypass rule stays primary.
|
|
249
|
+
const GOVERNOR_SEND_KEYS_DENY = {
|
|
250
|
+
"herdr agent send-keys*": "deny",
|
|
251
|
+
};
|
|
252
|
+
const GOVERNOR_SEND_KEYS_CTRL_C_ALLOWS = {
|
|
253
|
+
"herdr agent send-keys * --keys C-c*": "allow",
|
|
254
|
+
"herdr agent send-keys * --keys c-c*": "allow",
|
|
255
|
+
"herdr agent send-keys * --keys ctrl+c*": "allow",
|
|
256
|
+
"herdr agent send-keys * --keys Ctrl+C*": "allow",
|
|
257
|
+
"herdr agent send-keys * C-c*": "allow",
|
|
258
|
+
"herdr agent send-keys * ctrl+c*": "allow",
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Pane layout (14-18-M1): live discovery on installed 0.8.2 via full-path
|
|
262
|
+
// `herdr --help` plus `herdr --skill` plus scoped read-only queries
|
|
263
|
+
// (`pane list --workspace w1K`, `tab list --workspace w1K`,
|
|
264
|
+
// `pane current --current`, `pane layout --current`, `agent get <name>`,
|
|
265
|
+
// `pane get w1K:p999` error sampling). Direct `herdr ...` spelling stays
|
|
266
|
+
// denied for leaves; discovery used only read-only help plus list plus get
|
|
267
|
+
// plus current plus layout, never split plus rename plus close plus start.
|
|
268
|
+
// Tab evidenced as list plus create plus get plus focus plus rename plus
|
|
269
|
+
// close; list takes `--workspace <ID>`, create takes
|
|
270
|
+
// `--workspace/--cwd/--label/--env/--focus/--no-focus`, rename takes
|
|
271
|
+
// `<TAB_ID> <LABEL>...`, close plus get plus focus take `<tab_id>`.
|
|
272
|
+
// Pane placement evidenced as split plus move plus focus plus resize plus
|
|
273
|
+
// swap plus layout plus get plus list plus current plus read plus rename plus
|
|
274
|
+
// close; split is `[PANE_ID] --pane/--current --direction right/down --ratio
|
|
275
|
+
// --cwd --env --focus/--no-focus`, move is `<PANE_ID>
|
|
276
|
+
// --tab/--split/--target-pane/--ratio/--new-tab/--workspace/--new-workspace/--label/--tab-label/--focus/--no-focus`,
|
|
277
|
+
// focus and neighbor take `--direction left/right/up/down --pane/--current`,
|
|
278
|
+
// resize takes `--direction --amount --pane/--current`, swap takes
|
|
279
|
+
// `--direction/--pane/--current/--source-pane/--target-pane`, layout takes
|
|
280
|
+
// `--pane/--current`, get takes `<pane_id>`, list takes
|
|
281
|
+
// `--workspace <ID>`, current takes `--pane/--current`, read takes
|
|
282
|
+
// `<PANE_ID> --source visible/recent/recent-unwrapped/detection
|
|
283
|
+
// --lines/--format/--ansi/--raw`, rename takes `<PANE_ID> [LABEL]...
|
|
284
|
+
// --clear`, close takes `<pane_id>`.
|
|
285
|
+
// Agent rename evidenced as `herdr agent rename <TARGET> <NAME>|--clear`
|
|
286
|
+
// (live `agent list` shows `name` such as `issue1418m1sheep` and `agent get`
|
|
287
|
+
// shows `name` plus `pane_id`); pane rename plus tab rename helps evidenced
|
|
288
|
+
// but only pane plus agent rename are enabled, tab rename stays denied to
|
|
289
|
+
// keep the single-tab 4-pane cap.
|
|
290
|
+
// Count queries evidenced as `herdr tab list --workspace <ID>` returning
|
|
291
|
+
// `{"id":"cli:tab:list","result":{"tabs":[{"pane_count":1,"tab_id":"w1K:t1",...}]},"type":"tab_list"}`,
|
|
292
|
+
// `herdr pane list --workspace <ID>` returning
|
|
293
|
+
// `{"id":"cli:pane:list","result":{"panes":[{...,"pane_id":"w1K:p1","tab_id":"w1K:t1","workspace_id":"w1K",...}]},"type":"pane_list"}`
|
|
294
|
+
// (count is length filtered by workspace plus tab), `herdr pane layout
|
|
295
|
+
// --current` returning
|
|
296
|
+
// `{"id":"cli:pane:layout","result":{"layout":{"panes":[...],"splits":[],"focused_pane_id":"w1K:p1",...}},"type":"pane_layout"}`,
|
|
297
|
+
// `herdr pane current --current` returning
|
|
298
|
+
// `{"id":"cli:pane:current","result":{"pane":{...}},"type":"pane_current"}`.
|
|
299
|
+
// Creation JSON per `herdr --skill`: `tab create` returns
|
|
300
|
+
// `.result.tab` plus `.result.root_pane`, `pane split` returns the new pane
|
|
301
|
+
// as `.result.pane` (skill geometry is `pane split --current --direction
|
|
302
|
+
// right/down --cwd "$PWD" --no-focus`, wide to the right and narrow or tall
|
|
303
|
+
// down, reading `.result.pane.pane_id` and never deriving from sidebar
|
|
304
|
+
// order), `pane move` would return
|
|
305
|
+
// `.result.move_result.pane.pane_id` plus `.result.move_result.previous_pane_id`
|
|
306
|
+
// but move stays denied and is documented only.
|
|
307
|
+
// Exits per `herdr --skill` plus live sampling: most controls return JSON on
|
|
308
|
+
// stdout with `id` plus `result` plus `type`; server errors are JSON such as
|
|
309
|
+
// `{"error":{"code":"pane_not_found","message":"pane w1K:p999 not found"},"id":"cli:pane:get"}`
|
|
310
|
+
// on stderr with exit 1; syntax plus validation such as `pane split
|
|
311
|
+
// --direction invalid` returning `invalid split direction` exits with 2.
|
|
312
|
+
// IDs are opaque stable handles (`w1K`, `w1K:t1`, `w1K:p1` from
|
|
313
|
+
// `HERDR_WORKSPACE_ID` plus `HERDR_TAB_ID` plus `HERDR_PANE_ID`); closed IDs
|
|
314
|
+
// are not reused; prefer `--current` and never rely on the UI-focused pane.
|
|
315
|
+
// Fallback if any of tab list plus pane get plus rename plus close plus
|
|
316
|
+
// split are missing: reuse the current pane via `--pane` plus `--current`,
|
|
317
|
+
// report STOP naming the missing capability, never invent `herdr pane
|
|
318
|
+
// create*` plus `herdr tab split*` plus `herdr agent events*` plus
|
|
319
|
+
// `herdr pane move*` plus `herdr pane resize*` plus `herdr workspace
|
|
320
|
+
// create*` behavior.
|
|
321
|
+
// Protected Dev Developer Terminal exclusion cannot be matcher-enforced:
|
|
322
|
+
// pane IDs are opaque and labels are absent from scan plus split plus close
|
|
323
|
+
// command strings, while a `*Dev*` glob would overmatch legitimate
|
|
324
|
+
// `--cwd C:\Dev\...` values, so no such glob is added; the prompt plus
|
|
325
|
+
// README Pane layout policy exclusion stays primary; see README residual.
|
|
326
|
+
const SHEPHERD_PANE_ALLOWS = {
|
|
327
|
+
"herdr tab list*": "allow",
|
|
328
|
+
"herdr pane get*": "allow",
|
|
329
|
+
"herdr pane rename*": "allow",
|
|
330
|
+
"herdr agent rename*": "allow",
|
|
331
|
+
};
|
|
332
|
+
const GOVERNOR_PANE_ALLOWS = {
|
|
333
|
+
"herdr tab list*": "allow",
|
|
334
|
+
"herdr pane get*": "allow",
|
|
335
|
+
"herdr pane rename*": "allow",
|
|
336
|
+
"herdr agent rename*": "allow",
|
|
337
|
+
};
|
|
338
|
+
const SHEEPDOG_PANE_ALLOWS = {
|
|
339
|
+
"herdr tab list*": "allow",
|
|
340
|
+
"herdr pane get*": "allow",
|
|
341
|
+
"herdr pane rename*": "allow",
|
|
342
|
+
"herdr agent rename*": "allow",
|
|
343
|
+
"herdr pane close*": "allow",
|
|
344
|
+
};
|
|
345
|
+
|
|
125
346
|
const SHEEPDOG_DENIALS = {
|
|
126
347
|
"git push*": "deny",
|
|
127
348
|
"git pull*": "deny",
|
|
@@ -197,11 +418,14 @@ export function createAgents(options = {}) {
|
|
|
197
418
|
const shepherdModel = options.shepherdModel;
|
|
198
419
|
const workerModel = options.workerModel ?? "litellm/glm-5.3-flash";
|
|
199
420
|
const workerVariant = options.workerVariant;
|
|
421
|
+
const grazerVariant = options.grazerVariant ?? workerVariant;
|
|
200
422
|
const sheepdogModel = options.sheepdogModel ?? "litellm/glm-5.3-flash";
|
|
201
423
|
const sheepdogVariant = options.sheepdogVariant;
|
|
202
424
|
const reviewerModel = options.reviewerModel ?? "litellm-responses/gpt-5.6-terra";
|
|
203
425
|
const shepherdPermissions = options.shepherdPermissions ?? {};
|
|
204
|
-
const shepherdPrompt = appendPrompt(SHEPHERD_PROMPT, options.shepherdPromptAppend);
|
|
426
|
+
const shepherdPrompt = appendPrompt(SHEPHERD_PROMPT, options.shepherdPromptAppend, "shepherdPromptAppend");
|
|
427
|
+
const sheepdogPermissions = options.sheepdogPermissions ?? {};
|
|
428
|
+
const sheepdogPrompt = appendPrompt(SHEEPDOG_PROMPT, options.sheepdogPromptAppend, "sheepdogPromptAppend");
|
|
205
429
|
|
|
206
430
|
return {
|
|
207
431
|
shepherd: {
|
|
@@ -217,9 +441,13 @@ export function createAgents(options = {}) {
|
|
|
217
441
|
apply_patch: markdownOnly,
|
|
218
442
|
herdr_agent_response: "allow",
|
|
219
443
|
...stateToolPermissions(SHEPHERD_STATE_TOOLS),
|
|
444
|
+
...steeringToolPermissions([]),
|
|
445
|
+
...rawSteeringToolPermissions(SHEPHERD_RAW_STEERING_TOOLS),
|
|
446
|
+
...ownershipToolPermissions(SHEPHERD_OWNERSHIP_TOOLS),
|
|
220
447
|
bash: {
|
|
221
448
|
"*": "deny",
|
|
222
449
|
...herdrInspection,
|
|
450
|
+
...SHEPHERD_PANE_ALLOWS,
|
|
223
451
|
...spawnMatrix(SHEPHERD_SPAWNABLE_AGENTS),
|
|
224
452
|
"git status*": "allow",
|
|
225
453
|
"git diff*": "allow",
|
|
@@ -260,9 +488,13 @@ export function createAgents(options = {}) {
|
|
|
260
488
|
apply_patch: markdownOnly,
|
|
261
489
|
herdr_agent_response: "allow",
|
|
262
490
|
...stateToolPermissions(GOVERNOR_STATE_TOOLS),
|
|
491
|
+
...steeringToolPermissions([]),
|
|
492
|
+
...rawSteeringToolPermissions(SHEPHERD_RAW_STEERING_TOOLS),
|
|
493
|
+
...ownershipToolPermissions(SHEPHERD_OWNERSHIP_TOOLS),
|
|
263
494
|
bash: {
|
|
264
495
|
"*": "deny",
|
|
265
496
|
...herdrInspection,
|
|
497
|
+
...GOVERNOR_PANE_ALLOWS,
|
|
266
498
|
...spawnMatrix(GOVERNOR_SPAWNABLE_AGENTS),
|
|
267
499
|
"git status*": "allow",
|
|
268
500
|
"git diff*": "allow",
|
|
@@ -302,6 +534,8 @@ export function createAgents(options = {}) {
|
|
|
302
534
|
"gh pr create*": "allow",
|
|
303
535
|
"gh pr view*": "allow",
|
|
304
536
|
"gh pr checks*": "allow",
|
|
537
|
+
...GOVERNOR_SEND_KEYS_DENY,
|
|
538
|
+
...GOVERNOR_SEND_KEYS_CTRL_C_ALLOWS,
|
|
305
539
|
...separatorDenials,
|
|
306
540
|
},
|
|
307
541
|
task: "deny",
|
|
@@ -314,7 +548,7 @@ export function createAgents(options = {}) {
|
|
|
314
548
|
...(sheepdogVariant ? { variant: sheepdogVariant } : {}),
|
|
315
549
|
description:
|
|
316
550
|
"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:
|
|
551
|
+
prompt: sheepdogPrompt,
|
|
318
552
|
permission: {
|
|
319
553
|
read: "allow",
|
|
320
554
|
glob: "allow",
|
|
@@ -325,23 +559,31 @@ export function createAgents(options = {}) {
|
|
|
325
559
|
apply_patch: "deny",
|
|
326
560
|
herdr_agent_response: "allow",
|
|
327
561
|
...stateToolPermissions(SHEEPDOG_STATE_TOOLS),
|
|
562
|
+
...steeringToolPermissions([]),
|
|
563
|
+
...rawSteeringToolPermissions([]),
|
|
564
|
+
...ownershipToolPermissions([]),
|
|
328
565
|
bash: {
|
|
329
566
|
"*": "deny",
|
|
330
567
|
...safeGitInspection,
|
|
331
568
|
...herdrInspection,
|
|
569
|
+
...SHEEPDOG_PANE_ALLOWS,
|
|
332
570
|
...spawnMatrix(SHEEPDOG_SPAWNABLE_AGENTS),
|
|
571
|
+
...SHEEPDOG_HERDR_LIFECYCLE_ALLOWS,
|
|
333
572
|
...SHEEPDOG_LIFECYCLE_ALLOWS,
|
|
334
573
|
...SHEEPDOG_DENIALS,
|
|
574
|
+
...SHEEPDOG_SEND_KEYS_DENY,
|
|
575
|
+
...SHEEPDOG_SEND_KEYS_CTRL_C_ALLOWS,
|
|
335
576
|
...separatorDenials,
|
|
336
577
|
},
|
|
337
578
|
task: "deny",
|
|
579
|
+
...sheepdogPermissions,
|
|
338
580
|
},
|
|
339
581
|
},
|
|
340
582
|
|
|
341
583
|
grazer: {
|
|
342
584
|
mode: "primary",
|
|
343
585
|
model: workerModel,
|
|
344
|
-
...(
|
|
586
|
+
...(grazerVariant ? { variant: grazerVariant } : {}),
|
|
345
587
|
description: "Performs read-only repository research for the shepherd, shepherd-governor, or sheepdog.",
|
|
346
588
|
prompt: GRAZER_PROMPT,
|
|
347
589
|
permission: {
|
|
@@ -354,6 +596,9 @@ export function createAgents(options = {}) {
|
|
|
354
596
|
apply_patch: "deny",
|
|
355
597
|
herdr_agent_response: "deny",
|
|
356
598
|
...stateToolPermissions([]),
|
|
599
|
+
...steeringToolPermissions([]),
|
|
600
|
+
...rawSteeringToolPermissions([]),
|
|
601
|
+
...ownershipToolPermissions([]),
|
|
357
602
|
bash: { "*": "deny", ...safeGitInspection, ...separatorDenials },
|
|
358
603
|
task: "deny",
|
|
359
604
|
},
|
|
@@ -370,6 +615,9 @@ export function createAgents(options = {}) {
|
|
|
370
615
|
grep: "allow",
|
|
371
616
|
herdr_agent_response: "deny",
|
|
372
617
|
...stateToolPermissions([]),
|
|
618
|
+
...steeringToolPermissions([]),
|
|
619
|
+
...rawSteeringToolPermissions([]),
|
|
620
|
+
...ownershipToolPermissions([]),
|
|
373
621
|
bash: {
|
|
374
622
|
"*": "allow",
|
|
375
623
|
...SHEEP_DENIALS,
|
|
@@ -384,10 +632,10 @@ export function createAgents(options = {}) {
|
|
|
384
632
|
};
|
|
385
633
|
}
|
|
386
634
|
|
|
387
|
-
function appendPrompt(prompt, addition) {
|
|
635
|
+
function appendPrompt(prompt, addition, optionName = "shepherdPromptAppend") {
|
|
388
636
|
if (addition === undefined || addition === "") return prompt;
|
|
389
637
|
if (typeof addition !== "string") {
|
|
390
|
-
throw new TypeError(
|
|
638
|
+
throw new TypeError(`${optionName} must be a string.`);
|
|
391
639
|
}
|
|
392
640
|
return `${prompt.trimEnd()}\n\n${addition.trim()}`;
|
|
393
641
|
}
|
|
@@ -409,6 +657,9 @@ function reviewerAgent(model, variant) {
|
|
|
409
657
|
todowrite: "deny",
|
|
410
658
|
herdr_agent_response: "deny",
|
|
411
659
|
...stateToolPermissions([]),
|
|
660
|
+
...steeringToolPermissions([]),
|
|
661
|
+
...rawSteeringToolPermissions([]),
|
|
662
|
+
...ownershipToolPermissions([]),
|
|
412
663
|
bash: { "*": "deny", ...safeGitInspection, ...separatorDenials },
|
|
413
664
|
task: "deny",
|
|
414
665
|
},
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// 20-M2 operational diagnostics log (diagnostics only, never results).
|
|
2
|
+
// Strategy: process-local bounded in-memory ring buffer with no filesystem,
|
|
3
|
+
// no Git, no Herdr commands, no plugin tools, and no persistence. Entries are
|
|
4
|
+
// ephemeral like SHEPHERD_MODE and response cursors: they do not survive a
|
|
5
|
+
// plugin restart and must never substitute for authoritative retrieval via
|
|
6
|
+
// herdr_agent_response until complete is true.
|
|
7
|
+
// Guardrails: no chain of thought, no transcripts, no scrollback, bounded
|
|
8
|
+
// size per field, bounded retention per log, never read as results.
|
|
9
|
+
export const DIAGNOSTIC_EVENT_TYPES = Object.freeze([
|
|
10
|
+
"worker-started",
|
|
11
|
+
"prompt-submitted",
|
|
12
|
+
"state-changed",
|
|
13
|
+
"command-failed",
|
|
14
|
+
"settled",
|
|
15
|
+
"disappeared",
|
|
16
|
+
"timed-out",
|
|
17
|
+
"recovery-started",
|
|
18
|
+
]);
|
|
19
|
+
const DIAGNOSTIC_EVENT_SET = new Set(DIAGNOSTIC_EVENT_TYPES);
|
|
20
|
+
export const MAX_DIAGNOSTIC_EVENTS_DEFAULT = 100;
|
|
21
|
+
export const MAX_DIAGNOSTIC_EVENTS_LIMIT = 1000;
|
|
22
|
+
export const MAX_DIAGNOSTIC_TARGET_CHARS = 64;
|
|
23
|
+
export const MAX_DIAGNOSTIC_CODE_CHARS = 64;
|
|
24
|
+
export const MAX_DIAGNOSTIC_DETAIL_CHARS = 512;
|
|
25
|
+
const SENSITIVE_DIAGNOSTIC_PATTERN = /(transcript|scrollback|chain\s*of\s*thought)/i;
|
|
26
|
+
function diagnosticError(code, message) {
|
|
27
|
+
return { ok: false, error: { code, message, retryable: false } };
|
|
28
|
+
}
|
|
29
|
+
function validateDiagnosticType(type) {
|
|
30
|
+
if (typeof type !== "string" || !DIAGNOSTIC_EVENT_SET.has(type)) {
|
|
31
|
+
return diagnosticError(
|
|
32
|
+
"INVALID_DIAGNOSTIC_TYPE",
|
|
33
|
+
`Diagnostic type must be one of: ${DIAGNOSTIC_EVENT_TYPES.join(", ")}.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function validateDiagnosticTarget(target) {
|
|
39
|
+
if (typeof target !== "string" || target.length === 0 || target.length > MAX_DIAGNOSTIC_TARGET_CHARS) {
|
|
40
|
+
return diagnosticError(
|
|
41
|
+
"INVALID_DIAGNOSTIC_TARGET",
|
|
42
|
+
`Diagnostic target must be a non-empty string of at most ${MAX_DIAGNOSTIC_TARGET_CHARS} characters.`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function validateDiagnosticCode(code) {
|
|
48
|
+
if (code === undefined) return null;
|
|
49
|
+
if (typeof code !== "string" || code.length === 0 || code.length > MAX_DIAGNOSTIC_CODE_CHARS) {
|
|
50
|
+
return diagnosticError(
|
|
51
|
+
"INVALID_DIAGNOSTIC_CODE",
|
|
52
|
+
`Diagnostic code must be a non-empty string of at most ${MAX_DIAGNOSTIC_CODE_CHARS} characters.`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (SENSITIVE_DIAGNOSTIC_PATTERN.test(code)) {
|
|
56
|
+
return diagnosticError(
|
|
57
|
+
"SENSITIVE_CONTENT_EXCLUDED",
|
|
58
|
+
"Diagnostic code must not contain transcript, scrollback, or chain of thought content.",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function validateDiagnosticDetail(detail) {
|
|
64
|
+
if (detail === undefined) return null;
|
|
65
|
+
if (typeof detail !== "string" || detail.length > MAX_DIAGNOSTIC_DETAIL_CHARS) {
|
|
66
|
+
return diagnosticError(
|
|
67
|
+
"INVALID_DIAGNOSTIC_DETAIL",
|
|
68
|
+
`Diagnostic detail must be a string of at most ${MAX_DIAGNOSTIC_DETAIL_CHARS} characters; response text is never stored.`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (SENSITIVE_DIAGNOSTIC_PATTERN.test(detail)) {
|
|
72
|
+
return diagnosticError(
|
|
73
|
+
"SENSITIVE_CONTENT_EXCLUDED",
|
|
74
|
+
"Diagnostic detail must not contain transcript, scrollback, or chain of thought content; store only bounded operational summaries.",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
export function createDiagnosticsLog(options = {}) {
|
|
80
|
+
const maxEvents = options.maxEvents ?? MAX_DIAGNOSTIC_EVENTS_DEFAULT;
|
|
81
|
+
if (!Number.isSafeInteger(maxEvents) || maxEvents < 1 || maxEvents > MAX_DIAGNOSTIC_EVENTS_LIMIT) {
|
|
82
|
+
throw new TypeError(`maxEvents must be an integer from 1 through ${MAX_DIAGNOSTIC_EVENTS_LIMIT}.`);
|
|
83
|
+
}
|
|
84
|
+
const now = options.now ?? Date.now;
|
|
85
|
+
let sequence = 0;
|
|
86
|
+
const events = [];
|
|
87
|
+
function record(type, fields = {}) {
|
|
88
|
+
const typeFailure = validateDiagnosticType(type);
|
|
89
|
+
if (typeFailure) return typeFailure;
|
|
90
|
+
const targetFailure = validateDiagnosticTarget(fields.target);
|
|
91
|
+
if (targetFailure) return targetFailure;
|
|
92
|
+
const codeFailure = validateDiagnosticCode(fields.code);
|
|
93
|
+
if (codeFailure) return codeFailure;
|
|
94
|
+
const detailFailure = validateDiagnosticDetail(fields.detail);
|
|
95
|
+
if (detailFailure) return detailFailure;
|
|
96
|
+
sequence += 1;
|
|
97
|
+
const event = {
|
|
98
|
+
sequence,
|
|
99
|
+
type,
|
|
100
|
+
target: fields.target,
|
|
101
|
+
...(fields.code === undefined ? {} : { code: fields.code }),
|
|
102
|
+
...(fields.detail === undefined ? {} : { detail: fields.detail }),
|
|
103
|
+
at: new Date(now()).toISOString(),
|
|
104
|
+
};
|
|
105
|
+
events.push(event);
|
|
106
|
+
while (events.length > maxEvents) events.shift();
|
|
107
|
+
return { ok: true, event: { ...event } };
|
|
108
|
+
}
|
|
109
|
+
function list() {
|
|
110
|
+
return { ok: true, events: events.map((event) => ({ ...event })), dropped: sequence - events.length, maxEvents };
|
|
111
|
+
}
|
|
112
|
+
function clear() {
|
|
113
|
+
events.length = 0;
|
|
114
|
+
return { ok: true, dropped: sequence, maxEvents };
|
|
115
|
+
}
|
|
116
|
+
return { record, list, clear, maxEvents };
|
|
117
|
+
}
|