opencode-agenthub 0.1.1 → 0.1.3
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 +2 -2
- package/dist/composer/bootstrap.js +2 -1
- package/dist/composer/builtin-assets.js +2 -0
- package/dist/composer/compose.js +30 -1
- package/dist/composer/library/bundles/explore.json +21 -0
- package/dist/composer/library/instructions/hr-boundaries.md +1 -0
- package/dist/composer/library/instructions/hr-protocol.md +13 -14
- package/dist/composer/library/souls/explore.md +26 -0
- package/dist/composer/library/souls/hr-adapter.md +3 -2
- package/dist/composer/library/souls/hr-cto.md +6 -3
- package/dist/composer/library/souls/hr-planner.md +3 -3
- package/dist/composer/library/souls/hr-sourcer.md +1 -0
- package/dist/composer/library/souls/hr-verifier.md +2 -2
- package/dist/composer/library/souls/hr.md +25 -27
- package/dist/composer/model-utils.js +25 -1
- package/dist/composer/opencode-profile.js +340 -73
- package/dist/composer/platform.js +4 -1
- package/dist/composer/settings.js +103 -2
- package/dist/skills/agenthub-doctor/diagnose.js +21 -0
- package/dist/skills/agenthub-doctor/interactive.js +19 -0
- package/dist/skills/hr-assembly/SKILL.md +4 -1
- package/dist/skills/hr-final-check/SKILL.md +8 -6
- package/dist/skills/hr-staffing/SKILL.md +8 -5
- package/dist/skills/hr-support/bin/validate_staged_package.py +15 -1
- package/package.json +1 -1
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import {
|
|
6
|
+
normalizeModelSelection,
|
|
7
|
+
pickModelSelection,
|
|
8
|
+
validateModelAgainstCatalog,
|
|
9
|
+
validateModelIdentifier
|
|
10
|
+
} from "./model-utils.js";
|
|
5
11
|
import { buildBuiltinVersionManifest } from "./builtin-assets.js";
|
|
6
12
|
import { getDefaultProfilePlugins } from "./defaults.js";
|
|
7
13
|
import { readPackageVersion } from "./package-version.js";
|
|
8
|
-
import { resolveHomeConfigRoot } from "./platform.js";
|
|
14
|
+
import { resolveHomeConfigRoot, spawnOptions } from "./platform.js";
|
|
9
15
|
const hrPrimaryAgentName = "hr";
|
|
10
16
|
const recommendedHrBootstrapModel = "openai/gpt-5.4-mini";
|
|
11
17
|
const recommendedHrBootstrapVariant = "high";
|
|
@@ -253,6 +259,97 @@ const normalizeConfiguredModel = (value) => {
|
|
|
253
259
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
254
260
|
};
|
|
255
261
|
const fallbackInstalledModel = (installedModels, nativeModel) => installedModels[hrPrimaryAgentName]?.model || installedModels.auto?.model || Object.values(installedModels)[0]?.model || nativeModel;
|
|
262
|
+
const readHrKnownModelIds = async (targetRoot) => {
|
|
263
|
+
const filePath = path.join(targetRoot, "inventory", "models", "valid-model-ids.txt");
|
|
264
|
+
try {
|
|
265
|
+
const raw = await readFile(filePath, "utf8");
|
|
266
|
+
const values = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
267
|
+
return values.length > 0 ? new Set(values) : void 0;
|
|
268
|
+
} catch {
|
|
269
|
+
return void 0;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
const listAvailableOpencodeModels = async () => new Promise((resolve) => {
|
|
273
|
+
const child = spawn("opencode", ["models"], {
|
|
274
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
275
|
+
...spawnOptions()
|
|
276
|
+
});
|
|
277
|
+
const timeout = setTimeout(() => {
|
|
278
|
+
child.kill();
|
|
279
|
+
resolve(void 0);
|
|
280
|
+
}, 15e3);
|
|
281
|
+
let stdout = "";
|
|
282
|
+
child.stdout?.on("data", (chunk) => {
|
|
283
|
+
stdout += chunk.toString();
|
|
284
|
+
});
|
|
285
|
+
child.on("error", () => {
|
|
286
|
+
clearTimeout(timeout);
|
|
287
|
+
resolve(void 0);
|
|
288
|
+
});
|
|
289
|
+
child.on("close", () => {
|
|
290
|
+
clearTimeout(timeout);
|
|
291
|
+
const models = stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
292
|
+
resolve(models.length > 0 ? [...new Set(models)].sort() : void 0);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
const probeOpencodeModelAvailability = async (model, options = {}) => {
|
|
296
|
+
const models = await (options.listModels ?? listAvailableOpencodeModels)();
|
|
297
|
+
if (!models) {
|
|
298
|
+
return {
|
|
299
|
+
available: false,
|
|
300
|
+
reason: "probe_failed",
|
|
301
|
+
message: "Unable to verify model availability from opencode. Continue only if you know this model works in your environment."
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return models.includes(model) ? { available: true } : {
|
|
305
|
+
available: false,
|
|
306
|
+
reason: "unavailable",
|
|
307
|
+
message: `Model '${model}' is not available in the current opencode environment.`
|
|
308
|
+
};
|
|
309
|
+
};
|
|
310
|
+
const agentModelValidationMessage = (agentName, model, status) => `Agent '${agentName}' model '${model}' is invalid: ${status.message}`;
|
|
311
|
+
const validateHrAgentModelConfiguration = async (targetRoot, settings, options = {}) => {
|
|
312
|
+
const currentSettings = settings ?? await readAgentHubSettings(targetRoot);
|
|
313
|
+
if (!currentSettings?.agents) return { valid: true };
|
|
314
|
+
const knownModels = await readHrKnownModelIds(targetRoot);
|
|
315
|
+
const availabilityCache = /* @__PURE__ */ new Map();
|
|
316
|
+
for (const agentName of hrAgentNames) {
|
|
317
|
+
const model = currentSettings.agents[agentName]?.model;
|
|
318
|
+
if (typeof model !== "string" || model.trim().length === 0) continue;
|
|
319
|
+
const syntax = validateModelIdentifier(model);
|
|
320
|
+
if (!syntax.ok) {
|
|
321
|
+
return {
|
|
322
|
+
valid: false,
|
|
323
|
+
agentName,
|
|
324
|
+
model,
|
|
325
|
+
syntax,
|
|
326
|
+
message: agentModelValidationMessage(agentName, model, syntax)
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const catalog = validateModelAgainstCatalog(model, knownModels);
|
|
330
|
+
if (!catalog.ok) {
|
|
331
|
+
return {
|
|
332
|
+
valid: false,
|
|
333
|
+
agentName,
|
|
334
|
+
model,
|
|
335
|
+
catalog,
|
|
336
|
+
message: agentModelValidationMessage(agentName, model, catalog)
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
const availability = availabilityCache.get(model) || await probeOpencodeModelAvailability(model, options);
|
|
340
|
+
availabilityCache.set(model, availability);
|
|
341
|
+
if (!availability.available && availability.reason !== "probe_failed") {
|
|
342
|
+
return {
|
|
343
|
+
valid: false,
|
|
344
|
+
agentName,
|
|
345
|
+
model,
|
|
346
|
+
availability,
|
|
347
|
+
message: agentModelValidationMessage(agentName, model, availability)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return { valid: true };
|
|
352
|
+
};
|
|
256
353
|
const resolveHrBootstrapAgentModels = async ({
|
|
257
354
|
targetRoot,
|
|
258
355
|
selection
|
|
@@ -383,11 +480,14 @@ export {
|
|
|
383
480
|
hrAgentNames,
|
|
384
481
|
hrPrimaryAgentName,
|
|
385
482
|
hrSubagentNames,
|
|
483
|
+
listAvailableOpencodeModels,
|
|
386
484
|
loadNativeOpenCodeConfig,
|
|
387
485
|
loadNativeOpenCodePreferences,
|
|
388
486
|
mergeAgentHubSettingsDefaults,
|
|
389
487
|
nativeOpenCodeConfigPath,
|
|
488
|
+
probeOpencodeModelAvailability,
|
|
390
489
|
readAgentHubSettings,
|
|
490
|
+
readHrKnownModelIds,
|
|
391
491
|
readNativeAgentOverrides,
|
|
392
492
|
readNativePluginEntries,
|
|
393
493
|
readWorkflowInjectionConfig,
|
|
@@ -395,6 +495,7 @@ export {
|
|
|
395
495
|
recommendedHrBootstrapVariant,
|
|
396
496
|
resolveHrBootstrapAgentModels,
|
|
397
497
|
settingsPathForRoot,
|
|
498
|
+
validateHrAgentModelConfiguration,
|
|
398
499
|
workflowInjectionPathForRoot,
|
|
399
500
|
writeAgentHubSettings
|
|
400
501
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile, access } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { readAgentHubSettings } from "../../composer/settings.js";
|
|
4
|
+
import { validateModelIdentifier } from "../../composer/model-utils.js";
|
|
4
5
|
const readJson = async (filePath) => {
|
|
5
6
|
const content = await readFile(filePath, "utf-8");
|
|
6
7
|
return JSON.parse(content);
|
|
@@ -98,8 +99,28 @@ async function runDiagnostics(targetRoot) {
|
|
|
98
99
|
if (omoIssue) {
|
|
99
100
|
report.issues.push(omoIssue);
|
|
100
101
|
}
|
|
102
|
+
for (const issue of diagnoseInvalidModelSyntax(settings)) {
|
|
103
|
+
report.issues.push(issue);
|
|
104
|
+
}
|
|
101
105
|
return report;
|
|
102
106
|
}
|
|
107
|
+
const diagnoseInvalidModelSyntax = (settings) => {
|
|
108
|
+
if (!settings?.agents) return [];
|
|
109
|
+
const issues = [];
|
|
110
|
+
for (const [agentName, agent] of Object.entries(settings.agents)) {
|
|
111
|
+
if (typeof agent.model !== "string" || agent.model.trim().length === 0) continue;
|
|
112
|
+
const syntax = validateModelIdentifier(agent.model);
|
|
113
|
+
if (!syntax.ok) {
|
|
114
|
+
issues.push({
|
|
115
|
+
type: "model_invalid_syntax",
|
|
116
|
+
severity: "error",
|
|
117
|
+
message: `Agent '${agentName}' has an invalid model override: ${syntax.message}`,
|
|
118
|
+
details: { agentName, model: agent.model }
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return issues;
|
|
123
|
+
};
|
|
103
124
|
async function diagnoseMissingGuards(settings) {
|
|
104
125
|
if (!settings) return REQUIRED_GUARDS;
|
|
105
126
|
const existingGuards = Object.keys(settings.guards || {});
|
|
@@ -3,9 +3,15 @@ import { readdir, readFile, access, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import {
|
|
5
5
|
loadNativeOpenCodeConfig,
|
|
6
|
+
probeOpencodeModelAvailability,
|
|
7
|
+
readHrKnownModelIds,
|
|
6
8
|
readAgentHubSettings,
|
|
7
9
|
writeAgentHubSettings
|
|
8
10
|
} from "../../composer/settings.js";
|
|
11
|
+
import {
|
|
12
|
+
validateModelAgainstCatalog,
|
|
13
|
+
validateModelIdentifier
|
|
14
|
+
} from "../../composer/model-utils.js";
|
|
9
15
|
import {
|
|
10
16
|
fixMissingGuards,
|
|
11
17
|
createBundleForSoul,
|
|
@@ -686,6 +692,19 @@ async function updateAgentModelOverride(targetRoot, agentName, model) {
|
|
|
686
692
|
await writeAgentHubSettings(targetRoot, settings);
|
|
687
693
|
return `Cleared model override for '${agentName}'.`;
|
|
688
694
|
}
|
|
695
|
+
const syntax = validateModelIdentifier(trimmed);
|
|
696
|
+
if (!syntax.ok) {
|
|
697
|
+
return `${syntax.message} Use provider/model format or leave blank to clear the override.`;
|
|
698
|
+
}
|
|
699
|
+
const knownModels = await readHrKnownModelIds(targetRoot);
|
|
700
|
+
const catalog = validateModelAgainstCatalog(trimmed, knownModels);
|
|
701
|
+
if (!catalog.ok) {
|
|
702
|
+
return `${catalog.message} Sync HR sources or choose a listed model, then try again.`;
|
|
703
|
+
}
|
|
704
|
+
const availability = await probeOpencodeModelAvailability(trimmed);
|
|
705
|
+
if (!availability.available) {
|
|
706
|
+
return `${availability.message} Pick another model or clear the override.`;
|
|
707
|
+
}
|
|
689
708
|
settings.agents[agentName] = {
|
|
690
709
|
...existing,
|
|
691
710
|
model: trimmed
|
|
@@ -41,7 +41,10 @@ $HR_HOME/staging/<package-id>/
|
|
|
41
41
|
- Prefer namespaced bundle/soul/profile names for adapted teams or rewrites so the package does not overwrite shared starter assets like `plan`, `build`, or `explore` unless the human explicitly requests replacement.
|
|
42
42
|
- If a staged profile sets `defaultAgent`, it must use the staged bundle's `agent.name` value, not the bundle filename. This matters when bundle filenames are namespaced but `agent.name` is shorter.
|
|
43
43
|
- Before final assembly, confirm whether the operator wants to keep default opencode agents such as `general`, `explore`, `plan`, and `build`. If not, the staged profile must set `"nativeAgentPolicy": "team-only"`. This suppresses host native agent merges and emits `disable: true` overrides for default opencode agents that are not supplied by the staged team itself.
|
|
44
|
-
-
|
|
44
|
+
- If `nativeAgentPolicy` is `team-only` and the staged bundle set does not already provide `explore`, automatically include the built-in hidden `explore` subagent so the team retains investigation coverage without another user prompt.
|
|
45
|
+
- Before final assembly, MUST verify the staged bundle set includes at least one `agent.mode: "primary"` agent that is not hidden. If all sourced candidates are subagent-style, either add/create a primary host agent or keep native agents visible. Never stage an all-subagent team as `team-only`.
|
|
46
|
+
- If a prior or external flow has set `promotion_preferences.set_default_profile`, preserve it in `handoff.json`. Do not proactively ask the operator about default-profile preferences during assembly.
|
|
47
|
+
- If AI models are still unresolved when final assembly begins, stop and confirm the exact model choice here before writing staged agent defaults. Model confirmation must use opencode environment availability probing, not the synced inventory catalog.
|
|
45
48
|
- If the operator specifies a model variant such as `xhigh`, `high`, or `thinking`, store it canonically as `agent.model: "provider/model"` plus `agent.variant: "..."`. For backward compatibility, combined strings like `"provider/model xhigh"` may still be accepted on read, but staged output should prefer the split form.
|
|
46
49
|
- The package must be promotable by:
|
|
47
50
|
|
|
@@ -26,7 +26,7 @@ Verify that a staged package is understandable and safe before the human operato
|
|
|
26
26
|
7. deployment role correctness
|
|
27
27
|
8. unresolved risks / open decisions
|
|
28
28
|
9. import-root and assemble-only validation
|
|
29
|
-
10. runtime
|
|
29
|
+
10. runtime configuration confirmation
|
|
30
30
|
11. protocol-compliance checkpoints
|
|
31
31
|
|
|
32
32
|
## Clarity Definitions
|
|
@@ -69,13 +69,14 @@ Keep `final-checklist.md` compact and explicit. Use this shape:
|
|
|
69
69
|
| descriptions are operator-readable | pass/fail |
|
|
70
70
|
| MCP registrations resolve to staged servers or blocker | pass/fail |
|
|
71
71
|
| handoff clearly separates test/use/promote | pass/fail |
|
|
72
|
-
| model preferences were
|
|
72
|
+
| model preferences were confirmed before assembly | pass/fail |
|
|
73
73
|
| final names were user-confirmed | pass/fail |
|
|
74
74
|
| specialized work was delegated | pass/fail |
|
|
75
|
-
| staged model ids
|
|
75
|
+
| staged model ids confirmed via opencode environment | pass/fail |
|
|
76
76
|
| profile defaultAgent matches bundle agent.name | pass/fail |
|
|
77
|
+
| team includes at least one primary, non-hidden agent | pass/fail |
|
|
77
78
|
| default opencode agent policy confirmed | pass/fail |
|
|
78
|
-
| default
|
|
79
|
+
| default-profile preference recorded if present | pass/fail |
|
|
79
80
|
| no host project mutations | pass/fail |
|
|
80
81
|
overall: READY FOR HUMAN CONFIRMATION | READY WITH CAVEATS | NOT READY
|
|
81
82
|
blocker: <description or none>
|
|
@@ -89,10 +90,11 @@ The package cannot be marked ready unless the verifier confirms:
|
|
|
89
90
|
2. the manual import fallback points to `<package-root>/agenthub-home` and is described as advanced/manual only
|
|
90
91
|
3. all referenced skills either exist inside the staged `skills/` directory or are explicitly rejected as missing blockers
|
|
91
92
|
4. `python3 $HR_HOME/bin/validate_staged_package.py $HR_HOME/staging/<package-id>` passes
|
|
92
|
-
5. the package explicitly records whether default opencode agents are kept or hidden, and
|
|
93
|
-
6. staged model ids either
|
|
93
|
+
5. the package explicitly records whether default opencode agents are kept or hidden, and if `promotion_preferences.set_default_profile` is present, it is consistent
|
|
94
|
+
6. staged model ids are either confirmed available in the opencode environment or are called out as blockers/caveats for human review
|
|
94
95
|
7. if any bundle references MCP tools, the staged package includes the referenced `mcp/*.json` files, the required `mcp-servers/` implementation files, and `mcp-servers/package.json` when runtime dependencies are needed
|
|
95
96
|
8. the handoff clearly shows how to test/use the staged profile in a workspace before promote, and promote is not described as mandatory for workspace use
|
|
96
97
|
9. if a profile sets `defaultAgent`, that value exactly matches one of the staged bundles' `agent.name` values (not just the bundle filename)
|
|
98
|
+
10. the staged team includes at least one non-hidden primary agent, and any `team-only` profile keeps at least one such primary agent available to the operator
|
|
97
99
|
|
|
98
100
|
If bundle metadata contains fake runtime keys such as `optional_skills` or `runtime_conditional_skills`, mark the package `NOT READY` until they are removed or rewritten as plain documentation outside runtime bundle semantics.
|
|
@@ -37,10 +37,9 @@ Every staffing plan must include:
|
|
|
37
37
|
- `composition`
|
|
38
38
|
- `required_skills`
|
|
39
39
|
- `required_tools`
|
|
40
|
-
- `suggested_model_provider`
|
|
41
|
-
- `proposed_agent_models` (initial proposal only; final per-agent defaults require user confirmation later)
|
|
42
40
|
- `draft_names` (draft agent names and draft profile name for user review)
|
|
43
41
|
- `risks`
|
|
42
|
+
- `team_size_advisory` (included when recommended team exceeds four agents)
|
|
44
43
|
- `next_action`
|
|
45
44
|
|
|
46
45
|
## Compact Summary Shape
|
|
@@ -57,10 +56,11 @@ composition:
|
|
|
57
56
|
draft_names:
|
|
58
57
|
- seat: ... | proposed_agent_name: ... | reason: ...
|
|
59
58
|
- profile: ... | reason: ...
|
|
60
|
-
|
|
61
|
-
-
|
|
59
|
+
required_skills:
|
|
60
|
+
- skill: ... | why: ...
|
|
62
61
|
risks:
|
|
63
62
|
- ...
|
|
63
|
+
team_size_advisory: <omit if ≤4 agents; otherwise: "Recommend one to two primary agents with remaining agents as subagents.">
|
|
64
64
|
next_action: ...
|
|
65
65
|
```
|
|
66
66
|
|
|
@@ -75,11 +75,14 @@ For each recommended entry, specify:
|
|
|
75
75
|
|
|
76
76
|
## Decision Rules
|
|
77
77
|
|
|
78
|
+
- At least one staffing-plan entry must have `deployment_role: primary-capable`.
|
|
78
79
|
- Prefer the smallest team that can cover planning, sourcing/exploration, implementation, audit, verification, and documentation.
|
|
80
|
+
- If the recommended team has more than four agents, include a `team_size_advisory` noting that the team should be structured around one to two primary agents with the remaining agents deployed as subagents. Do not recommend more than two primary-capable agents unless the user explicitly requests it.
|
|
81
|
+
- When multiple candidates can cover the same seat, prefer a pure-soul agent with attached skills over a mixed soul+skill agent. Use a mixed soul+skill agent only when the specialized workflow cannot be cleanly separated, the source is tightly fused, or the user explicitly wants that mixed form.
|
|
79
82
|
- Prefer local worker cards from `$HR_HOME/inventory/workers/` with `inventory_status = available`.
|
|
80
83
|
- Treat `draft` worker cards as sourcing inputs that still need review or explicit operator acceptance.
|
|
81
84
|
- Exclude `retired` worker cards from recommended staffing compositions.
|
|
82
|
-
- Treat user-supplied model names as advisory
|
|
85
|
+
- Treat user-supplied model names as advisory. Model confirmation happens during staging via opencode environment availability probing, not during staffing planning. If a user-supplied name looks syntactically invalid, note that, but do not attempt catalog-based validation here.
|
|
83
86
|
- If multiple valid compositions exist, present them as options rather than pretending one is certain.
|
|
84
87
|
- If a skill host is unresolved, say so plainly.
|
|
85
88
|
- Draft names must be treated as proposals only. The parent HR agent must show them to the user and get confirmation before adaptation starts.
|
|
@@ -168,6 +168,7 @@ def validate_profile_default_agents(import_root: Path) -> None:
|
|
|
168
168
|
)
|
|
169
169
|
|
|
170
170
|
references: list[tuple[str, str]] = []
|
|
171
|
+
primary_agent_names: set[str] = set()
|
|
171
172
|
missing_bundles: list[str] = []
|
|
172
173
|
for raw_bundle_name in bundle_names:
|
|
173
174
|
if not isinstance(raw_bundle_name, str) or not raw_bundle_name.strip():
|
|
@@ -181,11 +182,16 @@ def validate_profile_default_agents(import_root: Path) -> None:
|
|
|
181
182
|
continue
|
|
182
183
|
agent = bundle.get("agent", {})
|
|
183
184
|
agent_name = agent.get("name") if isinstance(agent, dict) else None
|
|
185
|
+
agent_mode = agent.get("mode") if isinstance(agent, dict) else None
|
|
186
|
+
agent_hidden = agent.get("hidden") if isinstance(agent, dict) else None
|
|
184
187
|
if not isinstance(agent_name, str) or not agent_name.strip():
|
|
185
188
|
raise SystemExit(
|
|
186
189
|
f"Bundle '{bundle_name}' is missing required agent.name; profile '{profile_name}' cannot use it."
|
|
187
190
|
)
|
|
188
|
-
|
|
191
|
+
normalized_agent_name = agent_name.strip()
|
|
192
|
+
references.append((bundle_name, normalized_agent_name))
|
|
193
|
+
if agent_mode == "primary" and agent_hidden is not True:
|
|
194
|
+
primary_agent_names.add(normalized_agent_name)
|
|
189
195
|
|
|
190
196
|
if missing_bundles:
|
|
191
197
|
detail = ", ".join(missing_bundles)
|
|
@@ -199,6 +205,10 @@ def validate_profile_default_agents(import_root: Path) -> None:
|
|
|
199
205
|
raise SystemExit(
|
|
200
206
|
f"Profile '{profile_name}' uses nativeAgentPolicy 'team-only' and must set defaultAgent explicitly."
|
|
201
207
|
)
|
|
208
|
+
if native_agent_policy == "team-only" and not primary_agent_names:
|
|
209
|
+
raise SystemExit(
|
|
210
|
+
f"Profile '{profile_name}' uses nativeAgentPolicy 'team-only' but does not include at least one primary, non-hidden agent."
|
|
211
|
+
)
|
|
202
212
|
if default_agent is None:
|
|
203
213
|
continue
|
|
204
214
|
if not isinstance(default_agent, str) or not default_agent.strip():
|
|
@@ -224,6 +234,10 @@ def validate_profile_default_agents(import_root: Path) -> None:
|
|
|
224
234
|
|
|
225
235
|
agent_names = {agent_name for _, agent_name in references}
|
|
226
236
|
if normalized_default_agent in agent_names:
|
|
237
|
+
if normalized_default_agent not in primary_agent_names:
|
|
238
|
+
raise SystemExit(
|
|
239
|
+
f"Profile '{profile_name}' defaultAgent '{normalized_default_agent}' must point to a primary, non-hidden agent."
|
|
240
|
+
)
|
|
227
241
|
continue
|
|
228
242
|
|
|
229
243
|
available = ", ".join(sorted(agent_names)) or "(none)"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-agenthub",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A control plane for organizing, composing, and activating OpenCode agents, skills, profiles, and bundles.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/plugins/opencode-agenthub.js",
|