opencode-herdr-orchestration 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +312 -5
- package/bin/orchestration.js +6 -1
- package/package.json +4 -3
- package/src/agents.js +275 -4
- package/src/diagnostics.js +117 -0
- package/src/index.js +312 -36
- package/src/installer.js +268 -1
- package/src/prompts.js +213 -6
- package/src/response.js +71 -2
- package/src/state.js +2252 -11
- package/src/steer.js +124 -0
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,81 @@ 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 plus the /steer hook in src/steer.js
|
|
109
|
+
// stays authoritative over these static entries: even a user override
|
|
110
|
+
// flipping one to "allow" must not bypass the allowlist. No flock role may
|
|
111
|
+
// present as Developer: the developer profile below holds only the submit
|
|
112
|
+
// tool and no spawn matrix entry creates or targets it.
|
|
113
|
+
export const DEVELOPER_AGENT = "developer";
|
|
114
|
+
export const DEVELOPER_PROMPT = String.raw`
|
|
115
|
+
You are developer, the trusted steering submitter. Submit bounded steering via /steer as <content> or <planId> :: <content>; omit planId only when exactly one active steering target exists. You hold only herdr_steering_submit; you never implement flock work, never spawn workers, and never read raw steering.
|
|
116
|
+
`.trim();
|
|
117
|
+
export const ORCHESTRATION_ROLES = Object.freeze([
|
|
118
|
+
"shepherd",
|
|
119
|
+
"shepherd-governor",
|
|
120
|
+
"sheepdog",
|
|
121
|
+
"grazer",
|
|
122
|
+
"sheep",
|
|
123
|
+
"shearer-low",
|
|
124
|
+
"shearer-medium",
|
|
125
|
+
]);
|
|
126
|
+
export const STEERING_TOOLS = Object.freeze({
|
|
127
|
+
submit: "herdr_steering_submit",
|
|
128
|
+
});
|
|
129
|
+
export const STEERING_TOOL_ACCESS = Object.freeze(
|
|
130
|
+
new Map([[STEERING_TOOLS.submit, new Set([DEVELOPER_AGENT])]]),
|
|
131
|
+
);
|
|
132
|
+
const ALL_STEERING_TOOLS = Object.freeze(Object.values(STEERING_TOOLS));
|
|
133
|
+
|
|
134
|
+
function steeringToolPermissions(allowedTools) {
|
|
135
|
+
return Object.fromEntries(
|
|
136
|
+
ALL_STEERING_TOOLS.map((name) => [name, allowedTools.includes(name) ? "allow" : "deny"]),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Shepherd ownership raw steering plus lifecycle tools (M3). Only the two
|
|
141
|
+
// Shepherd phases may check, read, or consume raw steering and only the
|
|
142
|
+
// recorded owner phase plus session plus generation passes the state-level
|
|
143
|
+
// fencing (NOT AUTHORITATIVE PHASE otherwise). Sheepdog and every leaf are
|
|
144
|
+
// explicitly denied in code, not only in prompts; the runtime check in
|
|
145
|
+
// src/index.js stays authoritative over static overrides.
|
|
146
|
+
export const SHEPHERD_PHASES = Object.freeze(["shepherd", "shepherd-governor"]);
|
|
147
|
+
export const RAW_STEERING_TOOLS = Object.freeze({
|
|
148
|
+
check: "herdr_steering_check",
|
|
149
|
+
read: "herdr_steering_read",
|
|
150
|
+
consume: "herdr_steering_consume",
|
|
151
|
+
});
|
|
152
|
+
export const OWNERSHIP_TOOLS = Object.freeze({
|
|
153
|
+
claim: "herdr_ownership_claim",
|
|
154
|
+
read: "herdr_ownership_read",
|
|
155
|
+
sync: "herdr_ownership_sync",
|
|
156
|
+
snapshot: "herdr_ownership_snapshot",
|
|
157
|
+
correct: "herdr_ownership_correct",
|
|
158
|
+
});
|
|
159
|
+
export const RAW_STEERING_TOOL_ACCESS = Object.freeze(
|
|
160
|
+
new Map(Object.values(RAW_STEERING_TOOLS).map((name) => [name, new Set(SHEPHERD_PHASES)])),
|
|
161
|
+
);
|
|
162
|
+
export const OWNERSHIP_TOOL_ACCESS = Object.freeze(
|
|
163
|
+
new Map(Object.values(OWNERSHIP_TOOLS).map((name) => [name, new Set(SHEPHERD_PHASES)])),
|
|
164
|
+
);
|
|
165
|
+
const ALL_RAW_STEERING_TOOLS = Object.freeze(Object.values(RAW_STEERING_TOOLS));
|
|
166
|
+
const ALL_OWNERSHIP_TOOLS = Object.freeze(Object.values(OWNERSHIP_TOOLS));
|
|
167
|
+
|
|
168
|
+
function rawSteeringToolPermissions(allowedTools) {
|
|
169
|
+
return Object.fromEntries(
|
|
170
|
+
ALL_RAW_STEERING_TOOLS.map((name) => [name, allowedTools.includes(name) ? "allow" : "deny"]),
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function ownershipToolPermissions(allowedTools) {
|
|
175
|
+
return Object.fromEntries(
|
|
176
|
+
ALL_OWNERSHIP_TOOLS.map((name) => [name, allowedTools.includes(name) ? "allow" : "deny"]),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
99
180
|
const SHEPHERD_STATE_TOOLS = [STATE_TOOLS.planWrite, STATE_TOOLS.planRead];
|
|
100
181
|
const GOVERNOR_STATE_TOOLS = [STATE_TOOLS.planRead];
|
|
101
182
|
const SHEEPDOG_STATE_TOOLS = [
|
|
@@ -103,6 +184,8 @@ const SHEEPDOG_STATE_TOOLS = [
|
|
|
103
184
|
STATE_TOOLS.executionWrite,
|
|
104
185
|
STATE_TOOLS.executionRead,
|
|
105
186
|
];
|
|
187
|
+
const SHEPHERD_RAW_STEERING_TOOLS = Object.values(RAW_STEERING_TOOLS);
|
|
188
|
+
const SHEPHERD_OWNERSHIP_TOOLS = Object.values(OWNERSHIP_TOOLS);
|
|
106
189
|
|
|
107
190
|
const SHEPHERD_SPAWNABLE_AGENTS = ["grazer"];
|
|
108
191
|
const GOVERNOR_SPAWNABLE_AGENTS = ["grazer", "sheepdog"];
|
|
@@ -122,6 +205,148 @@ const SHEEPDOG_LIFECYCLE_ALLOWS = {
|
|
|
122
205
|
"git commit*": "allow",
|
|
123
206
|
};
|
|
124
207
|
|
|
208
|
+
// Sheepdog Herdr lifecycle (21-M1): explicit prompt plus wait plus get plus
|
|
209
|
+
// read allows. These re-assert the shared herdrInspection entries so the four
|
|
210
|
+
// lifecycle operations stay evaluation-effective under OpenCode last-match
|
|
211
|
+
// glob semantics: "*" deny is the fallback first, these allows sit in the
|
|
212
|
+
// middle, separator denies stay global last. A prompt whose task text carries
|
|
213
|
+
// raw "; && || | > <" matches a separator deny after the prompt allow and
|
|
214
|
+
// fails closed to deny even inside quotes, so task text must avoid raw
|
|
215
|
+
// separators (see SHEEPDOG_PROMPT safe rule).
|
|
216
|
+
const SHEEPDOG_HERDR_LIFECYCLE_ALLOWS = {
|
|
217
|
+
"herdr agent prompt*": "allow",
|
|
218
|
+
"herdr agent wait*": "allow",
|
|
219
|
+
"herdr agent get*": "allow",
|
|
220
|
+
"herdr agent read*": "allow",
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Sheepdog interrupt bound (21-M1): replacement for the broad
|
|
224
|
+
// "herdr agent send-keys*" allow in shared herdrInspection. The broad pattern
|
|
225
|
+
// is explicitly denied first so only the narrow Ctrl-C spellings below are
|
|
226
|
+
// evaluation-effective. Matchers are string globs and cannot prove semantic
|
|
227
|
+
// intent, so the prompt inspect-first plus never-type rule stays primary; see
|
|
228
|
+
// SHEEPDOG_PROMPT and README Worker interruption residual.
|
|
229
|
+
const SHEEPDOG_SEND_KEYS_DENY = {
|
|
230
|
+
"herdr agent send-keys*": "deny",
|
|
231
|
+
};
|
|
232
|
+
const SHEEPDOG_SEND_KEYS_CTRL_C_ALLOWS = {
|
|
233
|
+
"herdr agent send-keys * --keys C-c*": "allow",
|
|
234
|
+
"herdr agent send-keys * --keys c-c*": "allow",
|
|
235
|
+
"herdr agent send-keys * --keys ctrl+c*": "allow",
|
|
236
|
+
"herdr agent send-keys * --keys Ctrl+C*": "allow",
|
|
237
|
+
"herdr agent send-keys * C-c*": "allow",
|
|
238
|
+
"herdr agent send-keys * ctrl+c*": "allow",
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Governor Herdr prompt residual (21-M2): `herdr agent prompt` plus wait plus
|
|
242
|
+
// get plus read patterns are name-based and cannot encode worker role, so they
|
|
243
|
+
// stay broad as far as text matchers permit. Prompt bans plus start denial
|
|
244
|
+
// (spawn matrix allows only grazer and sheepdog) plus the response matrix
|
|
245
|
+
// (retrieval allows only grazer and sheepdog) are the load-bearing layers;
|
|
246
|
+
// see SHEPHERD_GOVERNOR_PROMPT and README Governor prompt scoping residual.
|
|
247
|
+
// Governor interrupt bound (21-M2): replacement for the broad
|
|
248
|
+
// "herdr agent send-keys*" allow in shared herdrInspection, mirroring the
|
|
249
|
+
// sheepdog M1 bound. The broad pattern is explicitly denied first so only the
|
|
250
|
+
// narrow Ctrl-C spellings below are evaluation-effective. Matchers are string
|
|
251
|
+
// globs and cannot prove semantic intent, so the prompt inspect-first plus
|
|
252
|
+
// never-type plus never-bypass rule stays primary.
|
|
253
|
+
const GOVERNOR_SEND_KEYS_DENY = {
|
|
254
|
+
"herdr agent send-keys*": "deny",
|
|
255
|
+
};
|
|
256
|
+
const GOVERNOR_SEND_KEYS_CTRL_C_ALLOWS = {
|
|
257
|
+
"herdr agent send-keys * --keys C-c*": "allow",
|
|
258
|
+
"herdr agent send-keys * --keys c-c*": "allow",
|
|
259
|
+
"herdr agent send-keys * --keys ctrl+c*": "allow",
|
|
260
|
+
"herdr agent send-keys * --keys Ctrl+C*": "allow",
|
|
261
|
+
"herdr agent send-keys * C-c*": "allow",
|
|
262
|
+
"herdr agent send-keys * ctrl+c*": "allow",
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Pane layout (14-18-M1): live discovery on installed 0.8.2 via full-path
|
|
266
|
+
// `herdr --help` plus `herdr --skill` plus scoped read-only queries
|
|
267
|
+
// (`pane list --workspace w1K`, `tab list --workspace w1K`,
|
|
268
|
+
// `pane current --current`, `pane layout --current`, `agent get <name>`,
|
|
269
|
+
// `pane get w1K:p999` error sampling). Direct `herdr ...` spelling stays
|
|
270
|
+
// denied for leaves; discovery used only read-only help plus list plus get
|
|
271
|
+
// plus current plus layout, never split plus rename plus close plus start.
|
|
272
|
+
// Tab evidenced as list plus create plus get plus focus plus rename plus
|
|
273
|
+
// close; list takes `--workspace <ID>`, create takes
|
|
274
|
+
// `--workspace/--cwd/--label/--env/--focus/--no-focus`, rename takes
|
|
275
|
+
// `<TAB_ID> <LABEL>...`, close plus get plus focus take `<tab_id>`.
|
|
276
|
+
// Pane placement evidenced as split plus move plus focus plus resize plus
|
|
277
|
+
// swap plus layout plus get plus list plus current plus read plus rename plus
|
|
278
|
+
// close; split is `[PANE_ID] --pane/--current --direction right/down --ratio
|
|
279
|
+
// --cwd --env --focus/--no-focus`, move is `<PANE_ID>
|
|
280
|
+
// --tab/--split/--target-pane/--ratio/--new-tab/--workspace/--new-workspace/--label/--tab-label/--focus/--no-focus`,
|
|
281
|
+
// focus and neighbor take `--direction left/right/up/down --pane/--current`,
|
|
282
|
+
// resize takes `--direction --amount --pane/--current`, swap takes
|
|
283
|
+
// `--direction/--pane/--current/--source-pane/--target-pane`, layout takes
|
|
284
|
+
// `--pane/--current`, get takes `<pane_id>`, list takes
|
|
285
|
+
// `--workspace <ID>`, current takes `--pane/--current`, read takes
|
|
286
|
+
// `<PANE_ID> --source visible/recent/recent-unwrapped/detection
|
|
287
|
+
// --lines/--format/--ansi/--raw`, rename takes `<PANE_ID> [LABEL]...
|
|
288
|
+
// --clear`, close takes `<pane_id>`.
|
|
289
|
+
// Agent rename evidenced as `herdr agent rename <TARGET> <NAME>|--clear`
|
|
290
|
+
// (live `agent list` shows `name` such as `issue1418m1sheep` and `agent get`
|
|
291
|
+
// shows `name` plus `pane_id`); pane rename plus tab rename helps evidenced
|
|
292
|
+
// but only pane plus agent rename are enabled, tab rename stays denied to
|
|
293
|
+
// keep the single-tab 4-pane cap.
|
|
294
|
+
// Count queries evidenced as `herdr tab list --workspace <ID>` returning
|
|
295
|
+
// `{"id":"cli:tab:list","result":{"tabs":[{"pane_count":1,"tab_id":"w1K:t1",...}]},"type":"tab_list"}`,
|
|
296
|
+
// `herdr pane list --workspace <ID>` returning
|
|
297
|
+
// `{"id":"cli:pane:list","result":{"panes":[{...,"pane_id":"w1K:p1","tab_id":"w1K:t1","workspace_id":"w1K",...}]},"type":"pane_list"}`
|
|
298
|
+
// (count is length filtered by workspace plus tab), `herdr pane layout
|
|
299
|
+
// --current` returning
|
|
300
|
+
// `{"id":"cli:pane:layout","result":{"layout":{"panes":[...],"splits":[],"focused_pane_id":"w1K:p1",...}},"type":"pane_layout"}`,
|
|
301
|
+
// `herdr pane current --current` returning
|
|
302
|
+
// `{"id":"cli:pane:current","result":{"pane":{...}},"type":"pane_current"}`.
|
|
303
|
+
// Creation JSON per `herdr --skill`: `tab create` returns
|
|
304
|
+
// `.result.tab` plus `.result.root_pane`, `pane split` returns the new pane
|
|
305
|
+
// as `.result.pane` (skill geometry is `pane split --current --direction
|
|
306
|
+
// right/down --cwd "$PWD" --no-focus`, wide to the right and narrow or tall
|
|
307
|
+
// down, reading `.result.pane.pane_id` and never deriving from sidebar
|
|
308
|
+
// order), `pane move` would return
|
|
309
|
+
// `.result.move_result.pane.pane_id` plus `.result.move_result.previous_pane_id`
|
|
310
|
+
// but move stays denied and is documented only.
|
|
311
|
+
// Exits per `herdr --skill` plus live sampling: most controls return JSON on
|
|
312
|
+
// stdout with `id` plus `result` plus `type`; server errors are JSON such as
|
|
313
|
+
// `{"error":{"code":"pane_not_found","message":"pane w1K:p999 not found"},"id":"cli:pane:get"}`
|
|
314
|
+
// on stderr with exit 1; syntax plus validation such as `pane split
|
|
315
|
+
// --direction invalid` returning `invalid split direction` exits with 2.
|
|
316
|
+
// IDs are opaque stable handles (`w1K`, `w1K:t1`, `w1K:p1` from
|
|
317
|
+
// `HERDR_WORKSPACE_ID` plus `HERDR_TAB_ID` plus `HERDR_PANE_ID`); closed IDs
|
|
318
|
+
// are not reused; prefer `--current` and never rely on the UI-focused pane.
|
|
319
|
+
// Fallback if any of tab list plus pane get plus rename plus close plus
|
|
320
|
+
// split are missing: reuse the current pane via `--pane` plus `--current`,
|
|
321
|
+
// report STOP naming the missing capability, never invent `herdr pane
|
|
322
|
+
// create*` plus `herdr tab split*` plus `herdr agent events*` plus
|
|
323
|
+
// `herdr pane move*` plus `herdr pane resize*` plus `herdr workspace
|
|
324
|
+
// create*` behavior.
|
|
325
|
+
// Protected Dev Developer Terminal exclusion cannot be matcher-enforced:
|
|
326
|
+
// pane IDs are opaque and labels are absent from scan plus split plus close
|
|
327
|
+
// command strings, while a `*Dev*` glob would overmatch legitimate
|
|
328
|
+
// `--cwd C:\Dev\...` values, so no such glob is added; the prompt plus
|
|
329
|
+
// README Pane layout policy exclusion stays primary; see README residual.
|
|
330
|
+
const SHEPHERD_PANE_ALLOWS = {
|
|
331
|
+
"herdr tab list*": "allow",
|
|
332
|
+
"herdr pane get*": "allow",
|
|
333
|
+
"herdr pane rename*": "allow",
|
|
334
|
+
"herdr agent rename*": "allow",
|
|
335
|
+
};
|
|
336
|
+
const GOVERNOR_PANE_ALLOWS = {
|
|
337
|
+
"herdr tab list*": "allow",
|
|
338
|
+
"herdr pane get*": "allow",
|
|
339
|
+
"herdr pane rename*": "allow",
|
|
340
|
+
"herdr agent rename*": "allow",
|
|
341
|
+
};
|
|
342
|
+
const SHEEPDOG_PANE_ALLOWS = {
|
|
343
|
+
"herdr tab list*": "allow",
|
|
344
|
+
"herdr pane get*": "allow",
|
|
345
|
+
"herdr pane rename*": "allow",
|
|
346
|
+
"herdr agent rename*": "allow",
|
|
347
|
+
"herdr pane close*": "allow",
|
|
348
|
+
};
|
|
349
|
+
|
|
125
350
|
const SHEEPDOG_DENIALS = {
|
|
126
351
|
"git push*": "deny",
|
|
127
352
|
"git pull*": "deny",
|
|
@@ -202,7 +427,9 @@ export function createAgents(options = {}) {
|
|
|
202
427
|
const sheepdogVariant = options.sheepdogVariant;
|
|
203
428
|
const reviewerModel = options.reviewerModel ?? "litellm-responses/gpt-5.6-terra";
|
|
204
429
|
const shepherdPermissions = options.shepherdPermissions ?? {};
|
|
205
|
-
const shepherdPrompt = appendPrompt(SHEPHERD_PROMPT, options.shepherdPromptAppend);
|
|
430
|
+
const shepherdPrompt = appendPrompt(SHEPHERD_PROMPT, options.shepherdPromptAppend, "shepherdPromptAppend");
|
|
431
|
+
const sheepdogPermissions = options.sheepdogPermissions ?? {};
|
|
432
|
+
const sheepdogPrompt = appendPrompt(SHEEPDOG_PROMPT, options.sheepdogPromptAppend, "sheepdogPromptAppend");
|
|
206
433
|
|
|
207
434
|
return {
|
|
208
435
|
shepherd: {
|
|
@@ -218,9 +445,13 @@ export function createAgents(options = {}) {
|
|
|
218
445
|
apply_patch: markdownOnly,
|
|
219
446
|
herdr_agent_response: "allow",
|
|
220
447
|
...stateToolPermissions(SHEPHERD_STATE_TOOLS),
|
|
448
|
+
...steeringToolPermissions([]),
|
|
449
|
+
...rawSteeringToolPermissions(SHEPHERD_RAW_STEERING_TOOLS),
|
|
450
|
+
...ownershipToolPermissions(SHEPHERD_OWNERSHIP_TOOLS),
|
|
221
451
|
bash: {
|
|
222
452
|
"*": "deny",
|
|
223
453
|
...herdrInspection,
|
|
454
|
+
...SHEPHERD_PANE_ALLOWS,
|
|
224
455
|
...spawnMatrix(SHEPHERD_SPAWNABLE_AGENTS),
|
|
225
456
|
"git status*": "allow",
|
|
226
457
|
"git diff*": "allow",
|
|
@@ -261,9 +492,13 @@ export function createAgents(options = {}) {
|
|
|
261
492
|
apply_patch: markdownOnly,
|
|
262
493
|
herdr_agent_response: "allow",
|
|
263
494
|
...stateToolPermissions(GOVERNOR_STATE_TOOLS),
|
|
495
|
+
...steeringToolPermissions([]),
|
|
496
|
+
...rawSteeringToolPermissions(SHEPHERD_RAW_STEERING_TOOLS),
|
|
497
|
+
...ownershipToolPermissions(SHEPHERD_OWNERSHIP_TOOLS),
|
|
264
498
|
bash: {
|
|
265
499
|
"*": "deny",
|
|
266
500
|
...herdrInspection,
|
|
501
|
+
...GOVERNOR_PANE_ALLOWS,
|
|
267
502
|
...spawnMatrix(GOVERNOR_SPAWNABLE_AGENTS),
|
|
268
503
|
"git status*": "allow",
|
|
269
504
|
"git diff*": "allow",
|
|
@@ -303,6 +538,8 @@ export function createAgents(options = {}) {
|
|
|
303
538
|
"gh pr create*": "allow",
|
|
304
539
|
"gh pr view*": "allow",
|
|
305
540
|
"gh pr checks*": "allow",
|
|
541
|
+
...GOVERNOR_SEND_KEYS_DENY,
|
|
542
|
+
...GOVERNOR_SEND_KEYS_CTRL_C_ALLOWS,
|
|
306
543
|
...separatorDenials,
|
|
307
544
|
},
|
|
308
545
|
task: "deny",
|
|
@@ -315,7 +552,7 @@ export function createAgents(options = {}) {
|
|
|
315
552
|
...(sheepdogVariant ? { variant: sheepdogVariant } : {}),
|
|
316
553
|
description:
|
|
317
554
|
"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.",
|
|
318
|
-
prompt:
|
|
555
|
+
prompt: sheepdogPrompt,
|
|
319
556
|
permission: {
|
|
320
557
|
read: "allow",
|
|
321
558
|
glob: "allow",
|
|
@@ -326,16 +563,24 @@ export function createAgents(options = {}) {
|
|
|
326
563
|
apply_patch: "deny",
|
|
327
564
|
herdr_agent_response: "allow",
|
|
328
565
|
...stateToolPermissions(SHEEPDOG_STATE_TOOLS),
|
|
566
|
+
...steeringToolPermissions([]),
|
|
567
|
+
...rawSteeringToolPermissions([]),
|
|
568
|
+
...ownershipToolPermissions([]),
|
|
329
569
|
bash: {
|
|
330
570
|
"*": "deny",
|
|
331
571
|
...safeGitInspection,
|
|
332
572
|
...herdrInspection,
|
|
573
|
+
...SHEEPDOG_PANE_ALLOWS,
|
|
333
574
|
...spawnMatrix(SHEEPDOG_SPAWNABLE_AGENTS),
|
|
575
|
+
...SHEEPDOG_HERDR_LIFECYCLE_ALLOWS,
|
|
334
576
|
...SHEEPDOG_LIFECYCLE_ALLOWS,
|
|
335
577
|
...SHEEPDOG_DENIALS,
|
|
578
|
+
...SHEEPDOG_SEND_KEYS_DENY,
|
|
579
|
+
...SHEEPDOG_SEND_KEYS_CTRL_C_ALLOWS,
|
|
336
580
|
...separatorDenials,
|
|
337
581
|
},
|
|
338
582
|
task: "deny",
|
|
583
|
+
...sheepdogPermissions,
|
|
339
584
|
},
|
|
340
585
|
},
|
|
341
586
|
|
|
@@ -355,6 +600,9 @@ export function createAgents(options = {}) {
|
|
|
355
600
|
apply_patch: "deny",
|
|
356
601
|
herdr_agent_response: "deny",
|
|
357
602
|
...stateToolPermissions([]),
|
|
603
|
+
...steeringToolPermissions([]),
|
|
604
|
+
...rawSteeringToolPermissions([]),
|
|
605
|
+
...ownershipToolPermissions([]),
|
|
358
606
|
bash: { "*": "deny", ...safeGitInspection, ...separatorDenials },
|
|
359
607
|
task: "deny",
|
|
360
608
|
},
|
|
@@ -371,6 +619,9 @@ export function createAgents(options = {}) {
|
|
|
371
619
|
grep: "allow",
|
|
372
620
|
herdr_agent_response: "deny",
|
|
373
621
|
...stateToolPermissions([]),
|
|
622
|
+
...steeringToolPermissions([]),
|
|
623
|
+
...rawSteeringToolPermissions([]),
|
|
624
|
+
...ownershipToolPermissions([]),
|
|
374
625
|
bash: {
|
|
375
626
|
"*": "allow",
|
|
376
627
|
...SHEEP_DENIALS,
|
|
@@ -382,13 +633,30 @@ export function createAgents(options = {}) {
|
|
|
382
633
|
|
|
383
634
|
"shearer-low": reviewerAgent(reviewerModel, "low"),
|
|
384
635
|
"shearer-medium": reviewerAgent(reviewerModel, "medium"),
|
|
636
|
+
|
|
637
|
+
developer: {
|
|
638
|
+
mode: "primary",
|
|
639
|
+
description: "Trusted Developer steering submitter; submits bounded steering via /steer; never implements flock work.",
|
|
640
|
+
prompt: DEVELOPER_PROMPT,
|
|
641
|
+
permission: {
|
|
642
|
+
...stateToolPermissions([]),
|
|
643
|
+
...steeringToolPermissions([STEERING_TOOLS.submit]),
|
|
644
|
+
...rawSteeringToolPermissions([]),
|
|
645
|
+
...ownershipToolPermissions([]),
|
|
646
|
+
herdr_agent_response: "deny",
|
|
647
|
+
task: "deny",
|
|
648
|
+
edit: "deny",
|
|
649
|
+
apply_patch: "deny",
|
|
650
|
+
bash: { "*": "deny", ...separatorDenials },
|
|
651
|
+
},
|
|
652
|
+
},
|
|
385
653
|
};
|
|
386
654
|
}
|
|
387
655
|
|
|
388
|
-
function appendPrompt(prompt, addition) {
|
|
656
|
+
function appendPrompt(prompt, addition, optionName = "shepherdPromptAppend") {
|
|
389
657
|
if (addition === undefined || addition === "") return prompt;
|
|
390
658
|
if (typeof addition !== "string") {
|
|
391
|
-
throw new TypeError(
|
|
659
|
+
throw new TypeError(`${optionName} must be a string.`);
|
|
392
660
|
}
|
|
393
661
|
return `${prompt.trimEnd()}\n\n${addition.trim()}`;
|
|
394
662
|
}
|
|
@@ -410,6 +678,9 @@ function reviewerAgent(model, variant) {
|
|
|
410
678
|
todowrite: "deny",
|
|
411
679
|
herdr_agent_response: "deny",
|
|
412
680
|
...stateToolPermissions([]),
|
|
681
|
+
...steeringToolPermissions([]),
|
|
682
|
+
...rawSteeringToolPermissions([]),
|
|
683
|
+
...ownershipToolPermissions([]),
|
|
413
684
|
bash: { "*": "deny", ...safeGitInspection, ...separatorDenials },
|
|
414
685
|
task: "deny",
|
|
415
686
|
},
|
|
@@ -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
|
+
}
|