@kisev/agentomatic 11.0.2 → 12.0.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 +32 -16
- package/README.ru.md +33 -16
- package/dist/agent-profiles.d.ts +8 -2
- package/dist/agent-profiles.js +131 -82
- package/dist/assets/agents/architect.md +4 -4
- package/dist/assets/agents/critic.md +10 -11
- package/dist/assets/agents/manager.md +11 -12
- package/dist/assets/agents/mapper.md +4 -4
- package/dist/assets/agents/review.md +11 -12
- package/dist/assets/agents/worker.md +29 -32
- package/dist/assets/commands/asd-ste100.md +10 -0
- package/dist/assets/commands/code-simplify.md +12 -0
- package/dist/assets/commands/eli5.md +10 -0
- package/dist/assets/commands/handoff.md +10 -0
- package/dist/assets/commands/rtk-stats.md +2 -2
- package/dist/assets/commands/skill-doctor.md +10 -0
- package/dist/assets/migration-inventory.json +17 -6
- package/dist/assets/plugins/code-simplify.js +3 -0
- package/dist/assets/plugins/rtk.js +1 -1
- package/dist/assets/plugins/rules-injector.js +1 -1
- package/dist/assets/plugins/zed-bell.js +1 -1
- package/dist/catalog.d.ts +2 -2
- package/dist/catalog.js +9 -3
- package/dist/cli-output.js +6 -6
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1257
- package/dist/command-cli.d.ts +1 -0
- package/dist/command-cli.js +1004 -0
- package/dist/config-setup.d.ts +14 -8
- package/dist/config-setup.js +197 -64
- package/dist/contracts.js +26 -0
- package/dist/doctor.d.ts +0 -1
- package/dist/doctor.js +42 -55
- package/dist/index.d.ts +13 -118
- package/dist/index.js +118 -125
- package/dist/installer.d.ts +25 -2
- package/dist/installer.js +68 -23
- package/dist/jsonc.d.ts +18 -2
- package/dist/jsonc.js +109 -5
- package/dist/opencode-config.d.ts +11 -0
- package/dist/opencode-config.js +237 -0
- package/dist/plugins/code-simplify.d.ts +25 -0
- package/dist/plugins/code-simplify.js +49 -0
- package/dist/plugins/events.d.ts +20 -0
- package/dist/plugins/events.js +48 -0
- package/dist/plugins/rtk.d.ts +9 -12
- package/dist/plugins/rtk.js +20 -5
- package/dist/plugins/rules-injector.d.ts +21 -11
- package/dist/plugins/rules-injector.js +43 -31
- package/dist/plugins/zed-bell.d.ts +8 -5
- package/dist/plugins/zed-bell.js +10 -2
- package/dist/reconcile.d.ts +4 -1
- package/dist/reconcile.js +12 -16
- package/dist/registry.d.ts +1 -0
- package/dist/registry.js +21 -5
- package/dist/routing.d.ts +5 -3
- package/dist/routing.js +57 -22
- package/dist/self-install.d.ts +7 -0
- package/dist/self-install.js +52 -1
- package/dist/terminal-wizard.d.ts +1 -1
- package/dist/terminal-wizard.js +2 -2
- package/package.json +11 -6
- package/dist/assets/commands/skill-improve.md +0 -10
- package/dist/assets/commands/stopit.md +0 -10
package/dist/agent-profiles.js
CHANGED
|
@@ -4,7 +4,7 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { applyTransaction, deploymentRoot, destination, LifecycleError, lifecycleRoot, listDirectRegular, readRegular,
|
|
7
|
+
import { applyTransaction, deploymentRoot, destination, LifecycleError, lifecycleRoot, listDirectRegular, readRegular, sha256, stable, withLifecycleLock, } from "./lifecycle.js";
|
|
8
8
|
import { readPackageVersion } from "./package-metadata.js";
|
|
9
9
|
export const FIXED_AGENT_ROLES = [
|
|
10
10
|
"manager",
|
|
@@ -51,6 +51,8 @@ function packageVersion() {
|
|
|
51
51
|
function emptyConfig() {
|
|
52
52
|
return {
|
|
53
53
|
schema_version: 1,
|
|
54
|
+
selected_fixed: [],
|
|
55
|
+
selected_critics: [],
|
|
54
56
|
fixed: Object.fromEntries(FIXED_AGENT_ROLES.map((role) => [role, {}])),
|
|
55
57
|
additional_critics: {},
|
|
56
58
|
};
|
|
@@ -100,7 +102,10 @@ function parseConfig(raw) {
|
|
|
100
102
|
throw new AgentProfileError("invalid_configuration", "Agent profile configuration is not valid JSON");
|
|
101
103
|
}
|
|
102
104
|
const config = value;
|
|
103
|
-
if (config
|
|
105
|
+
if (!config ||
|
|
106
|
+
typeof config !== "object" ||
|
|
107
|
+
Array.isArray(config) ||
|
|
108
|
+
config.schema_version !== 1 ||
|
|
104
109
|
!config.fixed ||
|
|
105
110
|
typeof config.fixed !== "object" ||
|
|
106
111
|
Array.isArray(config.fixed) ||
|
|
@@ -109,17 +114,37 @@ function parseConfig(raw) {
|
|
|
109
114
|
Array.isArray(config.additional_critics)) {
|
|
110
115
|
throw new AgentProfileError("invalid_configuration", "Agent profile configuration has an unsupported schema");
|
|
111
116
|
}
|
|
117
|
+
if (Object.keys(config).some((key) => ![
|
|
118
|
+
"schema_version",
|
|
119
|
+
"selected_fixed",
|
|
120
|
+
"selected_critics",
|
|
121
|
+
"fixed",
|
|
122
|
+
"additional_critics",
|
|
123
|
+
].includes(key)))
|
|
124
|
+
throw new AgentProfileError("invalid_configuration", "Agent profile configuration contains unknown fields; preserve and inspect it before retrying");
|
|
112
125
|
if (Object.keys(config.fixed).sort().join(",") !== [...FIXED_AGENT_ROLES].sort().join(","))
|
|
113
126
|
throw new AgentProfileError("invalid_configuration", "Configuration must contain every fixed role exactly once");
|
|
114
127
|
const fixed = Object.fromEntries(FIXED_AGENT_ROLES.map((role) => [role, parseSelection(config.fixed[role], false)]));
|
|
128
|
+
if (config.selected_fixed !== undefined &&
|
|
129
|
+
(!Array.isArray(config.selected_fixed) ||
|
|
130
|
+
config.selected_fixed.some((role) => !FIXED_AGENT_ROLES.includes(role)) ||
|
|
131
|
+
new Set(config.selected_fixed).size !== config.selected_fixed.length))
|
|
132
|
+
throw new AgentProfileError("invalid_configuration", "selected_fixed must contain unique fixed roles");
|
|
115
133
|
const additional = {};
|
|
116
134
|
for (const [name, selection] of Object.entries(config.additional_critics)) {
|
|
117
135
|
if (!CRITIC_PATTERN.test(name))
|
|
118
136
|
throw new AgentProfileError("invalid_configuration", `Unsafe additional critic name: ${name}`);
|
|
119
137
|
additional[name] = parseSelection(selection, true);
|
|
120
138
|
}
|
|
139
|
+
if (config.selected_critics !== undefined &&
|
|
140
|
+
(!Array.isArray(config.selected_critics) ||
|
|
141
|
+
config.selected_critics.some((name) => !CRITIC_PATTERN.test(name) || !(name in additional)) ||
|
|
142
|
+
new Set(config.selected_critics).size !== config.selected_critics.length))
|
|
143
|
+
throw new AgentProfileError("invalid_configuration", "selected_critics must reference unique saved critics");
|
|
121
144
|
return {
|
|
122
145
|
schema_version: 1,
|
|
146
|
+
selected_fixed: config.selected_fixed ?? [],
|
|
147
|
+
selected_critics: config.selected_critics ?? [],
|
|
123
148
|
fixed,
|
|
124
149
|
additional_critics: Object.fromEntries(Object.entries(additional).sort(([left], [right]) => left.localeCompare(right))),
|
|
125
150
|
};
|
|
@@ -183,10 +208,17 @@ async function loadState(scope, root) {
|
|
|
183
208
|
throw new AgentProfileError("unsafe_configuration", "Agent profile configuration must use mode 0600");
|
|
184
209
|
if (manifestRaw && (await inspectFileMode(manifestPath)) !== 0o600)
|
|
185
210
|
throw new AgentProfileError("unsafe_manifest", "Agent deployment manifest must use mode 0600");
|
|
211
|
+
const manifest = manifestRaw ? parseManifest(manifestRaw, scope) : undefined;
|
|
212
|
+
const config = configRaw ? parseConfig(configRaw) : emptyConfig();
|
|
213
|
+
// Recover the selected set from ownership when reading an existing pre-selection file.
|
|
214
|
+
if (configRaw && !("selected_fixed" in JSON.parse(configRaw.toString("utf8")))) {
|
|
215
|
+
config.selected_fixed = FIXED_AGENT_ROLES.filter((role) => Boolean(manifest?.profiles[role]));
|
|
216
|
+
config.selected_critics = Object.keys(config.additional_critics).filter((name) => Boolean(manifest?.profiles[name]));
|
|
217
|
+
}
|
|
186
218
|
return {
|
|
187
|
-
config
|
|
219
|
+
config,
|
|
188
220
|
configRaw,
|
|
189
|
-
manifest
|
|
221
|
+
manifest,
|
|
190
222
|
manifestRaw,
|
|
191
223
|
};
|
|
192
224
|
}
|
|
@@ -210,21 +242,21 @@ function withSelection(content, selection) {
|
|
|
210
242
|
const mode = filtered.findIndex((line, index) => index < end && line.startsWith("mode:"));
|
|
211
243
|
if (mode < 0)
|
|
212
244
|
throw new AgentProfileError("asset_error", "Canonical agent has no mode");
|
|
213
|
-
filtered.splice(mode + 1, 0, `model: ${selection.model}
|
|
245
|
+
filtered.splice(mode + 1, 0, `model: ${selection.model}${selection.variant ? `#${selection.variant}` : ""}`);
|
|
214
246
|
return filtered.join("\n");
|
|
215
247
|
}
|
|
216
248
|
function replaceTaskAllowlist(content, allowed) {
|
|
217
249
|
const lines = content.split("\n");
|
|
218
|
-
const permission = lines.indexOf("
|
|
250
|
+
const permission = lines.indexOf("permissions:");
|
|
219
251
|
if (permission < 0)
|
|
220
252
|
throw new AgentProfileError("asset_error", "Canonical primary agent has no permission block");
|
|
221
|
-
const task = lines.findIndex((line, index) => index > permission && line
|
|
253
|
+
const task = lines.findIndex((line, index) => index > permission && line.startsWith(" - { action: subagent,"));
|
|
222
254
|
if (task < 0)
|
|
223
255
|
throw new AgentProfileError("asset_error", "Canonical primary agent has no task permission");
|
|
224
256
|
let end = task + 1;
|
|
225
|
-
while (end < lines.length &&
|
|
257
|
+
while (end < lines.length && lines[end].startsWith(" - { action: subagent,"))
|
|
226
258
|
end += 1;
|
|
227
|
-
lines.splice(task, end - task,
|
|
259
|
+
lines.splice(task, end - task, ' - { action: subagent, resource: "*", effect: deny }', ...allowed.map((name) => ` - { action: subagent, resource: ${JSON.stringify(name)}, effect: allow }`));
|
|
228
260
|
return lines.join("\n");
|
|
229
261
|
}
|
|
230
262
|
export function renderAgentProfile(name, config, canonical) {
|
|
@@ -234,9 +266,12 @@ export function renderAgentProfile(name, config, canonical) {
|
|
|
234
266
|
if (!selection)
|
|
235
267
|
throw new AgentProfileError("invalid_configuration", `No configuration exists for ${name}`);
|
|
236
268
|
let rendered = withSelection(canonical[role].toString("utf8"), selection);
|
|
237
|
-
const pool =
|
|
269
|
+
const pool = desiredNames(config).filter((item) => item === "critic" || CRITIC_PATTERN.test(item));
|
|
238
270
|
if (name === "manager")
|
|
239
|
-
rendered = replaceTaskAllowlist(rendered, [
|
|
271
|
+
rendered = replaceTaskAllowlist(rendered, [
|
|
272
|
+
...config.selected_fixed.filter((role) => ["architect", "worker", "mapper", "review"].includes(role)),
|
|
273
|
+
...pool,
|
|
274
|
+
]);
|
|
240
275
|
if (name === "review")
|
|
241
276
|
rendered = replaceTaskAllowlist(rendered, pool);
|
|
242
277
|
return Buffer.from(rendered);
|
|
@@ -247,7 +282,7 @@ function selectionFor(config, name) {
|
|
|
247
282
|
: config.additional_critics[name];
|
|
248
283
|
}
|
|
249
284
|
function desiredNames(config) {
|
|
250
|
-
return [...
|
|
285
|
+
return [...config.selected_fixed, ...config.selected_critics].sort();
|
|
251
286
|
}
|
|
252
287
|
function desiredManifest(scope, config, rendered, canonical, names = desiredNames(config)) {
|
|
253
288
|
const profiles = {};
|
|
@@ -273,6 +308,11 @@ function desiredManifest(scope, config, rendered, canonical, names = desiredName
|
|
|
273
308
|
}
|
|
274
309
|
function changeConfig(current, request) {
|
|
275
310
|
const config = JSON.parse(JSON.stringify(current));
|
|
311
|
+
if (request.changes) {
|
|
312
|
+
if (request.changes.some((change) => change.changes || !["model-set", "critic-add", "critic-remove"].includes(change.action)))
|
|
313
|
+
throw new AgentProfileError("invalid_input", "Profile drafts accept only model and critic changes");
|
|
314
|
+
return request.changes.reduce((draft, change) => changeConfig(draft, change), config);
|
|
315
|
+
}
|
|
276
316
|
if (request.action === "model-set") {
|
|
277
317
|
const name = validateAgentName(request.name ?? "");
|
|
278
318
|
if (!NAME_PATTERN.test(name) && !(name in config.additional_critics))
|
|
@@ -297,6 +337,7 @@ function changeConfig(current, request) {
|
|
|
297
337
|
if (variant)
|
|
298
338
|
selection.variant = variant;
|
|
299
339
|
config.additional_critics[name] = selection;
|
|
340
|
+
config.selected_critics.push(name);
|
|
300
341
|
}
|
|
301
342
|
else if (request.action === "critic-remove") {
|
|
302
343
|
const name = request.name ?? "";
|
|
@@ -307,6 +348,7 @@ function changeConfig(current, request) {
|
|
|
307
348
|
if (!(name in config.additional_critics))
|
|
308
349
|
throw new AgentProfileError("unknown_profile", `Additional critic is not configured: ${name}`);
|
|
309
350
|
delete config.additional_critics[name];
|
|
351
|
+
config.selected_critics = config.selected_critics.filter((item) => item !== name);
|
|
310
352
|
}
|
|
311
353
|
config.additional_critics = Object.fromEntries(Object.entries(config.additional_critics).sort(([left], [right]) => left.localeCompare(right)));
|
|
312
354
|
return config;
|
|
@@ -335,11 +377,17 @@ export async function buildAgentProfilePlan(request, scope, cwd = process.cwd(),
|
|
|
335
377
|
]);
|
|
336
378
|
const byFile = new Map(agentFiles.filter((item) => item.name.endsWith(".md")).map((item) => [item.name, item.content]));
|
|
337
379
|
const config = changeConfig(state.config, request);
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
380
|
+
if (request.action === "install" && selectedAgents === undefined && !state.configRaw)
|
|
381
|
+
config.selected_fixed = [...FIXED_AGENT_ROLES];
|
|
382
|
+
if (selectedAgents !== undefined)
|
|
383
|
+
config.selected_fixed = [...selectedAgents];
|
|
384
|
+
if (request.action === "install")
|
|
385
|
+
config.selected_critics = Object.keys(config.additional_critics).sort();
|
|
386
|
+
if (request.action === "uninstall") {
|
|
387
|
+
config.selected_fixed = [];
|
|
388
|
+
config.selected_critics = [];
|
|
389
|
+
}
|
|
390
|
+
const names = request.action === "uninstall" ? [] : desiredNames(config);
|
|
343
391
|
const rendered = Object.fromEntries(names.map((name) => [name, renderAgentProfile(name, config, canonical)]));
|
|
344
392
|
const desired = request.action === "uninstall"
|
|
345
393
|
? undefined
|
|
@@ -412,11 +460,13 @@ export async function buildAgentProfilePlan(request, scope, cwd = process.cwd(),
|
|
|
412
460
|
(!content ||
|
|
413
461
|
sha256(content) !== ownedHash ||
|
|
414
462
|
agentFiles.find((item) => item.name === `${name}.md`)?.mode !== 0o600);
|
|
415
|
-
if (drift &&
|
|
463
|
+
if (drift &&
|
|
464
|
+
!(["install", "reconcile"].includes(request.action) &&
|
|
465
|
+
(!content || sha256(content) === ownedHash))) {
|
|
416
466
|
operations.push({
|
|
417
467
|
path,
|
|
418
468
|
operation: "conflict",
|
|
419
|
-
reason: "managed profile drift
|
|
469
|
+
reason: "managed profile drift is preserved; restore the recorded bytes before retrying",
|
|
420
470
|
...(content ? { sha256: sha256(content) } : {}),
|
|
421
471
|
});
|
|
422
472
|
continue;
|
|
@@ -455,8 +505,7 @@ export async function buildAgentProfilePlan(request, scope, cwd = process.cwd(),
|
|
|
455
505
|
}
|
|
456
506
|
}
|
|
457
507
|
const configContent = Buffer.from(`${stable(config)}\n`);
|
|
458
|
-
if (request.action
|
|
459
|
-
names.length > 0 &&
|
|
508
|
+
if ((names.length > 0 || Boolean(state.configRaw) || request.action === "model-set") &&
|
|
460
509
|
(!state.configRaw || !state.configRaw.equals(configContent))) {
|
|
461
510
|
operations.push({
|
|
462
511
|
path: CONFIG_PATH,
|
|
@@ -521,13 +570,14 @@ export async function buildAgentProfilePlan(request, scope, cwd = process.cwd(),
|
|
|
521
570
|
};
|
|
522
571
|
const plan = {
|
|
523
572
|
...base,
|
|
573
|
+
digest: sha256(stable({ mutations, config: state.configRaw, manifest: state.manifestRaw, base })),
|
|
524
574
|
};
|
|
525
575
|
return {
|
|
526
576
|
plan,
|
|
527
577
|
mutations,
|
|
528
578
|
config,
|
|
529
579
|
manifest: finalManifest,
|
|
530
|
-
expectedConfig: names.length ? configContent :
|
|
580
|
+
expectedConfig: names.length || state.configRaw || request.action === "model-set" ? configContent : undefined,
|
|
531
581
|
expectedManifest: manifestContent,
|
|
532
582
|
legacyTransferred,
|
|
533
583
|
};
|
|
@@ -577,17 +627,25 @@ export async function listAgentProfiles(scope, cwd = process.cwd(), home = homed
|
|
|
577
627
|
const records = [];
|
|
578
628
|
const collisions = [];
|
|
579
629
|
const drift = [];
|
|
580
|
-
for (const name of
|
|
630
|
+
for (const name of [
|
|
631
|
+
...new Set([
|
|
632
|
+
...FIXED_AGENT_ROLES,
|
|
633
|
+
...Object.keys(state.config.additional_critics),
|
|
634
|
+
...configured,
|
|
635
|
+
]),
|
|
636
|
+
].sort()) {
|
|
581
637
|
const file = byName.get(name);
|
|
582
638
|
const owned = state.manifest?.profiles[name];
|
|
583
639
|
let stateValue;
|
|
584
|
-
if (!owned && file)
|
|
585
|
-
stateValue = "
|
|
640
|
+
if (!configured.includes(name) && !owned && !file)
|
|
641
|
+
stateValue = "not-installed";
|
|
642
|
+
else if (!owned && file)
|
|
643
|
+
stateValue = configured.includes(name) ? "collision" : "current";
|
|
586
644
|
else if (!file)
|
|
587
645
|
stateValue = owned ? "drift" : "missing";
|
|
588
646
|
else if (!owned ||
|
|
589
647
|
sha256(file.content) !== owned.rendered_sha256 ||
|
|
590
|
-
!file.content.equals(desired[name]) ||
|
|
648
|
+
(configured.includes(name) && !file.content.equals(desired[name])) ||
|
|
591
649
|
file.mode !== 0o600)
|
|
592
650
|
stateValue = owned ? "drift" : "collision";
|
|
593
651
|
else
|
|
@@ -599,7 +657,11 @@ export async function listAgentProfiles(scope, cwd = process.cwd(), home = homed
|
|
|
599
657
|
const selection = selectionFor(state.config, name);
|
|
600
658
|
records.push({
|
|
601
659
|
name,
|
|
602
|
-
ownership:
|
|
660
|
+
ownership: !configured.includes(name) && file && !owned
|
|
661
|
+
? "user-owned"
|
|
662
|
+
: NAME_PATTERN.test(name)
|
|
663
|
+
? "package-owned"
|
|
664
|
+
: "managed",
|
|
603
665
|
state: stateValue,
|
|
604
666
|
...("model" in selection
|
|
605
667
|
? { model: selection.model, ...(selection.variant ? { variant: selection.variant } : {}) }
|
|
@@ -621,9 +683,12 @@ export async function listAgentProfiles(scope, cwd = process.cwd(), home = homed
|
|
|
621
683
|
root,
|
|
622
684
|
package_version: packageVersion(),
|
|
623
685
|
critic_pool: state.manifest?.critic_pool ??
|
|
624
|
-
|
|
686
|
+
configured.filter((name) => name === "critic" || CRITIC_PATTERN.test(name)),
|
|
625
687
|
profiles: records.sort((left, right) => left.name.localeCompare(right.name)),
|
|
626
|
-
user_owned:
|
|
688
|
+
user_owned: records
|
|
689
|
+
.filter((record) => record.ownership === "user-owned")
|
|
690
|
+
.map((record) => record.name)
|
|
691
|
+
.sort(),
|
|
627
692
|
collisions,
|
|
628
693
|
drift,
|
|
629
694
|
requires_restart: false,
|
|
@@ -632,25 +697,24 @@ export async function listAgentProfiles(scope, cwd = process.cwd(), home = homed
|
|
|
632
697
|
}
|
|
633
698
|
export async function previewAgentProfileChange(request, scope, cwd = process.cwd(), home = homedir()) {
|
|
634
699
|
const stateRoot = lifecycleRoot(scope, cwd, home);
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
return (await buildAgentProfilePlan(request, scope, cwd, home)).plan;
|
|
639
|
-
});
|
|
700
|
+
if (await readRegular(resolve(stateRoot, "transaction-journal.json")))
|
|
701
|
+
throw new AgentProfileError("recovery_required", "Run maintenance recover before a fresh preview");
|
|
702
|
+
return (await buildAgentProfilePlan(request, scope, cwd, home)).plan;
|
|
640
703
|
}
|
|
641
704
|
export async function applyAgentProfileChange(request, scope, cwd = process.cwd(), home = homedir(), options = {}) {
|
|
642
705
|
const stateRoot = lifecycleRoot(scope, cwd, home);
|
|
643
706
|
const root = deploymentRoot(scope, cwd, home);
|
|
644
707
|
return withLifecycleLock(stateRoot, async () => {
|
|
645
|
-
if (await
|
|
646
|
-
throw new AgentProfileError("
|
|
708
|
+
if (await readRegular(resolve(stateRoot, "transaction-journal.json")))
|
|
709
|
+
throw new AgentProfileError("recovery_required", "Run maintenance recover before a fresh preview");
|
|
647
710
|
const built = await buildAgentProfilePlan(request, scope, cwd, home);
|
|
711
|
+
if (options.expectedDigest && options.expectedDigest !== built.plan.digest)
|
|
712
|
+
throw new AgentProfileError("stale_plan", "Profiles changed; request a fresh preview");
|
|
648
713
|
if (built.plan.operations.some((item) => item.operation === "conflict" && item.reason.includes("collision")))
|
|
649
714
|
throw new AgentProfileError("collision", "Exact-name user-owned collision blocks apply");
|
|
650
715
|
if (request.action !== "uninstall" &&
|
|
651
|
-
request.action !== "reconcile" &&
|
|
652
716
|
built.plan.operations.some((item) => item.operation === "conflict"))
|
|
653
|
-
throw new AgentProfileError("drift", "Managed profile drift
|
|
717
|
+
throw new AgentProfileError("drift", "Managed profile drift is preserved; restore the recorded bytes before retrying");
|
|
654
718
|
await applyTransaction(root, stateRoot, built.mutations, {
|
|
655
719
|
...options,
|
|
656
720
|
validateFinal: async () => {
|
|
@@ -662,7 +726,7 @@ export async function applyAgentProfileChange(request, scope, cwd = process.cwd(
|
|
|
662
726
|
final.drift.length ||
|
|
663
727
|
final.profiles
|
|
664
728
|
.filter((item) => item.ownership !== "user-owned")
|
|
665
|
-
.some((item) => item.state !== "current"))) {
|
|
729
|
+
.some((item) => item.state !== "current" && item.state !== "not-installed"))) {
|
|
666
730
|
throw new AgentProfileError("final_validation_failed", "Final agent inventory validation failed");
|
|
667
731
|
}
|
|
668
732
|
},
|
|
@@ -675,65 +739,50 @@ export async function applyAgentProfileChange(request, scope, cwd = process.cwd(
|
|
|
675
739
|
};
|
|
676
740
|
});
|
|
677
741
|
}
|
|
742
|
+
async function modelCatalog() {
|
|
743
|
+
const query = new URLSearchParams({ "location[directory]": process.cwd() });
|
|
744
|
+
const { stdout } = await execFileAsync("opencode", ["api", "get", `/api/model?${query}`], {
|
|
745
|
+
timeout: 10_000,
|
|
746
|
+
encoding: "utf8",
|
|
747
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
748
|
+
});
|
|
749
|
+
const response = JSON.parse(stdout);
|
|
750
|
+
if (!Array.isArray(response.data) ||
|
|
751
|
+
!response.data.every((entry) => entry &&
|
|
752
|
+
typeof entry.providerID === "string" &&
|
|
753
|
+
typeof entry.id === "string" &&
|
|
754
|
+
MODEL_PATTERN.test(`${entry.providerID}/${entry.id}`)))
|
|
755
|
+
throw new Error("invalid V2 model inventory");
|
|
756
|
+
return response.data;
|
|
757
|
+
}
|
|
678
758
|
export async function availableModels() {
|
|
679
759
|
try {
|
|
680
|
-
const { stdout } = await execFileAsync("opencode", ["models"], {
|
|
681
|
-
timeout: 10_000,
|
|
682
|
-
encoding: "utf8",
|
|
683
|
-
});
|
|
684
760
|
const models = [
|
|
685
|
-
...new Set(
|
|
686
|
-
.split(/\r?\n/)
|
|
687
|
-
.map((line) => line.trim())
|
|
688
|
-
.filter((line) => MODEL_PATTERN.test(line))),
|
|
761
|
+
...new Set((await modelCatalog()).map((entry) => `${entry.providerID}/${entry.id}`)),
|
|
689
762
|
].sort();
|
|
690
763
|
if (!models.length)
|
|
691
764
|
throw new Error("empty catalog");
|
|
692
765
|
return models;
|
|
693
766
|
}
|
|
694
767
|
catch (error) {
|
|
695
|
-
throw new AgentProfileError("catalog_unavailable", `
|
|
768
|
+
throw new AgentProfileError("catalog_unavailable", `OpenCode V2 model snapshot is unavailable; provide an explicit provider/model: ${error instanceof Error ? error.message : String(error)}`);
|
|
696
769
|
}
|
|
697
770
|
}
|
|
698
771
|
export async function availableModelVariants(model) {
|
|
699
772
|
const selected = validateModel(model);
|
|
700
|
-
const provider = selected.split("/", 1)[0];
|
|
701
773
|
try {
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
let document = "";
|
|
712
|
-
for (index += 1; index < lines.length; index += 1) {
|
|
713
|
-
document += `${lines[index]}\n`;
|
|
714
|
-
try {
|
|
715
|
-
const metadata = JSON.parse(document);
|
|
716
|
-
if (metadata.variants === undefined)
|
|
717
|
-
return [];
|
|
718
|
-
if (!metadata.variants ||
|
|
719
|
-
typeof metadata.variants !== "object" ||
|
|
720
|
-
Array.isArray(metadata.variants))
|
|
721
|
-
throw new Error("invalid variants metadata");
|
|
722
|
-
const variants = Object.keys(metadata.variants);
|
|
723
|
-
if (!variants.every((variant) => VARIANT_PATTERN.test(variant)))
|
|
724
|
-
throw new Error("unsafe variant");
|
|
725
|
-
return variants;
|
|
726
|
-
}
|
|
727
|
-
catch (error) {
|
|
728
|
-
if (!(error instanceof SyntaxError))
|
|
729
|
-
throw error;
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
throw new Error("selected model is absent");
|
|
774
|
+
const metadata = (await modelCatalog()).find((entry) => `${entry.providerID}/${entry.id}` === selected);
|
|
775
|
+
if (!metadata)
|
|
776
|
+
throw new Error("selected model is absent");
|
|
777
|
+
if (metadata.variants === undefined)
|
|
778
|
+
return [];
|
|
779
|
+
if (!Array.isArray(metadata.variants) ||
|
|
780
|
+
!metadata.variants.every((variant) => variant && typeof variant.id === "string" && VARIANT_PATTERN.test(variant.id)))
|
|
781
|
+
throw new Error("invalid variants metadata");
|
|
782
|
+
return metadata.variants.map((variant) => variant.id);
|
|
734
783
|
}
|
|
735
784
|
catch (error) {
|
|
736
|
-
throw new AgentProfileError("catalog_unavailable", `
|
|
785
|
+
throw new AgentProfileError("catalog_unavailable", `OpenCode V2 model variants are unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
737
786
|
}
|
|
738
787
|
}
|
|
739
788
|
export async function inspectFileMode(path) {
|
|
@@ -3,10 +3,10 @@ description: Analyzes architecture choices, compatibility, and operational risk.
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
hidden: true
|
|
5
5
|
steps: 16
|
|
6
|
-
|
|
7
|
-
edit: deny
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
permissions:
|
|
7
|
+
- { action: edit, resource: "*", effect: deny }
|
|
8
|
+
- { action: shell, resource: "*", effect: deny }
|
|
9
|
+
- { action: subagent, resource: "*", effect: deny }
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Architect
|
|
@@ -3,14 +3,13 @@ description: Independently reviews changes for correctness, security, and regres
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
hidden: true
|
|
5
5
|
steps: 12
|
|
6
|
-
|
|
7
|
-
edit: deny
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
task: deny
|
|
6
|
+
permissions:
|
|
7
|
+
- { action: edit, resource: "*", effect: deny }
|
|
8
|
+
- { action: shell, resource: "*", effect: deny }
|
|
9
|
+
- { action: shell, resource: "git --no-optional-locks -c core.fsmonitor=false status --porcelain=v1 --untracked-files=all", effect: allow }
|
|
10
|
+
- { action: shell, resource: "git -c diff.external= -c diff.trustExitCode=false diff --no-ext-diff --no-textconv --", effect: allow }
|
|
11
|
+
- { action: shell, resource: "git -c diff.external= -c diff.trustExitCode=false diff --cached --no-ext-diff --no-textconv --", effect: allow }
|
|
12
|
+
- { action: subagent, resource: "*", effect: deny }
|
|
14
13
|
---
|
|
15
14
|
|
|
16
15
|
# Critic
|
|
@@ -24,7 +23,7 @@ a non-code target. Stay read-only and never delegate.
|
|
|
24
23
|
|
|
25
24
|
For execution-card verification, inspect the actual worktree diff, not a worker summary. Use only the supplied
|
|
26
25
|
execution card and worker report as context, and verify the result against the
|
|
27
|
-
card's exact write set, acceptance criteria, checks, and boundaries. For
|
|
26
|
+
card's exact write set, acceptance criteria, checks, and boundaries. For shell
|
|
28
27
|
inspection use exactly the three literal commands in this agent's allowlist:
|
|
29
28
|
|
|
30
29
|
```text
|
|
@@ -34,10 +33,10 @@ git -c diff.external= -c diff.trustExitCode=false diff --cached --no-ext-diff --
|
|
|
34
33
|
```
|
|
35
34
|
|
|
36
35
|
Read untracked files named by status with native OpenCode `Read`, never with
|
|
37
|
-
|
|
36
|
+
shell. Return exactly one structured `critic_report` with matching `card_id` and
|
|
38
37
|
`revision`. Its only status values are `APPROVED` and `CHANGES_REQUIRED`;
|
|
39
38
|
include `schema_version: 1`, verified findings, evidence, unrun checks, and risks. Do not edit files,
|
|
40
|
-
run any other
|
|
39
|
+
run any other shell command, delegate work, or propose direct worker remediation.
|
|
41
40
|
|
|
42
41
|
```json
|
|
43
42
|
{
|
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
description: Coordinates bounded OpenCode routing and execution. Russian triggers: менеджер, координация.
|
|
3
3
|
mode: primary
|
|
4
4
|
steps: 12
|
|
5
|
-
|
|
6
|
-
edit: deny
|
|
7
|
-
|
|
8
|
-
question: allow
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
critic: allow
|
|
5
|
+
permissions:
|
|
6
|
+
- { action: edit, resource: "*", effect: deny }
|
|
7
|
+
- { action: shell, resource: "*", effect: deny }
|
|
8
|
+
- { action: question, resource: "*", effect: allow }
|
|
9
|
+
- { action: subagent, resource: "*", effect: deny }
|
|
10
|
+
- { action: subagent, resource: "architect", effect: allow }
|
|
11
|
+
- { action: subagent, resource: "worker", effect: allow }
|
|
12
|
+
- { action: subagent, resource: "mapper", effect: allow }
|
|
13
|
+
- { action: subagent, resource: "review", effect: allow }
|
|
14
|
+
- { action: subagent, resource: "critic", effect: allow }
|
|
16
15
|
---
|
|
17
16
|
|
|
18
17
|
# Manager
|
|
@@ -33,7 +32,7 @@ Never pass caller-supplied agents, capabilities, tools, models, or availability
|
|
|
33
32
|
to routing. Unknown or user-owned profiles require an explicit trusted override.
|
|
34
33
|
|
|
35
34
|
Forward the original task, evidence, exact card, confirmation references, and
|
|
36
|
-
structured result unchanged. Validate machine contracts at each OpenCode
|
|
35
|
+
structured result unchanged. Validate machine contracts at each OpenCode subagent
|
|
37
36
|
dispatch and result boundary. Resolve disagreements claim-by-claim against the
|
|
38
37
|
supplied evidence and record the evidence reference; unresolved conflicts remain
|
|
39
38
|
blocked and return to the manager. Never vote between agents or start another worker or
|
|
@@ -3,10 +3,10 @@ description: Maps relevant files, callers, tests, and established repository pat
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
hidden: true
|
|
5
5
|
steps: 12
|
|
6
|
-
|
|
7
|
-
edit: deny
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
permissions:
|
|
7
|
+
- { action: edit, resource: "*", effect: deny }
|
|
8
|
+
- { action: shell, resource: "*", effect: deny }
|
|
9
|
+
- { action: subagent, resource: "*", effect: deny }
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Mapper
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Reviews exact targets, coordinates independent critics, and applies fixes only on explicit request. Russian triggers: ревью, проверка.
|
|
3
3
|
mode: all
|
|
4
|
-
|
|
5
|
-
edit: allow
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
skill: allow
|
|
4
|
+
permissions:
|
|
5
|
+
- { action: edit, resource: "*", effect: allow }
|
|
6
|
+
- { action: shell, resource: "*", effect: allow }
|
|
7
|
+
- { action: subagent, resource: "*", effect: deny }
|
|
8
|
+
- { action: subagent, resource: "critic", effect: allow }
|
|
9
|
+
- { action: webfetch, resource: "*", effect: deny }
|
|
10
|
+
- { action: websearch, resource: "*", effect: deny }
|
|
11
|
+
- { action: question, resource: "*", effect: allow }
|
|
12
|
+
- { action: skill, resource: "*", effect: allow }
|
|
14
13
|
---
|
|
15
14
|
|
|
16
15
|
# Reviewer
|
|
@@ -25,9 +24,9 @@ in force during review. Report changed files and checks after fixes.
|
|
|
25
24
|
|
|
26
25
|
Use independent critics when the selected skill requires them or the user requests
|
|
27
26
|
them. Select exactly
|
|
28
|
-
one critic from this profile's exact
|
|
27
|
+
one critic from this profile's exact subagent allowlist; never infer or expand the pool
|
|
29
28
|
with a prefix wildcard or auto-fan-out. Send the critic its clean package and
|
|
30
|
-
use `route` with the selected critic as the override before each
|
|
29
|
+
use `route` with the selected critic as the override before each subagent call. Keep
|
|
31
30
|
the child's session independent from the primary review and withhold your findings
|
|
32
31
|
while sharing the agreed scope, evidence, and accepted limitations. Validate its
|
|
33
32
|
versioned `review_report` before use. Report only verified findings,
|
|
@@ -3,38 +3,35 @@ description: Implements scoped changes and runs the relevant verification. Russi
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
hidden: true
|
|
5
5
|
steps: 12
|
|
6
|
-
|
|
7
|
-
edit: allow
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
websearch: deny
|
|
36
|
-
skill: deny
|
|
37
|
-
lsp: deny
|
|
6
|
+
permissions:
|
|
7
|
+
- { action: edit, resource: "*", effect: allow }
|
|
8
|
+
- { action: subagent, resource: "*", effect: deny }
|
|
9
|
+
- { action: shell, resource: "*", effect: ask }
|
|
10
|
+
- { action: shell, resource: "mypy", effect: allow }
|
|
11
|
+
- { action: shell, resource: "mypy *", effect: allow }
|
|
12
|
+
- { action: shell, resource: "ruff check*", effect: allow }
|
|
13
|
+
- { action: shell, resource: "ruff format --check*", effect: allow }
|
|
14
|
+
- { action: shell, resource: "task --list*", effect: allow }
|
|
15
|
+
- { action: shell, resource: "task -l*", effect: allow }
|
|
16
|
+
- { action: shell, resource: "rg", effect: deny }
|
|
17
|
+
- { action: shell, resource: "rg *", effect: deny }
|
|
18
|
+
- { action: shell, resource: "grep", effect: deny }
|
|
19
|
+
- { action: shell, resource: "grep *", effect: deny }
|
|
20
|
+
- { action: shell, resource: "find", effect: deny }
|
|
21
|
+
- { action: shell, resource: "find *", effect: deny }
|
|
22
|
+
- { action: shell, resource: "fd", effect: deny }
|
|
23
|
+
- { action: shell, resource: "fd *", effect: deny }
|
|
24
|
+
- { action: shell, resource: "git grep", effect: deny }
|
|
25
|
+
- { action: shell, resource: "git grep *", effect: deny }
|
|
26
|
+
- { action: shell, resource: "git -C * grep", effect: deny }
|
|
27
|
+
- { action: shell, resource: "git -C * grep *", effect: deny }
|
|
28
|
+
- { action: shell, resource: "git * grep", effect: deny }
|
|
29
|
+
- { action: shell, resource: "git * grep *", effect: deny }
|
|
30
|
+
- { action: glob, resource: "*", effect: deny }
|
|
31
|
+
- { action: grep, resource: "*", effect: deny }
|
|
32
|
+
- { action: webfetch, resource: "*", effect: deny }
|
|
33
|
+
- { action: websearch, resource: "*", effect: deny }
|
|
34
|
+
- { action: skill, resource: "*", effect: deny }
|
|
38
35
|
---
|
|
39
36
|
|
|
40
37
|
# Worker
|