@hybridlabor-api/aos 4.5.1 → 4.6.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/.agents/rules/antigravity-rtk-rules.md +32 -0
- package/.claude/agents/architect.md +0 -1
- package/.claude/agents/database-reviewer.md +3 -102
- package/.claude/agents/go-build-resolver.md +3 -105
- package/.claude/agents/godmode-engineering.md +1 -2
- package/.claude/agents/godmode-media-eventtech.md +1 -2
- package/.claude/agents/godmode-shipping.md +2 -3
- package/.claude/agents/godmode-ui-ux.md +2 -3
- package/.claude/agents/opensource-forker.md +4 -210
- package/.claude/agents/opensource-sanitizer.md +3 -199
- package/.claude/agents/reviewer.md +1 -2
- package/.claude/agents/security-reviewer.md +3 -119
- package/.claude/agents/silent-failure-hunter.md +3 -61
- package/.claude/agents/techlead.md +1 -2
- package/.opencode/agents/architect.md +1 -0
- package/.opencode/agents/database-reviewer.md +10 -0
- package/.opencode/agents/go-build-resolver.md +10 -0
- package/.opencode/agents/godmode-engineering.md +1 -0
- package/.opencode/agents/godmode-media-eventtech.md +1 -0
- package/.opencode/agents/godmode-shipping.md +2 -1
- package/.opencode/agents/godmode-ui-ux.md +1 -0
- package/.opencode/agents/opensource-forker.md +10 -0
- package/.opencode/agents/opensource-sanitizer.md +10 -0
- package/.opencode/agents/reviewer.md +1 -0
- package/.opencode/agents/security-reviewer.md +10 -0
- package/.opencode/agents/silent-failure-hunter.md +10 -0
- package/.opencode/agents/techlead.md +1 -0
- package/.opencode/plugins/bdb-aos.js +171 -0
- package/docs/email/aos-4.5.0-update-banner.html +211 -0
- package/installer.js +261 -30
- package/package.json +1 -1
- package/skills/global_config/aos-project-init/SKILL.md +2 -0
- package/skills/global_config/subagent-setup/SKILL.md +71 -0
- package/skills/global_config/subagent-setup/scripts/setup-subagents.mjs +185 -0
package/installer.js
CHANGED
|
@@ -2919,17 +2919,83 @@ function yamlQuote(str) {
|
|
|
2919
2919
|
return `"${str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, ' ')}"`;
|
|
2920
2920
|
}
|
|
2921
2921
|
|
|
2922
|
+
// Canonical model tiers mapped to verified provider-specific model IDs for each harness.
|
|
2923
|
+
// Protects against vendor model churn and provides seamless cross-harness defaults.
|
|
2924
|
+
const CANONICAL_TIERS = {
|
|
2925
|
+
reasoning_max: {
|
|
2926
|
+
claude: 'opus',
|
|
2927
|
+
antigravity: 'gemini-3.1-pro-high',
|
|
2928
|
+
opencode: 'opencode/muse-spark-1.3-contributor-free',
|
|
2929
|
+
codex: 'o3-mini'
|
|
2930
|
+
},
|
|
2931
|
+
standard_fast: {
|
|
2932
|
+
claude: 'sonnet',
|
|
2933
|
+
antigravity: 'gemini-3.8-flash-high',
|
|
2934
|
+
opencode: 'opencode/muse-spark-1.3-contributor-free',
|
|
2935
|
+
codex: 'gpt-4o'
|
|
2936
|
+
},
|
|
2937
|
+
trivial_low: {
|
|
2938
|
+
claude: 'haiku',
|
|
2939
|
+
antigravity: 'gemini-3.8-flash-low',
|
|
2940
|
+
opencode: 'opencode/muse-spark-1.3-contributor-free',
|
|
2941
|
+
codex: 'gpt-4o-mini'
|
|
2942
|
+
}
|
|
2943
|
+
};
|
|
2944
|
+
|
|
2945
|
+
// Loads .aos/pipeline.json or .aos/project.json containing role-to-model/harness mappings.
|
|
2946
|
+
function loadPipelineConfig(projectDir = currentDir) {
|
|
2947
|
+
const candidates = [
|
|
2948
|
+
path.join(projectDir, '.aos', 'pipeline.json'),
|
|
2949
|
+
path.join(projectDir, '.aos', 'project.json'),
|
|
2950
|
+
path.join(homeDir, '.aos', 'pipeline.json')
|
|
2951
|
+
];
|
|
2952
|
+
for (const p of candidates) {
|
|
2953
|
+
if (fs.existsSync(p)) {
|
|
2954
|
+
try {
|
|
2955
|
+
const data = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
2956
|
+
if (data && data.pipeline) return data.pipeline;
|
|
2957
|
+
} catch {}
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
return null;
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
// Resolves model ID, tier, and enabled status for a given agent role and target harness.
|
|
2964
|
+
function resolveAgentConfig(agentSlug, harness, pipelineConfig = null) {
|
|
2965
|
+
const normSlug = agentSlug.toLowerCase().replace(/-/g, '_');
|
|
2966
|
+
const roleCfg = pipelineConfig ? (pipelineConfig[normSlug] || pipelineConfig[agentSlug]) : null;
|
|
2967
|
+
if (roleCfg) {
|
|
2968
|
+
if (roleCfg.model) {
|
|
2969
|
+
return {
|
|
2970
|
+
model: roleCfg.model,
|
|
2971
|
+
tier: roleCfg.tier || 'custom',
|
|
2972
|
+
enabled: roleCfg.enabled !== false,
|
|
2973
|
+
harness: roleCfg.harness || harness
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
if (roleCfg.tier && CANONICAL_TIERS[roleCfg.tier] && CANONICAL_TIERS[roleCfg.tier][harness]) {
|
|
2977
|
+
return {
|
|
2978
|
+
model: CANONICAL_TIERS[roleCfg.tier][harness],
|
|
2979
|
+
tier: roleCfg.tier,
|
|
2980
|
+
enabled: roleCfg.enabled !== false,
|
|
2981
|
+
harness: roleCfg.harness || harness
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
const defaultTier = (normSlug === 'architect' || normSlug === 'reviewer') ? 'reasoning_max' : 'standard_fast';
|
|
2986
|
+
const fallbackModel = (CANONICAL_TIERS[defaultTier] && CANONICAL_TIERS[defaultTier][harness]) || 'inherit';
|
|
2987
|
+
return { model: fallbackModel, tier: defaultTier, enabled: true, harness };
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2922
2990
|
// Generates .claude/agents/<name>.md — Claude Code's native subagent format.
|
|
2923
2991
|
// Only frontmatter fields confirmed against code.claude.com/docs/en/sub-agents
|
|
2924
|
-
// are emitted (name, description, model).
|
|
2925
|
-
|
|
2926
|
-
// list -- an unverified mapping there would silently over- or under-scope a
|
|
2927
|
-
// subagent's tool access, which is worse than inheriting the default set.
|
|
2928
|
-
function compileClaudeAgents(agents, targetDir) {
|
|
2992
|
+
// are emitted (name, description, model).
|
|
2993
|
+
function compileClaudeAgents(agents, targetDir, pipelineConfig = null) {
|
|
2929
2994
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
2930
2995
|
for (const a of agents) {
|
|
2931
2996
|
const slug = a.name.toLowerCase().replace(/_/g, '-');
|
|
2932
|
-
const
|
|
2997
|
+
const resolved = resolveAgentConfig(slug, 'claude', pipelineConfig);
|
|
2998
|
+
const model = (resolved && resolved.model) ? resolved.model.toLowerCase() : (a.model ? a.model.toLowerCase() : 'inherit');
|
|
2933
2999
|
const body = [
|
|
2934
3000
|
a.role,
|
|
2935
3001
|
a.skills.length ? `**Primary skills:** ${a.skills.join(', ')}` : null,
|
|
@@ -2950,18 +3016,13 @@ function compileClaudeAgents(agents, targetDir) {
|
|
|
2950
3016
|
}
|
|
2951
3017
|
}
|
|
2952
3018
|
|
|
2953
|
-
// Generates .opencode/agents/<name>.md.
|
|
2954
|
-
|
|
2955
|
-
// `permission` are left unset: OpenCode's model-id format and its mapping
|
|
2956
|
-
// from an MCP-server list to `permission` keys are not verified against its
|
|
2957
|
-
// docs, so guessing either would risk emitting a value OpenCode silently
|
|
2958
|
-
// can't resolve. `mode: subagent` is used for every generated agent, since
|
|
2959
|
-
// none of these five is meant to be a primary/default agent a user talks to
|
|
2960
|
-
// directly.
|
|
2961
|
-
function compileOpenCodeAgents(agents, targetDir) {
|
|
3019
|
+
// Generates .opencode/agents/<name>.md. Emits description, mode: subagent, and model.
|
|
3020
|
+
function compileOpenCodeAgents(agents, targetDir, pipelineConfig = null) {
|
|
2962
3021
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
2963
3022
|
for (const a of agents) {
|
|
2964
3023
|
const slug = a.name.toLowerCase().replace(/_/g, '-');
|
|
3024
|
+
const resolved = resolveAgentConfig(slug, 'opencode', pipelineConfig);
|
|
3025
|
+
const model = resolved && resolved.model ? resolved.model : null;
|
|
2965
3026
|
const body = [
|
|
2966
3027
|
a.role,
|
|
2967
3028
|
a.skills.length ? `**Primary skills:** ${a.skills.join(', ')}` : null,
|
|
@@ -2969,18 +3030,54 @@ function compileOpenCodeAgents(agents, targetDir) {
|
|
|
2969
3030
|
a.output ? `**Output artifact(s):** ${a.output}` : null,
|
|
2970
3031
|
].filter(Boolean).join('\n\n');
|
|
2971
3032
|
|
|
2972
|
-
const
|
|
3033
|
+
const frontmatterLines = [
|
|
2973
3034
|
'---',
|
|
2974
3035
|
`description: ${yamlQuote(a.role)}`,
|
|
2975
3036
|
'mode: subagent',
|
|
2976
|
-
|
|
2977
|
-
|
|
3037
|
+
];
|
|
3038
|
+
if (model) {
|
|
3039
|
+
frontmatterLines.push(`model: ${model}`);
|
|
3040
|
+
}
|
|
3041
|
+
frontmatterLines.push('---', '');
|
|
3042
|
+
|
|
3043
|
+
fs.writeFileSync(path.join(targetDir, `${slug}.md`), frontmatterLines.join('\n') + body + '\n');
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
// Generates .codex/agents/<name>.toml and .codex/agents/<name>.md for ChatGPT Codex CLI.
|
|
3048
|
+
function compileCodexAgents(agents, targetDir, pipelineConfig = null) {
|
|
3049
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
3050
|
+
for (const a of agents) {
|
|
3051
|
+
const slug = a.name.toLowerCase().replace(/_/g, '-');
|
|
3052
|
+
const resolved = resolveAgentConfig(slug, 'codex', pipelineConfig);
|
|
3053
|
+
const model = resolved && resolved.model ? resolved.model : 'o3-mini';
|
|
3054
|
+
|
|
3055
|
+
const tomlContent = [
|
|
3056
|
+
`# Codex subagent configuration for ${slug}`,
|
|
3057
|
+
`name = "${slug}"`,
|
|
3058
|
+
`description = ${JSON.stringify(a.role)}`,
|
|
3059
|
+
`model = "${model}"`,
|
|
3060
|
+
`tier = "${resolved.tier || 'standard_fast'}"`,
|
|
3061
|
+
`enabled = ${resolved.enabled !== false}`,
|
|
3062
|
+
`prompt_file = "${slug}.md"`
|
|
2978
3063
|
].join('\n');
|
|
3064
|
+
fs.writeFileSync(path.join(targetDir, `${slug}.toml`), tomlContent + '\n');
|
|
2979
3065
|
|
|
2980
|
-
|
|
3066
|
+
const body = [
|
|
3067
|
+
`# Role: ${a.name}`,
|
|
3068
|
+
a.role,
|
|
3069
|
+
a.skills.length ? `**Primary skills:** ${a.skills.join(', ')}` : null,
|
|
3070
|
+
a.mcpServers.length ? `**MCP servers used:** ${a.mcpServers.join(', ')}` : null,
|
|
3071
|
+
a.output ? `**Output artifact(s):** ${a.output}` : null,
|
|
3072
|
+
'',
|
|
3073
|
+
'## Instructions',
|
|
3074
|
+
a.systemPrompt
|
|
3075
|
+
].filter(Boolean).join('\n\n');
|
|
3076
|
+
fs.writeFileSync(path.join(targetDir, `${slug}.md`), body + '\n');
|
|
2981
3077
|
}
|
|
2982
3078
|
}
|
|
2983
3079
|
|
|
3080
|
+
|
|
2984
3081
|
function injectHarnessRules() {
|
|
2985
3082
|
const geminiMdSrc = path.join(srcDir, 'GEMINI.md');
|
|
2986
3083
|
const agentsMdSrc = path.join(srcDir, '.agents', 'agents.md');
|
|
@@ -3034,11 +3131,16 @@ function injectHarnessRules() {
|
|
|
3034
3131
|
const agyAgentsDir = path.join(geminiDir, 'config', 'agents');
|
|
3035
3132
|
if (!fs.existsSync(agyAgentsDir)) fs.mkdirSync(agyAgentsDir, { recursive: true });
|
|
3036
3133
|
|
|
3134
|
+
const pipelineConfig = loadPipelineConfig();
|
|
3037
3135
|
const agents = parseAgentsMd(agentsMdContent);
|
|
3038
3136
|
for (const a of agents) {
|
|
3137
|
+
const slug = a.name.toLowerCase().replace(/_/g, '-');
|
|
3138
|
+
const resolved = resolveAgentConfig(slug, 'antigravity', pipelineConfig);
|
|
3039
3139
|
const agentConfig = {
|
|
3040
3140
|
name: a.name,
|
|
3041
3141
|
description: a.role,
|
|
3142
|
+
model: resolved.model,
|
|
3143
|
+
model_tier: resolved.tier,
|
|
3042
3144
|
enable_write_tools: true,
|
|
3043
3145
|
enable_subagent_tools: true,
|
|
3044
3146
|
enable_mcp_tools: true,
|
|
@@ -3053,22 +3155,34 @@ function injectHarnessRules() {
|
|
|
3053
3155
|
|
|
3054
3156
|
installStep('compile Claude Code subagents', () => {
|
|
3055
3157
|
if (agentsMdContent) {
|
|
3158
|
+
const pipelineConfig = loadPipelineConfig();
|
|
3056
3159
|
const agents = parseAgentsMd(agentsMdContent);
|
|
3057
3160
|
const claudeAgentsDir = path.join(homeDir, '.claude', 'agents');
|
|
3058
|
-
compileClaudeAgents(agents, claudeAgentsDir);
|
|
3161
|
+
compileClaudeAgents(agents, claudeAgentsDir, pipelineConfig);
|
|
3059
3162
|
log.step(`Compiled AGENTS.md to Claude Code subagents in ${claudeAgentsDir}`);
|
|
3060
3163
|
}
|
|
3061
3164
|
}, 'Claude Code agents unchanged');
|
|
3062
3165
|
|
|
3063
3166
|
installStep('compile OpenCode subagents', () => {
|
|
3064
3167
|
if (agentsMdContent) {
|
|
3168
|
+
const pipelineConfig = loadPipelineConfig();
|
|
3065
3169
|
const agents = parseAgentsMd(agentsMdContent);
|
|
3066
3170
|
const opencodeAgentsDir = path.join(homeDir, '.opencode', 'agents');
|
|
3067
|
-
compileOpenCodeAgents(agents, opencodeAgentsDir);
|
|
3171
|
+
compileOpenCodeAgents(agents, opencodeAgentsDir, pipelineConfig);
|
|
3068
3172
|
log.step(`Compiled AGENTS.md to OpenCode subagents in ${opencodeAgentsDir}`);
|
|
3069
3173
|
}
|
|
3070
3174
|
}, 'OpenCode agents unchanged');
|
|
3071
3175
|
|
|
3176
|
+
installStep('compile Codex subagents', () => {
|
|
3177
|
+
if (agentsMdContent) {
|
|
3178
|
+
const pipelineConfig = loadPipelineConfig();
|
|
3179
|
+
const agents = parseAgentsMd(agentsMdContent);
|
|
3180
|
+
const codexAgentsDir = path.join(homeDir, '.codex', 'agents');
|
|
3181
|
+
compileCodexAgents(agents, codexAgentsDir, pipelineConfig);
|
|
3182
|
+
log.step(`Compiled AGENTS.md to Codex subagents in ${codexAgentsDir}`);
|
|
3183
|
+
}
|
|
3184
|
+
}, 'Codex agents unchanged');
|
|
3185
|
+
|
|
3072
3186
|
const claudeMdPath = path.join(currentDir, 'CLAUDE.md');
|
|
3073
3187
|
installStep(`sync ${claudeMdPath}`, () => {
|
|
3074
3188
|
let claudeContent = fs.existsSync(claudeMdPath) ? fs.readFileSync(claudeMdPath, 'utf8') : '';
|
|
@@ -3213,6 +3327,24 @@ function installGlobalHooks() {
|
|
|
3213
3327
|
copyDirRecursiveSync(workflowsSrc, codexWorkflowsDir);
|
|
3214
3328
|
}
|
|
3215
3329
|
mergeCodexTomlHooks(path.join(homeDir, '.codex', 'config.toml'));
|
|
3330
|
+
|
|
3331
|
+
// 4. OpenCode CLI
|
|
3332
|
+
const opencodeDir = process.platform === 'win32'
|
|
3333
|
+
? path.join(process.env.APPDATA || homeDir, 'opencode')
|
|
3334
|
+
: path.join(homeDir, '.config', 'opencode');
|
|
3335
|
+
const opencodePluginSrc = path.join(srcDir, '.opencode', 'plugins', 'bdb-aos.js');
|
|
3336
|
+
if (fs.existsSync(opencodePluginSrc)) {
|
|
3337
|
+
const opencodePluginsDir = path.join(opencodeDir, 'plugins');
|
|
3338
|
+
const opencodePluginDest = path.join(opencodePluginsDir, 'bdb-aos.js');
|
|
3339
|
+
try {
|
|
3340
|
+
fs.mkdirSync(opencodePluginsDir, { recursive: true });
|
|
3341
|
+
fs.copyFileSync(opencodePluginSrc, opencodePluginDest);
|
|
3342
|
+
try { fs.chmodSync(opencodePluginDest, 0o644); } catch (e) { logDebug(e, 'chmod opencode plugin'); }
|
|
3343
|
+
log.step(`Installed OpenCode plugin to ${opencodePluginDest}`);
|
|
3344
|
+
} catch (e) {
|
|
3345
|
+
log.warn(`Could not install OpenCode plugin: ${e.message}`);
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3216
3348
|
}
|
|
3217
3349
|
|
|
3218
3350
|
// Merge the BDB hooks -- the two gates plus the memB ambient-memory hook --
|
|
@@ -3495,10 +3627,20 @@ function installProjectHarness() {
|
|
|
3495
3627
|
}, 'go-gate / graph-gate enforcement stays inactive in this project.');
|
|
3496
3628
|
|
|
3497
3629
|
installStep('copy agent definitions into project', () => {
|
|
3498
|
-
const
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3630
|
+
const agentsMdSrc = path.join(srcDir, '.agents', 'agents.md');
|
|
3631
|
+
const pipelineConfig = loadPipelineConfig(currentDir);
|
|
3632
|
+
if (fs.existsSync(agentsMdSrc)) {
|
|
3633
|
+
const agents = parseAgentsMd(fs.readFileSync(agentsMdSrc, 'utf8'));
|
|
3634
|
+
compileClaudeAgents(agents, path.join(projectClaudeDir, 'agents'), pipelineConfig);
|
|
3635
|
+
compileOpenCodeAgents(agents, path.join(currentDir, '.opencode', 'agents'), pipelineConfig);
|
|
3636
|
+
compileCodexAgents(agents, path.join(projectCodexDir, 'agents'), pipelineConfig);
|
|
3637
|
+
log.step(`Compiled project agent definitions for Claude, OpenCode, and Codex`);
|
|
3638
|
+
} else {
|
|
3639
|
+
const agentsSrc = path.join(srcDir, '.claude', 'agents');
|
|
3640
|
+
if (fs.existsSync(agentsSrc)) {
|
|
3641
|
+
copyDirRecursiveSync(agentsSrc, path.join(projectClaudeDir, 'agents'));
|
|
3642
|
+
log.step(`Copied agent definitions to ${path.join(projectClaudeDir, 'agents')}`);
|
|
3643
|
+
}
|
|
3502
3644
|
}
|
|
3503
3645
|
}, 'The dispatcher runs, but its agent-file pointers resolve to nothing.');
|
|
3504
3646
|
|
|
@@ -4022,17 +4164,98 @@ async function universalHarnessSync(primaryMcpConfigPath) {
|
|
|
4022
4164
|
const existing = readJsoncFile(targetPath);
|
|
4023
4165
|
const data = existing && existing.mcp ? existing : Object.assign({}, existing || {}, { mcp: {} });
|
|
4024
4166
|
if (masterMcpData.mcpServers) {
|
|
4167
|
+
// OpenCode provider APIs (e.g. OpenAI-compatible, Console) enforce tool name length limits
|
|
4168
|
+
// (e.g. max 64 chars) and context limits. Loading 20+ MCPs causes provider errors (e.g. 73-char tool names).
|
|
4169
|
+
// OpenCode uses a slim profile: only core servers (memb_mcp, zavora_computer_use) are enabled by default.
|
|
4170
|
+
const OPENCODE_DEFAULT_SLIM = new Set(['memb_mcp', 'zavora_computer_use']);
|
|
4171
|
+
const existingMcp = existing && existing.mcp ? existing.mcp : {};
|
|
4172
|
+
const hasExistingKeys = Object.keys(existingMcp).length > 0;
|
|
4173
|
+
|
|
4025
4174
|
for (const [key, val] of Object.entries(masterMcpData.mcpServers)) {
|
|
4026
|
-
const
|
|
4027
|
-
const
|
|
4175
|
+
const rawCmd = Array.isArray(val.command) ? val.command : [val.command];
|
|
4176
|
+
const rawArgs = Array.isArray(val.args) ? val.args : [];
|
|
4177
|
+
const fullCmd = [...rawCmd, ...rawArgs].map(c => {
|
|
4178
|
+
if (c === '__PYTHON_BIN__') {
|
|
4179
|
+
return process.platform === 'win32'
|
|
4180
|
+
? path.join(homeDir, '.gemini', 'config', 'mcps', 'memb-mcp', '.venv', 'Scripts', 'python.exe')
|
|
4181
|
+
: path.join(homeDir, '.gemini', 'config', 'mcps', 'memb-mcp', '.venv', 'bin', 'python');
|
|
4182
|
+
}
|
|
4183
|
+
return c;
|
|
4184
|
+
});
|
|
4185
|
+
|
|
4186
|
+
const existingEntry = existingMcp[key];
|
|
4187
|
+
|
|
4188
|
+
// If existing config is intentionally slimmed (has keys, but omitted this one), don't resurrect unless in slim set
|
|
4189
|
+
if (hasExistingKeys && !existingEntry && !OPENCODE_DEFAULT_SLIM.has(key)) {
|
|
4190
|
+
continue;
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
// Preserve user's explicit enabled/disabled setting; otherwise default to true only for slim set
|
|
4194
|
+
const isEnabled = existingEntry && typeof existingEntry.enabled === 'boolean'
|
|
4195
|
+
? existingEntry.enabled
|
|
4196
|
+
: OPENCODE_DEFAULT_SLIM.has(key);
|
|
4197
|
+
|
|
4198
|
+
let envObj = val.environment || val.env;
|
|
4199
|
+
if (envObj) {
|
|
4200
|
+
envObj = Object.assign({}, envObj);
|
|
4201
|
+
for (const [eKey, eVal] of Object.entries(envObj)) {
|
|
4202
|
+
if (eVal === '__GEMINI_API_KEY__') {
|
|
4203
|
+
envObj[eKey] = '${GEMINI_API_KEY}';
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4028
4208
|
data.mcp[key] = {
|
|
4029
4209
|
type: "local",
|
|
4030
|
-
command:
|
|
4031
|
-
enabled:
|
|
4032
|
-
...(
|
|
4210
|
+
command: fullCmd,
|
|
4211
|
+
enabled: isEnabled,
|
|
4212
|
+
...(envObj ? { environment: envObj } : {})
|
|
4033
4213
|
};
|
|
4034
4214
|
}
|
|
4035
4215
|
}
|
|
4216
|
+
|
|
4217
|
+
// Wire BDB AOS Plugin for OpenCode
|
|
4218
|
+
const opencodeDir = path.dirname(targetPath);
|
|
4219
|
+
const pluginsDir = path.join(opencodeDir, 'plugins');
|
|
4220
|
+
const pluginFile = path.join(pluginsDir, 'bdb-aos.js');
|
|
4221
|
+
const pluginSrc = path.join(srcDir, '.opencode', 'plugins', 'bdb-aos.js');
|
|
4222
|
+
|
|
4223
|
+
try {
|
|
4224
|
+
if (fs.existsSync(pluginSrc)) {
|
|
4225
|
+
fs.mkdirSync(pluginsDir, { recursive: true });
|
|
4226
|
+
fs.copyFileSync(pluginSrc, pluginFile);
|
|
4227
|
+
try { fs.chmodSync(pluginFile, 0o644); } catch (e) { logDebug(e, 'chmod pluginFile'); }
|
|
4228
|
+
}
|
|
4229
|
+
} catch (pluginErr) {
|
|
4230
|
+
log.warn(`Could not install OpenCode plugin: ${pluginErr.message}`);
|
|
4231
|
+
}
|
|
4232
|
+
|
|
4233
|
+
// Register plugin in opencode.jsonc if plugin file exists
|
|
4234
|
+
if (fs.existsSync(pluginFile)) {
|
|
4235
|
+
if (!Array.isArray(data.plugin)) {
|
|
4236
|
+
data.plugin = [];
|
|
4237
|
+
}
|
|
4238
|
+
const pluginPathNormalized = pluginFile.replace(/\\/g, '/');
|
|
4239
|
+
const alreadyRegistered = data.plugin.some(p => {
|
|
4240
|
+
const str = typeof p === 'string' ? p : (Array.isArray(p) ? p[0] : '');
|
|
4241
|
+
return str.includes('bdb-aos');
|
|
4242
|
+
});
|
|
4243
|
+
if (!alreadyRegistered) {
|
|
4244
|
+
data.plugin.push(pluginPathNormalized);
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4248
|
+
// Register skills paths for OpenCode
|
|
4249
|
+
if (!data.skills || typeof data.skills !== 'object') {
|
|
4250
|
+
data.skills = { paths: [".agents/skills"] };
|
|
4251
|
+
} else if (Array.isArray(data.skills.paths)) {
|
|
4252
|
+
if (!data.skills.paths.includes(".agents/skills")) {
|
|
4253
|
+
data.skills.paths.push(".agents/skills");
|
|
4254
|
+
}
|
|
4255
|
+
} else {
|
|
4256
|
+
data.skills.paths = [".agents/skills"];
|
|
4257
|
+
}
|
|
4258
|
+
|
|
4036
4259
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
4037
4260
|
fs.writeFileSync(targetPath, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
4038
4261
|
try { fs.chmodSync(targetPath, 0o600); } catch (e) { logDebug(e, 'chmod targetPath'); }
|
|
@@ -4616,4 +4839,12 @@ module.exports = {
|
|
|
4616
4839
|
flushSessionManifest,
|
|
4617
4840
|
copyDirRecursiveSync,
|
|
4618
4841
|
INSTALL_MANIFEST_PATH,
|
|
4842
|
+
// Agent compilers & pipeline helpers
|
|
4843
|
+
parseAgentsMd,
|
|
4844
|
+
compileClaudeAgents,
|
|
4845
|
+
compileOpenCodeAgents,
|
|
4846
|
+
compileCodexAgents,
|
|
4847
|
+
loadPipelineConfig,
|
|
4848
|
+
resolveAgentConfig,
|
|
4849
|
+
CANONICAL_TIERS,
|
|
4619
4850
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hybridlabor-api/aos",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"description": "AOS — A Curated AI AGENT OS. Optimized agent skills and add-ons like memB, OpenWiki, Heimdall Token Saver, and Godmode architectures.",
|
|
5
5
|
"main": "installer.js",
|
|
6
6
|
"engines": {
|
|
@@ -77,6 +77,8 @@ your head. Ask; do not default it on.
|
|
|
77
77
|
holds content, merge it into `AGENTS.md` first and only then replace it with a
|
|
78
78
|
symlink. Never silently overwrite it.
|
|
79
79
|
|
|
80
|
+
**Subagents & Multi-Harness Pipeline.** Offer to initialize `.aos/pipeline.json` using canonical tiers (or custom roles via `/subagent-setup`). Ensures subagents are automatically compiled across Claude Code, Antigravity, OpenCode, and Codex CLI.
|
|
81
|
+
|
|
80
82
|
**With a GitHub remote, two more.** Skip both entirely without a remote — a
|
|
81
83
|
local-only repo has nowhere to put them, and a skipped question is clearer than
|
|
82
84
|
one answered "no".
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: subagent-setup
|
|
3
|
+
description: Configure and synchronize multi-harness subagents (Claude Code, Google Antigravity, OpenCode, ChatGPT Codex CLI) with canonical model tiers and single source of truth in .aos/pipeline.json.
|
|
4
|
+
category: bdb-core
|
|
5
|
+
risk: low
|
|
6
|
+
source: bdb
|
|
7
|
+
date_added: "2026-09-20"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Skill: subagent-setup — Multi-Harness Subagent Configuration
|
|
11
|
+
|
|
12
|
+
Manages and synchronizes autonomous subagent roles across all four supported execution harnesses:
|
|
13
|
+
1. **Claude Code** (`.claude/agents/*.md`)
|
|
14
|
+
2. **Google Antigravity** (`~/.gemini/config/agents/*.json`)
|
|
15
|
+
3. **OpenCode** (`.opencode/agents/*.md`)
|
|
16
|
+
4. **ChatGPT Codex CLI** (`.codex/agents/*.toml` & `.codex/agents/*.md`)
|
|
17
|
+
|
|
18
|
+
Single Source of Truth: **`.aos/pipeline.json`** (compatible with AO Orchestrator Pipeline Wizard).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Canonical Model Tiers (Resilience against Model Churn)
|
|
23
|
+
|
|
24
|
+
To prevent breaking configurations when providers update model names, roles map to canonical tiers:
|
|
25
|
+
|
|
26
|
+
| Tier | Description | Claude Code | Antigravity | OpenCode | Codex CLI |
|
|
27
|
+
|---|---|---|---|---|---|
|
|
28
|
+
| `reasoning_max` | Deep reasoning, planning, adversarial review | `opus` | `gemini-3.1-pro-high` | `opencode/muse-spark-1.3-contributor-free` | `o3-mini` |
|
|
29
|
+
| `standard_fast` | Fast coding, frontend/backend engineering | `sonnet` | `gemini-3.8-flash-high` | `opencode/muse-spark-1.3-contributor-free` | `gpt-4o` |
|
|
30
|
+
| `trivial_low` | Mechanical tasks, summaries | `haiku` | `gemini-3.8-flash-low` | `opencode/muse-spark-1.3-contributor-free` | `gpt-4o-mini` |
|
|
31
|
+
|
|
32
|
+
*Note:* Custom model IDs can also be set directly per role and harness.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2. CLI Usage
|
|
37
|
+
|
|
38
|
+
### View Current Pipeline Status
|
|
39
|
+
```bash
|
|
40
|
+
node skills/global_config/subagent-setup/scripts/setup-subagents.mjs --show
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Initialize Default Pipeline Configuration
|
|
44
|
+
```bash
|
|
45
|
+
node skills/global_config/subagent-setup/scripts/setup-subagents.mjs --init-default
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Update a Specific Role
|
|
49
|
+
```bash
|
|
50
|
+
# Example: Set Reviewer to Codex with o3-mini
|
|
51
|
+
node skills/global_config/subagent-setup/scripts/setup-subagents.mjs \
|
|
52
|
+
--set-role reviewer --harness codex --model o3-mini --tier reasoning_max
|
|
53
|
+
|
|
54
|
+
# Example: Set Shipping to OpenCode with Free Tier
|
|
55
|
+
node skills/global_config/subagent-setup/scripts/setup-subagents.mjs \
|
|
56
|
+
--set-role shipping --harness opencode --model opencode/muse-spark-1.3-contributor-free
|
|
57
|
+
|
|
58
|
+
# Example: Enable or Disable a Role
|
|
59
|
+
node skills/global_config/subagent-setup/scripts/setup-subagents.mjs --set-role media_eventtech --enable
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Synchronize to All Harnesses
|
|
63
|
+
```bash
|
|
64
|
+
node skills/global_config/subagent-setup/scripts/setup-subagents.mjs --sync
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 3. Integration with `aos-project-init` & AO Orchestrator
|
|
70
|
+
- **AO Orchestrator (Port 3101)**: The Pipeline Wizard (Schritt 3 von 4: Agenten & Rollen) directly reads and writes `.aos/pipeline.json`.
|
|
71
|
+
- **`aos-project-init`**: Offers subagent configuration during project interview, defaulting to the canonical tier matrix.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* skills/global_config/subagent-setup/scripts/setup-subagents.mjs
|
|
5
|
+
*
|
|
6
|
+
* Interactive & CLI tool to configure subagent roles and model tiers across:
|
|
7
|
+
* - Claude Code (.claude/agents/*.md)
|
|
8
|
+
* - Google Antigravity (~/.gemini/config/agents/*.json)
|
|
9
|
+
* - OpenCode (.opencode/agents/*.md)
|
|
10
|
+
* - ChatGPT Codex CLI (.codex/agents/*.toml & *.md)
|
|
11
|
+
*
|
|
12
|
+
* Single Source of Truth: .aos/pipeline.json (or .aos/project.json)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import readline from 'node:readline';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { createRequire } from 'node:module';
|
|
20
|
+
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const installer = require('../../../../installer.js');
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
CANONICAL_TIERS,
|
|
26
|
+
loadPipelineConfig,
|
|
27
|
+
resolveAgentConfig,
|
|
28
|
+
parseAgentsMd,
|
|
29
|
+
compileClaudeAgents,
|
|
30
|
+
compileOpenCodeAgents,
|
|
31
|
+
compileCodexAgents
|
|
32
|
+
} = installer;
|
|
33
|
+
|
|
34
|
+
const ROLES = [
|
|
35
|
+
{ key: 'architect', label: 'Architect', defaultTier: 'reasoning_max', defaultHarness: 'claude' },
|
|
36
|
+
{ key: 'techlead', label: 'TechLead', defaultTier: 'standard_fast', defaultHarness: 'antigravity' },
|
|
37
|
+
{ key: 'ui_ux', label: 'Godmode_UI_UX', defaultTier: 'standard_fast', defaultHarness: 'claude' },
|
|
38
|
+
{ key: 'engineering', label: 'Godmode_Engineering', defaultTier: 'standard_fast', defaultHarness: 'claude' },
|
|
39
|
+
{ key: 'media_eventtech', label: 'Godmode_Media_EventTech', defaultTier: 'standard_fast', defaultHarness: 'antigravity', defaultEnabled: false },
|
|
40
|
+
{ key: 'reviewer', label: 'Reviewer', defaultTier: 'reasoning_max', defaultHarness: 'codex' },
|
|
41
|
+
{ key: 'shipping', label: 'Godmode_Shipping', defaultTier: 'standard_fast', defaultHarness: 'opencode' }
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function printPipeline(pipeline) {
|
|
45
|
+
console.log('\n=== AOS Multi-Harness Subagent Pipeline ===\n');
|
|
46
|
+
console.log(
|
|
47
|
+
'Role'.padEnd(20) +
|
|
48
|
+
'Status'.padEnd(10) +
|
|
49
|
+
'Harness'.padEnd(15) +
|
|
50
|
+
'Tier'.padEnd(18) +
|
|
51
|
+
'Model'
|
|
52
|
+
);
|
|
53
|
+
console.log('-'.repeat(80));
|
|
54
|
+
|
|
55
|
+
for (const r of ROLES) {
|
|
56
|
+
const cfg = pipeline[r.key] || {};
|
|
57
|
+
const enabled = cfg.enabled !== false;
|
|
58
|
+
const harness = cfg.harness || r.defaultHarness;
|
|
59
|
+
const tier = cfg.tier || r.defaultTier;
|
|
60
|
+
const model = cfg.model || (CANONICAL_TIERS[tier] ? CANONICAL_TIERS[tier][harness] : 'inherit');
|
|
61
|
+
|
|
62
|
+
console.log(
|
|
63
|
+
r.label.padEnd(20) +
|
|
64
|
+
(enabled ? '✔ ON' : '✖ OFF').padEnd(10) +
|
|
65
|
+
harness.padEnd(15) +
|
|
66
|
+
tier.padEnd(18) +
|
|
67
|
+
model
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
console.log('\nConfig file: .aos/pipeline.json\n');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getDefaultPipeline() {
|
|
74
|
+
const pipeline = {};
|
|
75
|
+
for (const r of ROLES) {
|
|
76
|
+
const harness = r.defaultHarness;
|
|
77
|
+
const tier = r.defaultTier;
|
|
78
|
+
const model = CANONICAL_TIERS[tier] ? CANONICAL_TIERS[tier][harness] : 'inherit';
|
|
79
|
+
pipeline[r.key] = {
|
|
80
|
+
enabled: r.defaultEnabled !== false,
|
|
81
|
+
harness,
|
|
82
|
+
tier,
|
|
83
|
+
model
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return pipeline;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function syncAllHarnesses(projectDir, pipeline) {
|
|
90
|
+
const agentsMdSrc = path.join(projectDir, '.agents', 'agents.md');
|
|
91
|
+
if (!fs.existsSync(agentsMdSrc)) {
|
|
92
|
+
console.error(`Error: .agents/agents.md not found at ${agentsMdSrc}`);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const content = fs.readFileSync(agentsMdSrc, 'utf8');
|
|
97
|
+
const agents = parseAgentsMd(content);
|
|
98
|
+
|
|
99
|
+
// 1. Claude
|
|
100
|
+
const claudeDir = path.join(projectDir, '.claude', 'agents');
|
|
101
|
+
compileClaudeAgents(agents, claudeDir, pipeline);
|
|
102
|
+
console.log(` ✓ Synced Claude Code agents to ${claudeDir}`);
|
|
103
|
+
|
|
104
|
+
// 2. OpenCode
|
|
105
|
+
const opencodeDir = path.join(projectDir, '.opencode', 'agents');
|
|
106
|
+
compileOpenCodeAgents(agents, opencodeDir, pipeline);
|
|
107
|
+
console.log(` ✓ Synced OpenCode agents to ${opencodeDir}`);
|
|
108
|
+
|
|
109
|
+
// 3. Codex
|
|
110
|
+
const codexDir = path.join(projectDir, '.codex', 'agents');
|
|
111
|
+
compileCodexAgents(agents, codexDir, pipeline);
|
|
112
|
+
console.log(` ✓ Synced Codex CLI agents to ${codexDir}`);
|
|
113
|
+
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function main() {
|
|
118
|
+
const args = process.argv.slice(2);
|
|
119
|
+
const cwd = process.cwd();
|
|
120
|
+
const aosDir = path.join(cwd, '.aos');
|
|
121
|
+
const pipelinePath = path.join(aosDir, 'pipeline.json');
|
|
122
|
+
|
|
123
|
+
let pipeline = loadPipelineConfig(cwd) || getDefaultPipeline();
|
|
124
|
+
|
|
125
|
+
if (args.includes('--show')) {
|
|
126
|
+
printPipeline(pipeline);
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (args.includes('--init-default')) {
|
|
131
|
+
fs.mkdirSync(aosDir, { recursive: true });
|
|
132
|
+
fs.writeFileSync(pipelinePath, JSON.stringify({ version: 1, pipeline: getDefaultPipeline() }, null, 2));
|
|
133
|
+
console.log(`Initialized default pipeline at ${pipelinePath}`);
|
|
134
|
+
printPipeline(getDefaultPipeline());
|
|
135
|
+
syncAllHarnesses(cwd, getDefaultPipeline());
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (args.includes('--sync')) {
|
|
140
|
+
console.log('Syncing all harnesses from .aos/pipeline.json...');
|
|
141
|
+
syncAllHarnesses(cwd, pipeline);
|
|
142
|
+
console.log('✓ All subagents synchronized.');
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const setRoleIdx = args.indexOf('--set-role');
|
|
147
|
+
if (setRoleIdx !== -1 && args[setRoleIdx + 1]) {
|
|
148
|
+
const role = args[setRoleIdx + 1].toLowerCase().replace(/-/g, '_');
|
|
149
|
+
const harnessIdx = args.indexOf('--harness');
|
|
150
|
+
const modelIdx = args.indexOf('--model');
|
|
151
|
+
const tierIdx = args.indexOf('--tier');
|
|
152
|
+
const disableFlag = args.includes('--disable');
|
|
153
|
+
const enableFlag = args.includes('--enable');
|
|
154
|
+
|
|
155
|
+
if (!pipeline[role]) {
|
|
156
|
+
pipeline[role] = {};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (harnessIdx !== -1 && args[harnessIdx + 1]) pipeline[role].harness = args[harnessIdx + 1];
|
|
160
|
+
if (modelIdx !== -1 && args[modelIdx + 1]) pipeline[role].model = args[modelIdx + 1];
|
|
161
|
+
if (tierIdx !== -1 && args[tierIdx + 1]) pipeline[role].tier = args[tierIdx + 1];
|
|
162
|
+
if (disableFlag) pipeline[role].enabled = false;
|
|
163
|
+
if (enableFlag) pipeline[role].enabled = true;
|
|
164
|
+
|
|
165
|
+
fs.mkdirSync(aosDir, { recursive: true });
|
|
166
|
+
fs.writeFileSync(pipelinePath, JSON.stringify({ version: 1, pipeline }, null, 2));
|
|
167
|
+
console.log(`Updated role "${role}" in ${pipelinePath}`);
|
|
168
|
+
printPipeline(pipeline);
|
|
169
|
+
syncAllHarnesses(cwd, pipeline);
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Default: Show pipeline and usage
|
|
174
|
+
printPipeline(pipeline);
|
|
175
|
+
console.log('Commands:');
|
|
176
|
+
console.log(' node setup-subagents.mjs --show');
|
|
177
|
+
console.log(' node setup-subagents.mjs --init-default');
|
|
178
|
+
console.log(' node setup-subagents.mjs --sync');
|
|
179
|
+
console.log(' node setup-subagents.mjs --set-role <role> --harness <claude|antigravity|opencode|codex> --model <model> [--tier <tier>]');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
main().catch(err => {
|
|
183
|
+
console.error(err);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
});
|