opencode-hive 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -2
- package/dist/agents/custom-agents.d.ts +18 -0
- package/dist/agents/hive.d.ts +1 -1
- package/dist/agents/swarm.d.ts +1 -1
- package/dist/hooks/variant-hook.d.ts +0 -33
- package/dist/index.js +699 -300
- package/package.json +1 -1
- package/skills/dispatching-parallel-agents/SKILL.md +3 -3
- package/skills/executing-plans/SKILL.md +1 -1
- package/skills/parallel-exploration/SKILL.md +2 -2
package/dist/index.js
CHANGED
|
@@ -12931,9 +12931,9 @@ Each agent gets:
|
|
|
12931
12931
|
|
|
12932
12932
|
\`\`\`typescript
|
|
12933
12933
|
// Using Hive tools for parallel execution
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12934
|
+
hive_worktree_start({ task: "01-fix-abort-tests" })
|
|
12935
|
+
hive_worktree_start({ task: "02-fix-batch-tests" })
|
|
12936
|
+
hive_worktree_start({ task: "03-fix-race-condition-tests" })
|
|
12937
12937
|
// All three run concurrently in isolated worktrees
|
|
12938
12938
|
\`\`\`
|
|
12939
12939
|
|
|
@@ -13433,7 +13433,7 @@ Only \`done\` satisfies dependencies (not \`blocked\`, \`failed\`, \`partial\`,
|
|
|
13433
13433
|
### Step 3: Execute Batch
|
|
13434
13434
|
|
|
13435
13435
|
For each task in the batch:
|
|
13436
|
-
1. Mark as in_progress via \`
|
|
13436
|
+
1. Mark as in_progress via \`hive_worktree_start()\`
|
|
13437
13437
|
2. Follow each step exactly (plan has bite-sized steps)
|
|
13438
13438
|
3. Run verifications as specified
|
|
13439
13439
|
4. Mark as completed
|
|
@@ -13501,7 +13501,7 @@ When you need to answer "where/how does X work?" across multiple domains (codeba
|
|
|
13501
13501
|
|
|
13502
13502
|
**Safe in Planning mode:** This is read-only exploration. It is OK to use during exploratory research even when there is no feature, no plan, and no approved tasks.
|
|
13503
13503
|
|
|
13504
|
-
**This skill is for read-only research.** For parallel implementation work, use \`hive_skill("dispatching-parallel-agents")\` with \`
|
|
13504
|
+
**This skill is for read-only research.** For parallel implementation work, use \`hive_skill("dispatching-parallel-agents")\` with \`hive_worktree_start\`.
|
|
13505
13505
|
|
|
13506
13506
|
## When to Use
|
|
13507
13507
|
|
|
@@ -13512,7 +13512,7 @@ When you need to answer "where/how does X work?" across multiple domains (codeba
|
|
|
13512
13512
|
- Questions are independent (answer to A doesn't affect B)
|
|
13513
13513
|
- User asks **3+ independent questions** (often as a numbered list or separate bullets)
|
|
13514
13514
|
- No edits needed (read-only exploration)
|
|
13515
|
-
- User asks for an
|
|
13515
|
+
- User asks for an exploration that likely spans multiple files/packages
|
|
13516
13516
|
- The work is read-only and the questions can be investigated independently
|
|
13517
13517
|
|
|
13518
13518
|
**Only skip this skill when:**
|
|
@@ -14852,7 +14852,7 @@ Intent Verbalization — verbalize before acting:
|
|
|
14852
14852
|
### Delegation
|
|
14853
14853
|
- Single-scout research → \`task({ subagent_type: "scout-researcher", prompt: "..." })\`
|
|
14854
14854
|
- Parallel exploration → Load \`hive_skill("parallel-exploration")\` and follow the task mode delegation guidance.
|
|
14855
|
-
- Implementation → \`
|
|
14855
|
+
- Implementation → \`hive_worktree_start({ task: "01-task-name" })\` (creates worktree + Forager)
|
|
14856
14856
|
|
|
14857
14857
|
During Planning, use \`task({ subagent_type: "scout-researcher", ... })\` for exploration (BLOCKING — returns when done). For parallel exploration, issue multiple \`task()\` calls in the same message.
|
|
14858
14858
|
|
|
@@ -14945,7 +14945,7 @@ Each task declares dependencies with **Depends on**:
|
|
|
14945
14945
|
### After Plan Written
|
|
14946
14946
|
Ask user via \`question()\`: "Plan complete. Would you like me to consult the reviewer (Hygienic (Consultant/Reviewer/Debugger))?"
|
|
14947
14947
|
|
|
14948
|
-
If yes → \`task({ subagent_type: "
|
|
14948
|
+
If yes → default to built-in \`hygienic-reviewer\`; choose a configured hygienic-derived reviewer only when its description in \`Configured Custom Subagents\` is a better match. Then run \`task({ subagent_type: "<chosen-reviewer>", prompt: "Review plan..." })\`.
|
|
14949
14949
|
|
|
14950
14950
|
After review decision, offer execution choice (subagent-driven vs parallel session) consistent with writing-plans.
|
|
14951
14951
|
|
|
@@ -14978,15 +14978,17 @@ Use \`hive_status()\` to see **runnable** tasks (dependencies satisfied) and **b
|
|
|
14978
14978
|
|
|
14979
14979
|
### Worker Spawning
|
|
14980
14980
|
\`\`\`
|
|
14981
|
-
|
|
14981
|
+
hive_worktree_start({ task: "01-task-name" }) // Creates worktree + Forager
|
|
14982
14982
|
\`\`\`
|
|
14983
14983
|
|
|
14984
14984
|
### After Delegation
|
|
14985
14985
|
1. \`task()\` is blocking — when it returns, the worker is done
|
|
14986
|
-
2.
|
|
14987
|
-
3.
|
|
14988
|
-
4. If
|
|
14989
|
-
5.
|
|
14986
|
+
2. After \`task()\` returns, immediately call \`hive_status()\` to check the new task state and find next runnable tasks before any resume attempt
|
|
14987
|
+
3. Use \`continueFrom: "blocked"\` only when status is exactly \`blocked\`
|
|
14988
|
+
4. If status is not \`blocked\`, do not use \`continueFrom: "blocked"\`; use \`hive_worktree_start({ feature, task })\` only for normal starts (\`pending\` / \`in_progress\`)
|
|
14989
|
+
5. Never loop \`continueFrom: "blocked"\` on non-blocked statuses
|
|
14990
|
+
6. If task status is blocked: read blocker info → \`question()\` → user decision → resume with \`continueFrom: "blocked"\`
|
|
14991
|
+
7. Skip polling — the result is available when \`task()\` returns
|
|
14990
14992
|
|
|
14991
14993
|
### Batch Merge + Verify Workflow
|
|
14992
14994
|
When multiple tasks are in flight, prefer **batch completion** over per-task verification:
|
|
@@ -15008,8 +15010,9 @@ When multiple tasks are in flight, prefer **batch completion** over per-task ver
|
|
|
15008
15010
|
### Post-Batch Review (Hygienic)
|
|
15009
15011
|
After completing and merging a batch:
|
|
15010
15012
|
1. Ask the user via \`question()\` if they want a Hygienic code review for the batch.
|
|
15011
|
-
2. If yes
|
|
15012
|
-
3.
|
|
15013
|
+
2. If yes → default to built-in \`hygienic-reviewer\`; choose a configured hygienic-derived reviewer only when its description in \`Configured Custom Subagents\` is a better match.
|
|
15014
|
+
3. Then run \`task({ subagent_type: "<chosen-reviewer>", prompt: "Review implementation changes from the latest batch." })\`.
|
|
15015
|
+
4. Apply feedback before starting the next batch.
|
|
15013
15016
|
|
|
15014
15017
|
### AGENTS.md Maintenance
|
|
15015
15018
|
After feature completion (all tasks merged):
|
|
@@ -15219,7 +15222,7 @@ Standard checks: specialized agent? can I do it myself for sure? external system
|
|
|
15219
15222
|
## Worker Spawning
|
|
15220
15223
|
|
|
15221
15224
|
\`\`\`
|
|
15222
|
-
|
|
15225
|
+
hive_worktree_start({ task: "01-task-name" })
|
|
15223
15226
|
// If external system data is needed (parallel exploration):
|
|
15224
15227
|
// Load hive_skill("parallel-exploration") for the full playbook, then:
|
|
15225
15228
|
// In task mode, use task() for research fan-out.
|
|
@@ -15227,8 +15230,10 @@ hive_worktree_create({ task: "01-task-name" })
|
|
|
15227
15230
|
|
|
15228
15231
|
Delegation guidance:
|
|
15229
15232
|
- \`task()\` is BLOCKING — returns when the worker is done
|
|
15230
|
-
-
|
|
15231
|
-
-
|
|
15233
|
+
- After \`task()\` returns, call \`hive_status()\` immediately to check new state and find next runnable tasks before any resume attempt
|
|
15234
|
+
- Use \`continueFrom: "blocked"\` only when status is exactly \`blocked\`
|
|
15235
|
+
- If status is not \`blocked\`, do not use \`continueFrom: "blocked"\`; use \`hive_worktree_start({ feature, task })\` only for normal starts (\`pending\` / \`in_progress\`)
|
|
15236
|
+
- Never loop \`continueFrom: "blocked"\` on non-blocked statuses
|
|
15232
15237
|
- For parallel fan-out, issue multiple \`task()\` calls in the same message
|
|
15233
15238
|
|
|
15234
15239
|
## After Delegation - VERIFY
|
|
@@ -15256,7 +15261,7 @@ After completing and merging a batch, run full verification on the main branch:
|
|
|
15256
15261
|
|
|
15257
15262
|
## Blocker Handling
|
|
15258
15263
|
|
|
15259
|
-
When worker reports blocked: \`hive_status()\` → read blocker info; \`question()\` → ask user (no plain text); \`hive_worktree_create({ task, continueFrom: "blocked", decision })\`.
|
|
15264
|
+
When worker reports blocked: \`hive_status()\` → confirm status is exactly \`blocked\` → read blocker info; \`question()\` → ask user (no plain text); \`hive_worktree_create({ task, continueFrom: "blocked", decision })\`. If status is not \`blocked\`, do not use blocked resume; only use \`hive_worktree_start({ feature, task })\` for normal starts (\`pending\` / \`in_progress\`).
|
|
15260
15265
|
|
|
15261
15266
|
## Failure Recovery (After 3 Consecutive Failures)
|
|
15262
15267
|
|
|
@@ -15275,7 +15280,9 @@ Merge after batch completes, then verify the merged result.
|
|
|
15275
15280
|
|
|
15276
15281
|
### Post-Batch Review (Hygienic)
|
|
15277
15282
|
|
|
15278
|
-
After completing and merging a batch: ask via \`question()\` if they want a Hygienic review.
|
|
15283
|
+
After completing and merging a batch: ask via \`question()\` if they want a Hygienic review.
|
|
15284
|
+
If yes, default to built-in \`hygienic-reviewer\`; choose a configured hygienic-derived reviewer only when its description in \`Configured Custom Subagents\` is a better match.
|
|
15285
|
+
Then run \`task({ subagent_type: "<chosen-reviewer>", prompt: "Review implementation changes from the latest batch." })\` and apply feedback before the next batch.
|
|
15279
15286
|
|
|
15280
15287
|
### AGENTS.md Maintenance
|
|
15281
15288
|
|
|
@@ -15289,7 +15296,7 @@ For projects without AGENTS.md:
|
|
|
15289
15296
|
|
|
15290
15297
|
## Turn Termination
|
|
15291
15298
|
|
|
15292
|
-
Valid endings: worker delegation (hive_worktree_create), status check (hive_status), user question (question()), merge (hive_merge).
|
|
15299
|
+
Valid endings: worker delegation (hive_worktree_start/hive_worktree_create), status check (hive_status), user question (question()), merge (hive_merge).
|
|
15293
15300
|
Avoid ending with: "Let me know when you're ready", "When you're ready...", summary without next action, or waiting for something unspecified.
|
|
15294
15301
|
|
|
15295
15302
|
## Guardrails
|
|
@@ -15653,6 +15660,33 @@ Before verdict, mentally execute 2-3 tasks:
|
|
|
15653
15660
|
- Focus on worker success, not perfection
|
|
15654
15661
|
`;
|
|
15655
15662
|
|
|
15663
|
+
// src/agents/custom-agents.ts
|
|
15664
|
+
function buildCustomSubagents({
|
|
15665
|
+
customAgents,
|
|
15666
|
+
baseAgents,
|
|
15667
|
+
autoLoadedSkills = {}
|
|
15668
|
+
}) {
|
|
15669
|
+
const derived = {};
|
|
15670
|
+
for (const [agentName, customConfig] of Object.entries(customAgents)) {
|
|
15671
|
+
const baseAgent = baseAgents[customConfig.baseAgent];
|
|
15672
|
+
if (!baseAgent) {
|
|
15673
|
+
continue;
|
|
15674
|
+
}
|
|
15675
|
+
const autoLoadedSkillsContent = autoLoadedSkills[agentName] ?? "";
|
|
15676
|
+
derived[agentName] = {
|
|
15677
|
+
model: customConfig.model ?? baseAgent.model,
|
|
15678
|
+
variant: customConfig.variant ?? baseAgent.variant,
|
|
15679
|
+
temperature: customConfig.temperature ?? baseAgent.temperature,
|
|
15680
|
+
mode: "subagent",
|
|
15681
|
+
description: customConfig.description,
|
|
15682
|
+
prompt: baseAgent.prompt + autoLoadedSkillsContent,
|
|
15683
|
+
tools: baseAgent.tools,
|
|
15684
|
+
permission: baseAgent.permission
|
|
15685
|
+
};
|
|
15686
|
+
}
|
|
15687
|
+
return derived;
|
|
15688
|
+
}
|
|
15689
|
+
|
|
15656
15690
|
// src/mcp/websearch.ts
|
|
15657
15691
|
var websearchMcp = {
|
|
15658
15692
|
type: "remote",
|
|
@@ -16554,6 +16588,28 @@ var require_dist2 = __commonJS((exports) => {
|
|
|
16554
16588
|
exports.createDeferred = deferred;
|
|
16555
16589
|
exports.default = deferred;
|
|
16556
16590
|
});
|
|
16591
|
+
var BUILT_IN_AGENT_NAMES = [
|
|
16592
|
+
"hive-master",
|
|
16593
|
+
"architect-planner",
|
|
16594
|
+
"swarm-orchestrator",
|
|
16595
|
+
"scout-researcher",
|
|
16596
|
+
"forager-worker",
|
|
16597
|
+
"hygienic-reviewer"
|
|
16598
|
+
];
|
|
16599
|
+
var CUSTOM_AGENT_BASES = ["forager-worker", "hygienic-reviewer"];
|
|
16600
|
+
var CUSTOM_AGENT_RESERVED_NAMES = [
|
|
16601
|
+
...BUILT_IN_AGENT_NAMES,
|
|
16602
|
+
"hive",
|
|
16603
|
+
"architect",
|
|
16604
|
+
"swarm",
|
|
16605
|
+
"scout",
|
|
16606
|
+
"forager",
|
|
16607
|
+
"hygienic",
|
|
16608
|
+
"receiver",
|
|
16609
|
+
"build",
|
|
16610
|
+
"plan",
|
|
16611
|
+
"code"
|
|
16612
|
+
];
|
|
16557
16613
|
var DEFAULT_AGENT_MODELS = {
|
|
16558
16614
|
"hive-master": "github-copilot/claude-opus-4.5",
|
|
16559
16615
|
"architect-planner": "github-copilot/gpt-5.2-codex",
|
|
@@ -16569,6 +16625,21 @@ var DEFAULT_HIVE_CONFIG = {
|
|
|
16569
16625
|
disableMcps: [],
|
|
16570
16626
|
agentMode: "unified",
|
|
16571
16627
|
sandbox: "none",
|
|
16628
|
+
customAgents: {
|
|
16629
|
+
"forager-example-template": {
|
|
16630
|
+
baseAgent: "forager-worker",
|
|
16631
|
+
description: "Example template only: rename or delete this entry before use. Do not expect planners/orchestrators to select this placeholder agent as configured.",
|
|
16632
|
+
model: "anthropic/claude-sonnet-4-20250514",
|
|
16633
|
+
temperature: 0.2,
|
|
16634
|
+
variant: "high",
|
|
16635
|
+
autoLoadSkills: ["test-driven-development"]
|
|
16636
|
+
},
|
|
16637
|
+
"hygienic-example-template": {
|
|
16638
|
+
baseAgent: "hygienic-reviewer",
|
|
16639
|
+
description: "Example template only: rename or delete this entry before use. Do not expect planners/orchestrators to select this placeholder agent as configured.",
|
|
16640
|
+
autoLoadSkills: ["code-reviewer"]
|
|
16641
|
+
}
|
|
16642
|
+
},
|
|
16572
16643
|
agents: {
|
|
16573
16644
|
"hive-master": {
|
|
16574
16645
|
model: DEFAULT_AGENT_MODELS["hive-master"],
|
|
@@ -22143,6 +22214,7 @@ ${f.content}`);
|
|
|
22143
22214
|
class ConfigService {
|
|
22144
22215
|
configPath;
|
|
22145
22216
|
cachedConfig = null;
|
|
22217
|
+
cachedCustomAgentConfigs = null;
|
|
22146
22218
|
constructor() {
|
|
22147
22219
|
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
22148
22220
|
const configDir = path6.join(homeDir, ".config", "opencode");
|
|
@@ -22158,51 +22230,44 @@ class ConfigService {
|
|
|
22158
22230
|
try {
|
|
22159
22231
|
if (!fs10.existsSync(this.configPath)) {
|
|
22160
22232
|
this.cachedConfig = { ...DEFAULT_HIVE_CONFIG };
|
|
22233
|
+
this.cachedCustomAgentConfigs = null;
|
|
22161
22234
|
return this.cachedConfig;
|
|
22162
22235
|
}
|
|
22163
22236
|
const raw = fs10.readFileSync(this.configPath, "utf-8");
|
|
22164
22237
|
const stored = JSON.parse(raw);
|
|
22238
|
+
const storedCustomAgents = this.isObjectRecord(stored.customAgents) ? stored.customAgents : {};
|
|
22239
|
+
const mergedBuiltInAgents = BUILT_IN_AGENT_NAMES.reduce((acc, agentName) => {
|
|
22240
|
+
acc[agentName] = {
|
|
22241
|
+
...DEFAULT_HIVE_CONFIG.agents?.[agentName],
|
|
22242
|
+
...stored.agents?.[agentName]
|
|
22243
|
+
};
|
|
22244
|
+
return acc;
|
|
22245
|
+
}, {});
|
|
22165
22246
|
const merged = {
|
|
22166
22247
|
...DEFAULT_HIVE_CONFIG,
|
|
22167
22248
|
...stored,
|
|
22168
22249
|
agents: {
|
|
22169
22250
|
...DEFAULT_HIVE_CONFIG.agents,
|
|
22170
22251
|
...stored.agents,
|
|
22171
|
-
|
|
22172
|
-
|
|
22173
|
-
|
|
22174
|
-
|
|
22175
|
-
|
|
22176
|
-
...DEFAULT_HIVE_CONFIG.agents?.["architect-planner"],
|
|
22177
|
-
...stored.agents?.["architect-planner"]
|
|
22178
|
-
},
|
|
22179
|
-
"swarm-orchestrator": {
|
|
22180
|
-
...DEFAULT_HIVE_CONFIG.agents?.["swarm-orchestrator"],
|
|
22181
|
-
...stored.agents?.["swarm-orchestrator"]
|
|
22182
|
-
},
|
|
22183
|
-
"scout-researcher": {
|
|
22184
|
-
...DEFAULT_HIVE_CONFIG.agents?.["scout-researcher"],
|
|
22185
|
-
...stored.agents?.["scout-researcher"]
|
|
22186
|
-
},
|
|
22187
|
-
"forager-worker": {
|
|
22188
|
-
...DEFAULT_HIVE_CONFIG.agents?.["forager-worker"],
|
|
22189
|
-
...stored.agents?.["forager-worker"]
|
|
22190
|
-
},
|
|
22191
|
-
"hygienic-reviewer": {
|
|
22192
|
-
...DEFAULT_HIVE_CONFIG.agents?.["hygienic-reviewer"],
|
|
22193
|
-
...stored.agents?.["hygienic-reviewer"]
|
|
22194
|
-
}
|
|
22252
|
+
...mergedBuiltInAgents
|
|
22253
|
+
},
|
|
22254
|
+
customAgents: {
|
|
22255
|
+
...DEFAULT_HIVE_CONFIG.customAgents,
|
|
22256
|
+
...storedCustomAgents
|
|
22195
22257
|
}
|
|
22196
22258
|
};
|
|
22197
22259
|
this.cachedConfig = merged;
|
|
22260
|
+
this.cachedCustomAgentConfigs = null;
|
|
22198
22261
|
return this.cachedConfig;
|
|
22199
22262
|
} catch {
|
|
22200
22263
|
this.cachedConfig = { ...DEFAULT_HIVE_CONFIG };
|
|
22264
|
+
this.cachedCustomAgentConfigs = null;
|
|
22201
22265
|
return this.cachedConfig;
|
|
22202
22266
|
}
|
|
22203
22267
|
}
|
|
22204
22268
|
set(updates) {
|
|
22205
22269
|
this.cachedConfig = null;
|
|
22270
|
+
this.cachedCustomAgentConfigs = null;
|
|
22206
22271
|
const current = this.get();
|
|
22207
22272
|
const merged = {
|
|
22208
22273
|
...current,
|
|
@@ -22210,7 +22275,11 @@ class ConfigService {
|
|
|
22210
22275
|
agents: updates.agents ? {
|
|
22211
22276
|
...current.agents,
|
|
22212
22277
|
...updates.agents
|
|
22213
|
-
} : current.agents
|
|
22278
|
+
} : current.agents,
|
|
22279
|
+
customAgents: updates.customAgents ? {
|
|
22280
|
+
...current.customAgents,
|
|
22281
|
+
...updates.customAgents
|
|
22282
|
+
} : current.customAgents
|
|
22214
22283
|
};
|
|
22215
22284
|
const configDir = path6.dirname(this.configPath);
|
|
22216
22285
|
if (!fs10.existsSync(configDir)) {
|
|
@@ -22218,6 +22287,7 @@ class ConfigService {
|
|
|
22218
22287
|
}
|
|
22219
22288
|
fs10.writeFileSync(this.configPath, JSON.stringify(merged, null, 2));
|
|
22220
22289
|
this.cachedConfig = merged;
|
|
22290
|
+
this.cachedCustomAgentConfigs = null;
|
|
22221
22291
|
return merged;
|
|
22222
22292
|
}
|
|
22223
22293
|
exists() {
|
|
@@ -22231,20 +22301,94 @@ class ConfigService {
|
|
|
22231
22301
|
}
|
|
22232
22302
|
getAgentConfig(agent) {
|
|
22233
22303
|
const config2 = this.get();
|
|
22234
|
-
|
|
22235
|
-
|
|
22236
|
-
|
|
22237
|
-
|
|
22238
|
-
|
|
22239
|
-
|
|
22240
|
-
|
|
22304
|
+
if (this.isBuiltInAgent(agent)) {
|
|
22305
|
+
const agentConfig = config2.agents?.[agent] ?? {};
|
|
22306
|
+
const defaultAutoLoadSkills = DEFAULT_HIVE_CONFIG.agents?.[agent]?.autoLoadSkills ?? [];
|
|
22307
|
+
const effectiveAutoLoadSkills = this.resolveAutoLoadSkills(defaultAutoLoadSkills, agentConfig.autoLoadSkills ?? [], this.isPlannerAgent(agent));
|
|
22308
|
+
return {
|
|
22309
|
+
...agentConfig,
|
|
22310
|
+
autoLoadSkills: effectiveAutoLoadSkills
|
|
22311
|
+
};
|
|
22312
|
+
}
|
|
22313
|
+
const customAgents = this.getCustomAgentConfigs();
|
|
22314
|
+
return customAgents[agent] ?? {};
|
|
22315
|
+
}
|
|
22316
|
+
getCustomAgentConfigs() {
|
|
22317
|
+
if (this.cachedCustomAgentConfigs !== null) {
|
|
22318
|
+
return this.cachedCustomAgentConfigs;
|
|
22319
|
+
}
|
|
22320
|
+
const config2 = this.get();
|
|
22321
|
+
const customAgents = this.isObjectRecord(config2.customAgents) ? config2.customAgents : {};
|
|
22322
|
+
const resolved = {};
|
|
22323
|
+
for (const [agentName, declaration] of Object.entries(customAgents)) {
|
|
22324
|
+
if (this.isReservedCustomAgentName(agentName)) {
|
|
22325
|
+
console.warn(`[hive:config] Skipping custom agent "${agentName}": reserved name`);
|
|
22326
|
+
continue;
|
|
22327
|
+
}
|
|
22328
|
+
if (!this.isObjectRecord(declaration)) {
|
|
22329
|
+
console.warn(`[hive:config] Skipping custom agent "${agentName}": invalid declaration (expected object)`);
|
|
22330
|
+
continue;
|
|
22331
|
+
}
|
|
22332
|
+
const baseAgent = declaration["baseAgent"];
|
|
22333
|
+
if (typeof baseAgent !== "string" || !this.isSupportedCustomAgentBase(baseAgent)) {
|
|
22334
|
+
console.warn(`[hive:config] Skipping custom agent "${agentName}": unsupported baseAgent "${String(baseAgent)}"`);
|
|
22335
|
+
continue;
|
|
22336
|
+
}
|
|
22337
|
+
const autoLoadSkillsValue = declaration["autoLoadSkills"];
|
|
22338
|
+
const additionalAutoLoadSkills = Array.isArray(autoLoadSkillsValue) ? autoLoadSkillsValue.filter((skill) => typeof skill === "string") : [];
|
|
22339
|
+
const baseAgentConfig = this.getAgentConfig(baseAgent);
|
|
22340
|
+
const effectiveAutoLoadSkills = this.resolveAutoLoadSkills(baseAgentConfig.autoLoadSkills ?? [], additionalAutoLoadSkills, this.isPlannerAgent(baseAgent));
|
|
22341
|
+
const descriptionValue = declaration["description"];
|
|
22342
|
+
const description = typeof descriptionValue === "string" ? descriptionValue.trim() : "";
|
|
22343
|
+
if (!description) {
|
|
22344
|
+
console.warn(`[hive:config] Skipping custom agent "${agentName}": description must be a non-empty string`);
|
|
22345
|
+
continue;
|
|
22346
|
+
}
|
|
22347
|
+
const modelValue = declaration["model"];
|
|
22348
|
+
const temperatureValue = declaration["temperature"];
|
|
22349
|
+
const variantValue = declaration["variant"];
|
|
22350
|
+
const model = typeof modelValue === "string" ? modelValue.trim() || baseAgentConfig.model : baseAgentConfig.model;
|
|
22351
|
+
const variant = typeof variantValue === "string" ? variantValue.trim() || baseAgentConfig.variant : baseAgentConfig.variant;
|
|
22352
|
+
resolved[agentName] = {
|
|
22353
|
+
baseAgent,
|
|
22354
|
+
description,
|
|
22355
|
+
model,
|
|
22356
|
+
temperature: typeof temperatureValue === "number" ? temperatureValue : baseAgentConfig.temperature,
|
|
22357
|
+
variant,
|
|
22358
|
+
autoLoadSkills: effectiveAutoLoadSkills
|
|
22359
|
+
};
|
|
22360
|
+
}
|
|
22361
|
+
this.cachedCustomAgentConfigs = resolved;
|
|
22362
|
+
return this.cachedCustomAgentConfigs;
|
|
22363
|
+
}
|
|
22364
|
+
hasConfiguredAgent(agent) {
|
|
22365
|
+
if (this.isBuiltInAgent(agent)) {
|
|
22366
|
+
return true;
|
|
22367
|
+
}
|
|
22368
|
+
const customAgents = this.getCustomAgentConfigs();
|
|
22369
|
+
return customAgents[agent] !== undefined;
|
|
22370
|
+
}
|
|
22371
|
+
isBuiltInAgent(agent) {
|
|
22372
|
+
return BUILT_IN_AGENT_NAMES.includes(agent);
|
|
22373
|
+
}
|
|
22374
|
+
isReservedCustomAgentName(agent) {
|
|
22375
|
+
return CUSTOM_AGENT_RESERVED_NAMES.includes(agent);
|
|
22376
|
+
}
|
|
22377
|
+
isSupportedCustomAgentBase(baseAgent) {
|
|
22378
|
+
return CUSTOM_AGENT_BASES.includes(baseAgent);
|
|
22379
|
+
}
|
|
22380
|
+
isPlannerAgent(agent) {
|
|
22381
|
+
return agent === "hive-master" || agent === "architect-planner";
|
|
22382
|
+
}
|
|
22383
|
+
isObjectRecord(value) {
|
|
22384
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
22385
|
+
}
|
|
22386
|
+
resolveAutoLoadSkills(baseAutoLoadSkills, additionalAutoLoadSkills, isPlannerAgent) {
|
|
22387
|
+
const effectiveAdditionalSkills = isPlannerAgent ? additionalAutoLoadSkills : additionalAutoLoadSkills.filter((skill) => skill !== "onboarding");
|
|
22388
|
+
const combinedAutoLoadSkills = [...baseAutoLoadSkills, ...effectiveAdditionalSkills];
|
|
22241
22389
|
const uniqueAutoLoadSkills = Array.from(new Set(combinedAutoLoadSkills));
|
|
22242
|
-
const disabledSkills =
|
|
22243
|
-
|
|
22244
|
-
return {
|
|
22245
|
-
...agentConfig,
|
|
22246
|
-
autoLoadSkills: effectiveAutoLoadSkills
|
|
22247
|
-
};
|
|
22390
|
+
const disabledSkills = this.getDisabledSkills();
|
|
22391
|
+
return uniqueAutoLoadSkills.filter((skill) => !disabledSkills.includes(skill));
|
|
22248
22392
|
}
|
|
22249
22393
|
isOmoSlimEnabled() {
|
|
22250
22394
|
const config2 = this.get();
|
|
@@ -23075,23 +23219,28 @@ function writeWorkerPromptFile(feature, task, prompt, hiveDir) {
|
|
|
23075
23219
|
}
|
|
23076
23220
|
|
|
23077
23221
|
// src/hooks/variant-hook.ts
|
|
23078
|
-
var HIVE_AGENT_NAMES = [
|
|
23079
|
-
"hive-master",
|
|
23080
|
-
"architect-planner",
|
|
23081
|
-
"swarm-orchestrator",
|
|
23082
|
-
"scout-researcher",
|
|
23083
|
-
"forager-worker",
|
|
23084
|
-
"hygienic-reviewer"
|
|
23085
|
-
];
|
|
23086
|
-
function isHiveAgent(agent) {
|
|
23087
|
-
return agent !== undefined && HIVE_AGENT_NAMES.includes(agent);
|
|
23088
|
-
}
|
|
23089
23222
|
function normalizeVariant(variant) {
|
|
23090
23223
|
if (variant === undefined)
|
|
23091
23224
|
return;
|
|
23092
23225
|
const trimmed2 = variant.trim();
|
|
23093
23226
|
return trimmed2.length > 0 ? trimmed2 : undefined;
|
|
23094
23227
|
}
|
|
23228
|
+
function createVariantHook(configService) {
|
|
23229
|
+
return async (input, output) => {
|
|
23230
|
+
const { agent } = input;
|
|
23231
|
+
if (!agent)
|
|
23232
|
+
return;
|
|
23233
|
+
if (!configService.hasConfiguredAgent(agent))
|
|
23234
|
+
return;
|
|
23235
|
+
if (output.message.variant !== undefined)
|
|
23236
|
+
return;
|
|
23237
|
+
const agentConfig = configService.getAgentConfig(agent);
|
|
23238
|
+
const configuredVariant = normalizeVariant(agentConfig.variant);
|
|
23239
|
+
if (configuredVariant !== undefined) {
|
|
23240
|
+
output.message.variant = configuredVariant;
|
|
23241
|
+
}
|
|
23242
|
+
};
|
|
23243
|
+
}
|
|
23095
23244
|
|
|
23096
23245
|
// src/hooks/system-hook.ts
|
|
23097
23246
|
var fallbackTurnCounters = {};
|
|
@@ -23138,9 +23287,8 @@ function formatSkillsXml(skills) {
|
|
|
23138
23287
|
${skillsXml}
|
|
23139
23288
|
</available_skills>`;
|
|
23140
23289
|
}
|
|
23141
|
-
async function buildAutoLoadedSkillsContent(agentName, configService, projectRoot) {
|
|
23142
|
-
const
|
|
23143
|
-
const autoLoadSkills = agentConfig.autoLoadSkills ?? [];
|
|
23290
|
+
async function buildAutoLoadedSkillsContent(agentName, configService, projectRoot, autoLoadSkillsOverride) {
|
|
23291
|
+
const autoLoadSkills = autoLoadSkillsOverride ?? (configService.getAgentConfig(agentName).autoLoadSkills ?? []);
|
|
23144
23292
|
if (autoLoadSkills.length === 0) {
|
|
23145
23293
|
return "";
|
|
23146
23294
|
}
|
|
@@ -23295,6 +23443,392 @@ To unblock: Remove .hive/features/${feature}/BLOCKED`;
|
|
|
23295
23443
|
}
|
|
23296
23444
|
return { allowed: true };
|
|
23297
23445
|
};
|
|
23446
|
+
const respond = (payload) => JSON.stringify(payload, null, 2);
|
|
23447
|
+
const buildWorktreeLaunchResponse = async ({
|
|
23448
|
+
feature,
|
|
23449
|
+
task,
|
|
23450
|
+
taskInfo,
|
|
23451
|
+
worktree,
|
|
23452
|
+
continueFrom,
|
|
23453
|
+
decision
|
|
23454
|
+
}) => {
|
|
23455
|
+
taskService.update(feature, task, {
|
|
23456
|
+
status: "in_progress",
|
|
23457
|
+
baseCommit: worktree.commit
|
|
23458
|
+
});
|
|
23459
|
+
const planResult = planService.read(feature);
|
|
23460
|
+
const allTasks = taskService.list(feature);
|
|
23461
|
+
const rawContextFiles = contextService.list(feature).map((f) => ({
|
|
23462
|
+
name: f.name,
|
|
23463
|
+
content: f.content
|
|
23464
|
+
}));
|
|
23465
|
+
const rawPreviousTasks = allTasks.filter((t) => t.status === "done" && t.summary).map((t) => ({ name: t.folder, summary: t.summary }));
|
|
23466
|
+
const taskBudgetResult = applyTaskBudget(rawPreviousTasks, { ...DEFAULT_BUDGET, feature });
|
|
23467
|
+
const contextBudgetResult = applyContextBudget(rawContextFiles, { ...DEFAULT_BUDGET, feature });
|
|
23468
|
+
const contextFiles = contextBudgetResult.files.map((f) => ({
|
|
23469
|
+
name: f.name,
|
|
23470
|
+
content: f.content
|
|
23471
|
+
}));
|
|
23472
|
+
const previousTasks = taskBudgetResult.tasks.map((t) => ({
|
|
23473
|
+
name: t.name,
|
|
23474
|
+
summary: t.summary
|
|
23475
|
+
}));
|
|
23476
|
+
const truncationEvents = [
|
|
23477
|
+
...taskBudgetResult.truncationEvents,
|
|
23478
|
+
...contextBudgetResult.truncationEvents
|
|
23479
|
+
];
|
|
23480
|
+
const droppedTasksHint = taskBudgetResult.droppedTasksHint;
|
|
23481
|
+
const taskOrder = parseInt(taskInfo.folder.match(/^(\d+)/)?.[1] || "0", 10);
|
|
23482
|
+
const status = taskService.getRawStatus(feature, task);
|
|
23483
|
+
const dependsOn = status?.dependsOn ?? [];
|
|
23484
|
+
const specContent = taskService.buildSpecContent({
|
|
23485
|
+
featureName: feature,
|
|
23486
|
+
task: {
|
|
23487
|
+
folder: task,
|
|
23488
|
+
name: taskInfo.planTitle ?? taskInfo.name,
|
|
23489
|
+
order: taskOrder,
|
|
23490
|
+
description: undefined
|
|
23491
|
+
},
|
|
23492
|
+
dependsOn,
|
|
23493
|
+
allTasks: allTasks.map((t) => ({
|
|
23494
|
+
folder: t.folder,
|
|
23495
|
+
name: t.name,
|
|
23496
|
+
order: parseInt(t.folder.match(/^(\d+)/)?.[1] || "0", 10)
|
|
23497
|
+
})),
|
|
23498
|
+
planContent: planResult?.content ?? null,
|
|
23499
|
+
contextFiles,
|
|
23500
|
+
completedTasks: previousTasks
|
|
23501
|
+
});
|
|
23502
|
+
taskService.writeSpec(feature, task, specContent);
|
|
23503
|
+
const workerPrompt = buildWorkerPrompt({
|
|
23504
|
+
feature,
|
|
23505
|
+
task,
|
|
23506
|
+
taskOrder,
|
|
23507
|
+
worktreePath: worktree.path,
|
|
23508
|
+
branch: worktree.branch,
|
|
23509
|
+
plan: planResult?.content || "No plan available",
|
|
23510
|
+
contextFiles,
|
|
23511
|
+
spec: specContent,
|
|
23512
|
+
previousTasks,
|
|
23513
|
+
continueFrom: continueFrom === "blocked" ? {
|
|
23514
|
+
status: "blocked",
|
|
23515
|
+
previousSummary: taskInfo.summary || "No previous summary",
|
|
23516
|
+
decision: decision || "No decision provided"
|
|
23517
|
+
} : undefined
|
|
23518
|
+
});
|
|
23519
|
+
const customAgentConfigs = configService.getCustomAgentConfigs();
|
|
23520
|
+
const defaultAgent = "forager-worker";
|
|
23521
|
+
const eligibleAgents = [
|
|
23522
|
+
{
|
|
23523
|
+
name: defaultAgent,
|
|
23524
|
+
baseAgent: defaultAgent,
|
|
23525
|
+
description: "Default implementation worker"
|
|
23526
|
+
},
|
|
23527
|
+
...Object.entries(customAgentConfigs).filter(([, config2]) => config2.baseAgent === "forager-worker").sort(([left], [right]) => left.localeCompare(right)).map(([name, config2]) => ({
|
|
23528
|
+
name,
|
|
23529
|
+
baseAgent: config2.baseAgent,
|
|
23530
|
+
description: config2.description
|
|
23531
|
+
}))
|
|
23532
|
+
];
|
|
23533
|
+
const agent = defaultAgent;
|
|
23534
|
+
const rawStatus = taskService.getRawStatus(feature, task);
|
|
23535
|
+
const attempt = (rawStatus?.workerSession?.attempt || 0) + 1;
|
|
23536
|
+
const idempotencyKey = `hive-${feature}-${task}-${attempt}`;
|
|
23537
|
+
taskService.patchBackgroundFields(feature, task, { idempotencyKey });
|
|
23538
|
+
const contextContent = contextFiles.map((f) => f.content).join(`
|
|
23539
|
+
|
|
23540
|
+
`);
|
|
23541
|
+
const previousTasksContent = previousTasks.map((t) => `- **${t.name}**: ${t.summary}`).join(`
|
|
23542
|
+
`);
|
|
23543
|
+
const promptMeta = calculatePromptMeta({
|
|
23544
|
+
plan: planResult?.content || "",
|
|
23545
|
+
context: contextContent,
|
|
23546
|
+
previousTasks: previousTasksContent,
|
|
23547
|
+
spec: specContent,
|
|
23548
|
+
workerPrompt
|
|
23549
|
+
});
|
|
23550
|
+
const hiveDir = path8.join(directory, ".hive");
|
|
23551
|
+
const workerPromptPath = writeWorkerPromptFile(feature, task, workerPrompt, hiveDir);
|
|
23552
|
+
const relativePromptPath = normalizePath(path8.relative(directory, workerPromptPath));
|
|
23553
|
+
const PREVIEW_MAX_LENGTH = 200;
|
|
23554
|
+
const workerPromptPreview = workerPrompt.length > PREVIEW_MAX_LENGTH ? workerPrompt.slice(0, PREVIEW_MAX_LENGTH) + "..." : workerPrompt;
|
|
23555
|
+
const taskToolPrompt = `Follow instructions in @${relativePromptPath}`;
|
|
23556
|
+
const taskToolInstructions = `## Delegation Required
|
|
23557
|
+
|
|
23558
|
+
Choose one of the eligible forager-derived agents below.
|
|
23559
|
+
Default to \`${defaultAgent}\` if no specialist is a better match.
|
|
23560
|
+
|
|
23561
|
+
${eligibleAgents.map((candidate) => `- \`${candidate.name}\` — ${candidate.description}`).join(`
|
|
23562
|
+
`)}
|
|
23563
|
+
|
|
23564
|
+
Use OpenCode's built-in \`task\` tool with the chosen \`subagent_type\` and the provided \`taskToolCall.prompt\` value.
|
|
23565
|
+
\`taskToolCall.subagent_type\` is prefilled with the default for convenience; override it when a specialist in \`eligibleAgents\` is a better match.
|
|
23566
|
+
|
|
23567
|
+
\`\`\`
|
|
23568
|
+
task({
|
|
23569
|
+
subagent_type: "<chosen-agent>",
|
|
23570
|
+
description: "Hive: ${task}",
|
|
23571
|
+
prompt: "${taskToolPrompt}"
|
|
23572
|
+
})
|
|
23573
|
+
\`\`\`
|
|
23574
|
+
|
|
23575
|
+
Use the \`@path\` attachment syntax in the prompt to reference the file. Do not inline the file contents.
|
|
23576
|
+
|
|
23577
|
+
`;
|
|
23578
|
+
const responseBase = {
|
|
23579
|
+
success: true,
|
|
23580
|
+
terminal: false,
|
|
23581
|
+
worktreePath: worktree.path,
|
|
23582
|
+
branch: worktree.branch,
|
|
23583
|
+
mode: "delegate",
|
|
23584
|
+
agent,
|
|
23585
|
+
defaultAgent,
|
|
23586
|
+
eligibleAgents,
|
|
23587
|
+
delegationRequired: true,
|
|
23588
|
+
workerPromptPath: relativePromptPath,
|
|
23589
|
+
workerPromptPreview,
|
|
23590
|
+
taskPromptMode: "opencode-at-file",
|
|
23591
|
+
taskToolCall: {
|
|
23592
|
+
subagent_type: agent,
|
|
23593
|
+
description: `Hive: ${task}`,
|
|
23594
|
+
prompt: taskToolPrompt
|
|
23595
|
+
},
|
|
23596
|
+
instructions: taskToolInstructions
|
|
23597
|
+
};
|
|
23598
|
+
const jsonPayload = JSON.stringify(responseBase, null, 2);
|
|
23599
|
+
const payloadMeta = calculatePayloadMeta({
|
|
23600
|
+
jsonPayload,
|
|
23601
|
+
promptInlined: false,
|
|
23602
|
+
promptReferencedByFile: true
|
|
23603
|
+
});
|
|
23604
|
+
const sizeWarnings = checkWarnings(promptMeta, payloadMeta);
|
|
23605
|
+
const budgetWarnings = truncationEvents.map((event) => ({
|
|
23606
|
+
type: event.type,
|
|
23607
|
+
severity: "info",
|
|
23608
|
+
message: event.message,
|
|
23609
|
+
affected: event.affected,
|
|
23610
|
+
count: event.count
|
|
23611
|
+
}));
|
|
23612
|
+
const allWarnings = [...sizeWarnings, ...budgetWarnings];
|
|
23613
|
+
return respond({
|
|
23614
|
+
...responseBase,
|
|
23615
|
+
promptMeta,
|
|
23616
|
+
payloadMeta,
|
|
23617
|
+
budgetApplied: {
|
|
23618
|
+
maxTasks: DEFAULT_BUDGET.maxTasks,
|
|
23619
|
+
maxSummaryChars: DEFAULT_BUDGET.maxSummaryChars,
|
|
23620
|
+
maxContextChars: DEFAULT_BUDGET.maxContextChars,
|
|
23621
|
+
maxTotalContextChars: DEFAULT_BUDGET.maxTotalContextChars,
|
|
23622
|
+
tasksIncluded: previousTasks.length,
|
|
23623
|
+
tasksDropped: rawPreviousTasks.length - previousTasks.length,
|
|
23624
|
+
droppedTasksHint
|
|
23625
|
+
},
|
|
23626
|
+
warnings: allWarnings.length > 0 ? allWarnings : undefined
|
|
23627
|
+
});
|
|
23628
|
+
};
|
|
23629
|
+
const executeWorktreeStart = async ({
|
|
23630
|
+
task,
|
|
23631
|
+
feature: explicitFeature
|
|
23632
|
+
}) => {
|
|
23633
|
+
const feature = resolveFeature(explicitFeature);
|
|
23634
|
+
if (!feature) {
|
|
23635
|
+
return respond({
|
|
23636
|
+
success: false,
|
|
23637
|
+
terminal: true,
|
|
23638
|
+
error: "No feature specified. Create a feature or provide feature param.",
|
|
23639
|
+
reason: "feature_required",
|
|
23640
|
+
task,
|
|
23641
|
+
hints: [
|
|
23642
|
+
"Create/select a feature first or pass the feature parameter explicitly.",
|
|
23643
|
+
"Use hive_status to inspect the active feature state before retrying."
|
|
23644
|
+
]
|
|
23645
|
+
});
|
|
23646
|
+
}
|
|
23647
|
+
const blockedMessage = checkBlocked(feature);
|
|
23648
|
+
if (blockedMessage) {
|
|
23649
|
+
return respond({
|
|
23650
|
+
success: false,
|
|
23651
|
+
terminal: true,
|
|
23652
|
+
error: blockedMessage,
|
|
23653
|
+
reason: "feature_blocked",
|
|
23654
|
+
feature,
|
|
23655
|
+
task,
|
|
23656
|
+
hints: [
|
|
23657
|
+
"Wait for the human to unblock the feature before retrying.",
|
|
23658
|
+
`If approved, remove .hive/features/${feature}/BLOCKED and retry hive_worktree_start.`
|
|
23659
|
+
]
|
|
23660
|
+
});
|
|
23661
|
+
}
|
|
23662
|
+
const taskInfo = taskService.get(feature, task);
|
|
23663
|
+
if (!taskInfo) {
|
|
23664
|
+
return respond({
|
|
23665
|
+
success: false,
|
|
23666
|
+
terminal: true,
|
|
23667
|
+
error: `Task "${task}" not found`,
|
|
23668
|
+
reason: "task_not_found",
|
|
23669
|
+
feature,
|
|
23670
|
+
task,
|
|
23671
|
+
hints: [
|
|
23672
|
+
"Check the task folder name in tasks.json or hive_status output.",
|
|
23673
|
+
"Run hive_tasks_sync if the approved plan has changed and tasks need regeneration."
|
|
23674
|
+
]
|
|
23675
|
+
});
|
|
23676
|
+
}
|
|
23677
|
+
if (taskInfo.status === "done") {
|
|
23678
|
+
return respond({
|
|
23679
|
+
success: false,
|
|
23680
|
+
terminal: true,
|
|
23681
|
+
error: `Task "${task}" is already completed (status: done). It cannot be restarted.`,
|
|
23682
|
+
currentStatus: "done",
|
|
23683
|
+
hints: [
|
|
23684
|
+
"Use hive_merge to integrate the completed task branch if not already merged.",
|
|
23685
|
+
"Use hive_status to see all task states and find the next runnable task."
|
|
23686
|
+
]
|
|
23687
|
+
});
|
|
23688
|
+
}
|
|
23689
|
+
if (taskInfo.status === "blocked") {
|
|
23690
|
+
return respond({
|
|
23691
|
+
success: false,
|
|
23692
|
+
terminal: true,
|
|
23693
|
+
error: `Task "${task}" is blocked and must be resumed with hive_worktree_create using continueFrom: 'blocked'.`,
|
|
23694
|
+
currentStatus: "blocked",
|
|
23695
|
+
feature,
|
|
23696
|
+
task,
|
|
23697
|
+
hints: [
|
|
23698
|
+
'Ask the user the blocker question, then call hive_worktree_create({ task, continueFrom: "blocked", decision }).',
|
|
23699
|
+
"Use hive_status to inspect blocker details before retrying."
|
|
23700
|
+
]
|
|
23701
|
+
});
|
|
23702
|
+
}
|
|
23703
|
+
const depCheck = checkDependencies(feature, task);
|
|
23704
|
+
if (!depCheck.allowed) {
|
|
23705
|
+
return respond({
|
|
23706
|
+
success: false,
|
|
23707
|
+
terminal: true,
|
|
23708
|
+
reason: "dependencies_not_done",
|
|
23709
|
+
feature,
|
|
23710
|
+
task,
|
|
23711
|
+
error: depCheck.error,
|
|
23712
|
+
hints: [
|
|
23713
|
+
"Complete the required dependencies before starting this task.",
|
|
23714
|
+
"Use hive_status to see current task states."
|
|
23715
|
+
]
|
|
23716
|
+
});
|
|
23717
|
+
}
|
|
23718
|
+
const worktree = await worktreeService.create(feature, task);
|
|
23719
|
+
return buildWorktreeLaunchResponse({ feature, task, taskInfo, worktree });
|
|
23720
|
+
};
|
|
23721
|
+
const executeBlockedResume = async ({
|
|
23722
|
+
task,
|
|
23723
|
+
feature: explicitFeature,
|
|
23724
|
+
continueFrom,
|
|
23725
|
+
decision
|
|
23726
|
+
}) => {
|
|
23727
|
+
const feature = resolveFeature(explicitFeature);
|
|
23728
|
+
if (!feature) {
|
|
23729
|
+
return respond({
|
|
23730
|
+
success: false,
|
|
23731
|
+
terminal: true,
|
|
23732
|
+
error: "No feature specified. Create a feature or provide feature param.",
|
|
23733
|
+
reason: "feature_required",
|
|
23734
|
+
task,
|
|
23735
|
+
hints: [
|
|
23736
|
+
"Create/select a feature first or pass the feature parameter explicitly.",
|
|
23737
|
+
"Use hive_status to inspect the active feature state before retrying."
|
|
23738
|
+
]
|
|
23739
|
+
});
|
|
23740
|
+
}
|
|
23741
|
+
const blockedMessage = checkBlocked(feature);
|
|
23742
|
+
if (blockedMessage) {
|
|
23743
|
+
return respond({
|
|
23744
|
+
success: false,
|
|
23745
|
+
terminal: true,
|
|
23746
|
+
error: blockedMessage,
|
|
23747
|
+
reason: "feature_blocked",
|
|
23748
|
+
feature,
|
|
23749
|
+
task,
|
|
23750
|
+
hints: [
|
|
23751
|
+
"Wait for the human to unblock the feature before retrying.",
|
|
23752
|
+
`If approved, remove .hive/features/${feature}/BLOCKED and retry hive_worktree_create.`
|
|
23753
|
+
]
|
|
23754
|
+
});
|
|
23755
|
+
}
|
|
23756
|
+
const taskInfo = taskService.get(feature, task);
|
|
23757
|
+
if (!taskInfo) {
|
|
23758
|
+
return respond({
|
|
23759
|
+
success: false,
|
|
23760
|
+
terminal: true,
|
|
23761
|
+
error: `Task "${task}" not found`,
|
|
23762
|
+
reason: "task_not_found",
|
|
23763
|
+
feature,
|
|
23764
|
+
task,
|
|
23765
|
+
hints: [
|
|
23766
|
+
"Check the task folder name in tasks.json or hive_status output.",
|
|
23767
|
+
"Run hive_tasks_sync if the approved plan has changed and tasks need regeneration."
|
|
23768
|
+
]
|
|
23769
|
+
});
|
|
23770
|
+
}
|
|
23771
|
+
if (taskInfo.status === "done") {
|
|
23772
|
+
return respond({
|
|
23773
|
+
success: false,
|
|
23774
|
+
terminal: true,
|
|
23775
|
+
error: `Task "${task}" is already completed (status: done). It cannot be restarted.`,
|
|
23776
|
+
currentStatus: "done",
|
|
23777
|
+
hints: [
|
|
23778
|
+
"Use hive_merge to integrate the completed task branch if not already merged.",
|
|
23779
|
+
"Use hive_status to see all task states and find the next runnable task."
|
|
23780
|
+
]
|
|
23781
|
+
});
|
|
23782
|
+
}
|
|
23783
|
+
if (continueFrom !== "blocked") {
|
|
23784
|
+
return respond({
|
|
23785
|
+
success: false,
|
|
23786
|
+
terminal: true,
|
|
23787
|
+
error: "hive_worktree_create is only for resuming blocked tasks.",
|
|
23788
|
+
reason: "blocked_resume_required",
|
|
23789
|
+
currentStatus: taskInfo.status,
|
|
23790
|
+
feature,
|
|
23791
|
+
task,
|
|
23792
|
+
hints: [
|
|
23793
|
+
"Use hive_worktree_start({ feature, task }) to start a pending or in-progress task normally.",
|
|
23794
|
+
'Use hive_worktree_create({ task, continueFrom: "blocked", decision }) only after hive_status confirms the task is blocked.'
|
|
23795
|
+
]
|
|
23796
|
+
});
|
|
23797
|
+
}
|
|
23798
|
+
if (taskInfo.status !== "blocked") {
|
|
23799
|
+
return respond({
|
|
23800
|
+
success: false,
|
|
23801
|
+
terminal: true,
|
|
23802
|
+
error: `continueFrom: 'blocked' was specified but task "${task}" is not in blocked state (current status: ${taskInfo.status}).`,
|
|
23803
|
+
currentStatus: taskInfo.status,
|
|
23804
|
+
hints: [
|
|
23805
|
+
"Use hive_worktree_start({ feature, task }) for normal starts or re-dispatch.",
|
|
23806
|
+
"Use hive_status to verify the current task status before retrying."
|
|
23807
|
+
]
|
|
23808
|
+
});
|
|
23809
|
+
}
|
|
23810
|
+
const worktree = await worktreeService.get(feature, task);
|
|
23811
|
+
if (!worktree) {
|
|
23812
|
+
return respond({
|
|
23813
|
+
success: false,
|
|
23814
|
+
terminal: true,
|
|
23815
|
+
error: `Cannot resume blocked task "${task}": no existing worktree record found.`,
|
|
23816
|
+
currentStatus: taskInfo.status,
|
|
23817
|
+
hints: [
|
|
23818
|
+
"The worktree may have been removed manually. Use hive_worktree_discard to reset the task to pending, then restart it with hive_worktree_start.",
|
|
23819
|
+
"Use hive_status to inspect the current state of the task and its worktree."
|
|
23820
|
+
]
|
|
23821
|
+
});
|
|
23822
|
+
}
|
|
23823
|
+
return buildWorktreeLaunchResponse({
|
|
23824
|
+
feature,
|
|
23825
|
+
task,
|
|
23826
|
+
taskInfo,
|
|
23827
|
+
worktree,
|
|
23828
|
+
continueFrom,
|
|
23829
|
+
decision
|
|
23830
|
+
});
|
|
23831
|
+
};
|
|
23298
23832
|
return {
|
|
23299
23833
|
"experimental.chat.system.transform": async (input, output) => {
|
|
23300
23834
|
if (!shouldExecuteHook("experimental.chat.system.transform", configService, turnCounters)) {
|
|
@@ -23323,20 +23857,7 @@ To unblock: Remove .hive/features/${feature}/BLOCKED`;
|
|
|
23323
23857
|
"experimental.session.compacting": async (_input, output) => {
|
|
23324
23858
|
output.context.push(buildCompactionPrompt());
|
|
23325
23859
|
},
|
|
23326
|
-
"chat.message":
|
|
23327
|
-
const { agent } = input;
|
|
23328
|
-
if (!agent)
|
|
23329
|
-
return;
|
|
23330
|
-
if (!isHiveAgent(agent))
|
|
23331
|
-
return;
|
|
23332
|
-
if (output.message.variant !== undefined)
|
|
23333
|
-
return;
|
|
23334
|
-
const agentConfig = configService.getAgentConfig(agent);
|
|
23335
|
-
const configuredVariant = normalizeVariant(agentConfig.variant);
|
|
23336
|
-
if (configuredVariant !== undefined) {
|
|
23337
|
-
output.message.variant = configuredVariant;
|
|
23338
|
-
}
|
|
23339
|
-
},
|
|
23860
|
+
"chat.message": createVariantHook(configService),
|
|
23340
23861
|
"tool.execute.before": async (input, output) => {
|
|
23341
23862
|
if (!shouldExecuteHook("tool.execute.before", configService, turnCounters, { safetyCritical: true })) {
|
|
23342
23863
|
return;
|
|
@@ -23529,7 +24050,7 @@ Expand your Discovery section and try again.`;
|
|
|
23529
24050
|
return "Error: No feature specified. Create a feature or provide feature param.";
|
|
23530
24051
|
const folder = taskService.create(feature, name, order);
|
|
23531
24052
|
return `Manual task created: ${folder}
|
|
23532
|
-
Reminder: start work with
|
|
24053
|
+
Reminder: start work with hive_worktree_start to use its worktree, and ensure any subagents work in that worktree too.`;
|
|
23533
24054
|
}
|
|
23534
24055
|
}),
|
|
23535
24056
|
hive_task_update: tool({
|
|
@@ -23551,198 +24072,26 @@ Reminder: start work with hive_worktree_create to use its worktree, and ensure a
|
|
|
23551
24072
|
return `Task "${task}" updated: status=${updated.status}`;
|
|
23552
24073
|
}
|
|
23553
24074
|
}),
|
|
24075
|
+
hive_worktree_start: tool({
|
|
24076
|
+
description: "Create worktree and begin work on pending/in-progress task. Spawns Forager worker automatically.",
|
|
24077
|
+
args: {
|
|
24078
|
+
task: tool.schema.string().describe("Task folder name"),
|
|
24079
|
+
feature: tool.schema.string().optional().describe("Feature name (defaults to detection or single feature)")
|
|
24080
|
+
},
|
|
24081
|
+
async execute({ task, feature: explicitFeature }) {
|
|
24082
|
+
return executeWorktreeStart({ task, feature: explicitFeature });
|
|
24083
|
+
}
|
|
24084
|
+
}),
|
|
23554
24085
|
hive_worktree_create: tool({
|
|
23555
|
-
description: "
|
|
24086
|
+
description: "Resume a blocked task in its existing worktree. Spawns Forager worker automatically.",
|
|
23556
24087
|
args: {
|
|
23557
24088
|
task: tool.schema.string().describe("Task folder name"),
|
|
23558
24089
|
feature: tool.schema.string().optional().describe("Feature name (defaults to detection or single feature)"),
|
|
23559
24090
|
continueFrom: tool.schema.enum(["blocked"]).optional().describe("Resume a blocked task"),
|
|
23560
24091
|
decision: tool.schema.string().optional().describe("Answer to blocker question when continuing")
|
|
23561
24092
|
},
|
|
23562
|
-
async execute({ task, feature: explicitFeature, continueFrom, decision }
|
|
23563
|
-
|
|
23564
|
-
if (!feature)
|
|
23565
|
-
return "Error: No feature specified. Create a feature or provide feature param.";
|
|
23566
|
-
const blockedMessage = checkBlocked(feature);
|
|
23567
|
-
if (blockedMessage)
|
|
23568
|
-
return blockedMessage;
|
|
23569
|
-
const taskInfo = taskService.get(feature, task);
|
|
23570
|
-
if (!taskInfo)
|
|
23571
|
-
return `Error: Task "${task}" not found`;
|
|
23572
|
-
if (taskInfo.status === "done")
|
|
23573
|
-
return "Error: Task already completed";
|
|
23574
|
-
if (continueFrom === "blocked" && taskInfo.status !== "blocked") {
|
|
23575
|
-
return "Error: Task is not in blocked state. Use without continueFrom.";
|
|
23576
|
-
}
|
|
23577
|
-
if (continueFrom !== "blocked") {
|
|
23578
|
-
const depCheck = checkDependencies(feature, task);
|
|
23579
|
-
if (!depCheck.allowed) {
|
|
23580
|
-
return JSON.stringify({
|
|
23581
|
-
success: false,
|
|
23582
|
-
error: depCheck.error,
|
|
23583
|
-
hints: [
|
|
23584
|
-
"Complete the required dependencies before starting this task.",
|
|
23585
|
-
"Use hive_status to see current task states."
|
|
23586
|
-
]
|
|
23587
|
-
});
|
|
23588
|
-
}
|
|
23589
|
-
}
|
|
23590
|
-
let worktree;
|
|
23591
|
-
if (continueFrom === "blocked") {
|
|
23592
|
-
worktree = await worktreeService.get(feature, task);
|
|
23593
|
-
if (!worktree)
|
|
23594
|
-
return "Error: No worktree found for blocked task";
|
|
23595
|
-
} else {
|
|
23596
|
-
worktree = await worktreeService.create(feature, task);
|
|
23597
|
-
}
|
|
23598
|
-
taskService.update(feature, task, {
|
|
23599
|
-
status: "in_progress",
|
|
23600
|
-
baseCommit: worktree.commit
|
|
23601
|
-
});
|
|
23602
|
-
const planResult = planService.read(feature);
|
|
23603
|
-
const allTasks = taskService.list(feature);
|
|
23604
|
-
const rawContextFiles = contextService.list(feature).map((f) => ({
|
|
23605
|
-
name: f.name,
|
|
23606
|
-
content: f.content
|
|
23607
|
-
}));
|
|
23608
|
-
const rawPreviousTasks = allTasks.filter((t) => t.status === "done" && t.summary).map((t) => ({ name: t.folder, summary: t.summary }));
|
|
23609
|
-
const taskBudgetResult = applyTaskBudget(rawPreviousTasks, { ...DEFAULT_BUDGET, feature });
|
|
23610
|
-
const contextBudgetResult = applyContextBudget(rawContextFiles, { ...DEFAULT_BUDGET, feature });
|
|
23611
|
-
const contextFiles = contextBudgetResult.files.map((f) => ({
|
|
23612
|
-
name: f.name,
|
|
23613
|
-
content: f.content
|
|
23614
|
-
}));
|
|
23615
|
-
const previousTasks = taskBudgetResult.tasks.map((t) => ({
|
|
23616
|
-
name: t.name,
|
|
23617
|
-
summary: t.summary
|
|
23618
|
-
}));
|
|
23619
|
-
const truncationEvents = [
|
|
23620
|
-
...taskBudgetResult.truncationEvents,
|
|
23621
|
-
...contextBudgetResult.truncationEvents
|
|
23622
|
-
];
|
|
23623
|
-
const droppedTasksHint = taskBudgetResult.droppedTasksHint;
|
|
23624
|
-
const taskOrder = parseInt(taskInfo.folder.match(/^(\d+)/)?.[1] || "0", 10);
|
|
23625
|
-
const status = taskService.getRawStatus(feature, task);
|
|
23626
|
-
const dependsOn = status?.dependsOn ?? [];
|
|
23627
|
-
const specContent = taskService.buildSpecContent({
|
|
23628
|
-
featureName: feature,
|
|
23629
|
-
task: {
|
|
23630
|
-
folder: task,
|
|
23631
|
-
name: taskInfo.planTitle ?? taskInfo.name,
|
|
23632
|
-
order: taskOrder,
|
|
23633
|
-
description: undefined
|
|
23634
|
-
},
|
|
23635
|
-
dependsOn,
|
|
23636
|
-
allTasks: allTasks.map((t) => ({
|
|
23637
|
-
folder: t.folder,
|
|
23638
|
-
name: t.name,
|
|
23639
|
-
order: parseInt(t.folder.match(/^(\d+)/)?.[1] || "0", 10)
|
|
23640
|
-
})),
|
|
23641
|
-
planContent: planResult?.content ?? null,
|
|
23642
|
-
contextFiles,
|
|
23643
|
-
completedTasks: previousTasks
|
|
23644
|
-
});
|
|
23645
|
-
taskService.writeSpec(feature, task, specContent);
|
|
23646
|
-
const workerPrompt = buildWorkerPrompt({
|
|
23647
|
-
feature,
|
|
23648
|
-
task,
|
|
23649
|
-
taskOrder: parseInt(taskInfo.folder.match(/^(\d+)/)?.[1] || "0", 10),
|
|
23650
|
-
worktreePath: worktree.path,
|
|
23651
|
-
branch: worktree.branch,
|
|
23652
|
-
plan: planResult?.content || "No plan available",
|
|
23653
|
-
contextFiles,
|
|
23654
|
-
spec: specContent,
|
|
23655
|
-
previousTasks,
|
|
23656
|
-
continueFrom: continueFrom === "blocked" ? {
|
|
23657
|
-
status: "blocked",
|
|
23658
|
-
previousSummary: taskInfo.summary || "No previous summary",
|
|
23659
|
-
decision: decision || "No decision provided"
|
|
23660
|
-
} : undefined
|
|
23661
|
-
});
|
|
23662
|
-
const agent = "forager-worker";
|
|
23663
|
-
const rawStatus = taskService.getRawStatus(feature, task);
|
|
23664
|
-
const attempt = (rawStatus?.workerSession?.attempt || 0) + 1;
|
|
23665
|
-
const idempotencyKey = `hive-${feature}-${task}-${attempt}`;
|
|
23666
|
-
taskService.patchBackgroundFields(feature, task, { idempotencyKey });
|
|
23667
|
-
const contextContent = contextFiles.map((f) => f.content).join(`
|
|
23668
|
-
|
|
23669
|
-
`);
|
|
23670
|
-
const previousTasksContent = previousTasks.map((t) => `- **${t.name}**: ${t.summary}`).join(`
|
|
23671
|
-
`);
|
|
23672
|
-
const promptMeta = calculatePromptMeta({
|
|
23673
|
-
plan: planResult?.content || "",
|
|
23674
|
-
context: contextContent,
|
|
23675
|
-
previousTasks: previousTasksContent,
|
|
23676
|
-
spec: specContent,
|
|
23677
|
-
workerPrompt
|
|
23678
|
-
});
|
|
23679
|
-
const hiveDir = path8.join(directory, ".hive");
|
|
23680
|
-
const workerPromptPath = writeWorkerPromptFile(feature, task, workerPrompt, hiveDir);
|
|
23681
|
-
const relativePromptPath = normalizePath(path8.relative(directory, workerPromptPath));
|
|
23682
|
-
const PREVIEW_MAX_LENGTH = 200;
|
|
23683
|
-
const workerPromptPreview = workerPrompt.length > PREVIEW_MAX_LENGTH ? workerPrompt.slice(0, PREVIEW_MAX_LENGTH) + "..." : workerPrompt;
|
|
23684
|
-
const taskToolPrompt = `Follow instructions in @${relativePromptPath}`;
|
|
23685
|
-
const taskToolInstructions = `## Delegation Required
|
|
23686
|
-
|
|
23687
|
-
Use OpenCode's built-in \`task\` tool to spawn a Forager (Worker/Coder) worker.
|
|
23688
|
-
|
|
23689
|
-
\`\`\`
|
|
23690
|
-
task({
|
|
23691
|
-
subagent_type: "${agent}",
|
|
23692
|
-
description: "Hive: ${task}",
|
|
23693
|
-
prompt: "${taskToolPrompt}"
|
|
23694
|
-
})
|
|
23695
|
-
\`\`\`
|
|
23696
|
-
|
|
23697
|
-
Use the \`@path\` attachment syntax in the prompt to reference the file. Do not inline the file contents.
|
|
23698
|
-
|
|
23699
|
-
`;
|
|
23700
|
-
const responseBase = {
|
|
23701
|
-
worktreePath: worktree.path,
|
|
23702
|
-
branch: worktree.branch,
|
|
23703
|
-
mode: "delegate",
|
|
23704
|
-
agent,
|
|
23705
|
-
delegationRequired: true,
|
|
23706
|
-
workerPromptPath: relativePromptPath,
|
|
23707
|
-
workerPromptPreview,
|
|
23708
|
-
taskPromptMode: "opencode-at-file",
|
|
23709
|
-
taskToolCall: {
|
|
23710
|
-
subagent_type: agent,
|
|
23711
|
-
description: `Hive: ${task}`,
|
|
23712
|
-
prompt: taskToolPrompt
|
|
23713
|
-
},
|
|
23714
|
-
instructions: taskToolInstructions
|
|
23715
|
-
};
|
|
23716
|
-
const jsonPayload = JSON.stringify(responseBase, null, 2);
|
|
23717
|
-
const payloadMeta = calculatePayloadMeta({
|
|
23718
|
-
jsonPayload,
|
|
23719
|
-
promptInlined: false,
|
|
23720
|
-
promptReferencedByFile: true
|
|
23721
|
-
});
|
|
23722
|
-
const sizeWarnings = checkWarnings(promptMeta, payloadMeta);
|
|
23723
|
-
const budgetWarnings = truncationEvents.map((event) => ({
|
|
23724
|
-
type: event.type,
|
|
23725
|
-
severity: "info",
|
|
23726
|
-
message: event.message,
|
|
23727
|
-
affected: event.affected,
|
|
23728
|
-
count: event.count
|
|
23729
|
-
}));
|
|
23730
|
-
const allWarnings = [...sizeWarnings, ...budgetWarnings];
|
|
23731
|
-
return JSON.stringify({
|
|
23732
|
-
...responseBase,
|
|
23733
|
-
promptMeta,
|
|
23734
|
-
payloadMeta,
|
|
23735
|
-
budgetApplied: {
|
|
23736
|
-
maxTasks: DEFAULT_BUDGET.maxTasks,
|
|
23737
|
-
maxSummaryChars: DEFAULT_BUDGET.maxSummaryChars,
|
|
23738
|
-
maxContextChars: DEFAULT_BUDGET.maxContextChars,
|
|
23739
|
-
maxTotalContextChars: DEFAULT_BUDGET.maxTotalContextChars,
|
|
23740
|
-
tasksIncluded: previousTasks.length,
|
|
23741
|
-
tasksDropped: rawPreviousTasks.length - previousTasks.length,
|
|
23742
|
-
droppedTasksHint
|
|
23743
|
-
},
|
|
23744
|
-
warnings: allWarnings.length > 0 ? allWarnings : undefined
|
|
23745
|
-
}, null, 2);
|
|
24093
|
+
async execute({ task, feature: explicitFeature, continueFrom, decision }) {
|
|
24094
|
+
return executeBlockedResume({ task, feature: explicitFeature, continueFrom, decision });
|
|
23746
24095
|
}
|
|
23747
24096
|
}),
|
|
23748
24097
|
hive_worktree_commit: tool({
|
|
@@ -23760,10 +24109,10 @@ Use the \`@path\` attachment syntax in the prompt to reference the file. Do not
|
|
|
23760
24109
|
feature: tool.schema.string().optional().describe("Feature name (defaults to detection or single feature)")
|
|
23761
24110
|
},
|
|
23762
24111
|
async execute({ task, summary, status = "completed", blocker, feature: explicitFeature }) {
|
|
23763
|
-
const
|
|
24112
|
+
const respond2 = (payload) => JSON.stringify(payload, null, 2);
|
|
23764
24113
|
const feature = resolveFeature(explicitFeature);
|
|
23765
24114
|
if (!feature) {
|
|
23766
|
-
return
|
|
24115
|
+
return respond2({
|
|
23767
24116
|
ok: false,
|
|
23768
24117
|
terminal: false,
|
|
23769
24118
|
status: "error",
|
|
@@ -23776,7 +24125,7 @@ Use the \`@path\` attachment syntax in the prompt to reference the file. Do not
|
|
|
23776
24125
|
}
|
|
23777
24126
|
const taskInfo = taskService.get(feature, task);
|
|
23778
24127
|
if (!taskInfo) {
|
|
23779
|
-
return
|
|
24128
|
+
return respond2({
|
|
23780
24129
|
ok: false,
|
|
23781
24130
|
terminal: false,
|
|
23782
24131
|
status: "error",
|
|
@@ -23789,7 +24138,7 @@ Use the \`@path\` attachment syntax in the prompt to reference the file. Do not
|
|
|
23789
24138
|
});
|
|
23790
24139
|
}
|
|
23791
24140
|
if (taskInfo.status !== "in_progress" && taskInfo.status !== "blocked") {
|
|
23792
|
-
return
|
|
24141
|
+
return respond2({
|
|
23793
24142
|
ok: false,
|
|
23794
24143
|
terminal: false,
|
|
23795
24144
|
status: "error",
|
|
@@ -23817,7 +24166,7 @@ Use the \`@path\` attachment syntax in the prompt to reference the file. Do not
|
|
|
23817
24166
|
blocker
|
|
23818
24167
|
});
|
|
23819
24168
|
const worktree2 = await worktreeService.get(feature, task);
|
|
23820
|
-
return
|
|
24169
|
+
return respond2({
|
|
23821
24170
|
ok: true,
|
|
23822
24171
|
terminal: true,
|
|
23823
24172
|
status: "blocked",
|
|
@@ -23835,7 +24184,7 @@ Use the \`@path\` attachment syntax in the prompt to reference the file. Do not
|
|
|
23835
24184
|
}
|
|
23836
24185
|
const commitResult = await worktreeService.commitChanges(feature, task, `hive(${task}): ${summary.slice(0, 50)}`);
|
|
23837
24186
|
if (status === "completed" && !commitResult.committed && commitResult.message !== "No changes to commit") {
|
|
23838
|
-
return
|
|
24187
|
+
return respond2({
|
|
23839
24188
|
ok: false,
|
|
23840
24189
|
terminal: false,
|
|
23841
24190
|
status: "rejected",
|
|
@@ -23887,7 +24236,7 @@ Use the \`@path\` attachment syntax in the prompt to reference the file. Do not
|
|
|
23887
24236
|
const finalStatus = status === "completed" ? "done" : status;
|
|
23888
24237
|
taskService.update(feature, task, { status: finalStatus, summary });
|
|
23889
24238
|
const worktree = await worktreeService.get(feature, task);
|
|
23890
|
-
return
|
|
24239
|
+
return respond2({
|
|
23891
24240
|
ok: true,
|
|
23892
24241
|
terminal: true,
|
|
23893
24242
|
status,
|
|
@@ -23977,23 +24326,40 @@ Files changed: ${result.filesChanged?.length || 0}`;
|
|
|
23977
24326
|
feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
|
|
23978
24327
|
},
|
|
23979
24328
|
async execute({ feature: explicitFeature }) {
|
|
24329
|
+
const respond2 = (payload) => JSON.stringify(payload, null, 2);
|
|
23980
24330
|
const feature = resolveFeature(explicitFeature);
|
|
23981
24331
|
if (!feature) {
|
|
23982
|
-
return
|
|
24332
|
+
return respond2({
|
|
24333
|
+
success: false,
|
|
24334
|
+
terminal: true,
|
|
24335
|
+
reason: "feature_required",
|
|
23983
24336
|
error: "No feature specified and no active feature found",
|
|
23984
24337
|
hint: "Use hive_feature_create to create a new feature"
|
|
23985
24338
|
});
|
|
23986
24339
|
}
|
|
23987
24340
|
const featureData = featureService.get(feature);
|
|
23988
24341
|
if (!featureData) {
|
|
23989
|
-
return
|
|
24342
|
+
return respond2({
|
|
24343
|
+
success: false,
|
|
24344
|
+
terminal: true,
|
|
24345
|
+
reason: "feature_not_found",
|
|
23990
24346
|
error: `Feature '${feature}' not found`,
|
|
23991
24347
|
availableFeatures: featureService.list()
|
|
23992
24348
|
});
|
|
23993
24349
|
}
|
|
23994
24350
|
const blocked = checkBlocked(feature);
|
|
23995
|
-
if (blocked)
|
|
23996
|
-
return
|
|
24351
|
+
if (blocked) {
|
|
24352
|
+
return respond2({
|
|
24353
|
+
success: false,
|
|
24354
|
+
terminal: true,
|
|
24355
|
+
blocked: true,
|
|
24356
|
+
error: blocked,
|
|
24357
|
+
hints: [
|
|
24358
|
+
"Read the blocker details and resolve them before retrying hive_status.",
|
|
24359
|
+
`Remove .hive/features/${feature}/BLOCKED once the blocker is resolved.`
|
|
24360
|
+
]
|
|
24361
|
+
});
|
|
24362
|
+
}
|
|
23997
24363
|
const plan = planService.read(feature);
|
|
23998
24364
|
const tasks = taskService.list(feature);
|
|
23999
24365
|
const contextFiles = contextService.list(feature);
|
|
@@ -24050,7 +24416,7 @@ Files changed: ${result.filesChanged?.length || 0}`;
|
|
|
24050
24416
|
return `${runnableTasks.length} tasks are ready to start in parallel: ${runnableTasks.join(", ")}`;
|
|
24051
24417
|
}
|
|
24052
24418
|
if (runnableTasks.length === 1) {
|
|
24053
|
-
return `Start next task with
|
|
24419
|
+
return `Start next task with hive_worktree_start: ${runnableTasks[0]}`;
|
|
24054
24420
|
}
|
|
24055
24421
|
const pending = tasks2.find((t) => t.status === "pending");
|
|
24056
24422
|
if (pending) {
|
|
@@ -24059,7 +24425,7 @@ Files changed: ${result.filesChanged?.length || 0}`;
|
|
|
24059
24425
|
return "All tasks complete. Review and merge or complete feature.";
|
|
24060
24426
|
};
|
|
24061
24427
|
const planStatus = featureData.status === "planning" ? "draft" : featureData.status === "approved" ? "approved" : featureData.status === "executing" ? "locked" : "none";
|
|
24062
|
-
return
|
|
24428
|
+
return respond2({
|
|
24063
24429
|
feature: {
|
|
24064
24430
|
name: feature,
|
|
24065
24431
|
status: featureData.status,
|
|
@@ -24152,6 +24518,7 @@ ${result.diff}
|
|
|
24152
24518
|
"hive_tasks_sync",
|
|
24153
24519
|
"hive_task_create",
|
|
24154
24520
|
"hive_task_update",
|
|
24521
|
+
"hive_worktree_start",
|
|
24155
24522
|
"hive_worktree_create",
|
|
24156
24523
|
"hive_worktree_commit",
|
|
24157
24524
|
"hive_worktree_discard",
|
|
@@ -24170,6 +24537,14 @@ ${result.diff}
|
|
|
24170
24537
|
return result;
|
|
24171
24538
|
}
|
|
24172
24539
|
configService.init();
|
|
24540
|
+
const hiveConfigData = configService.get();
|
|
24541
|
+
const agentMode = hiveConfigData.agentMode ?? "unified";
|
|
24542
|
+
const customAgentConfigs = configService.getCustomAgentConfigs();
|
|
24543
|
+
const customSubagentAppendix = Object.keys(customAgentConfigs).length === 0 ? "" : `
|
|
24544
|
+
|
|
24545
|
+
## Configured Custom Subagents
|
|
24546
|
+
${Object.entries(customAgentConfigs).sort(([left], [right]) => left.localeCompare(right)).map(([name, config2]) => `- \`${name}\` — derived from \`${config2.baseAgent}\`; ${config2.description}`).join(`
|
|
24547
|
+
`)}`;
|
|
24173
24548
|
const hiveUserConfig = configService.getAgentConfig("hive-master");
|
|
24174
24549
|
const hiveAutoLoadedSkills = await buildAutoLoadedSkillsContent("hive-master", configService, directory);
|
|
24175
24550
|
const hiveConfig = {
|
|
@@ -24177,7 +24552,7 @@ ${result.diff}
|
|
|
24177
24552
|
variant: hiveUserConfig.variant,
|
|
24178
24553
|
temperature: hiveUserConfig.temperature ?? 0.5,
|
|
24179
24554
|
description: "Hive (Hybrid) - Plans + orchestrates. Detects phase, loads skills on-demand.",
|
|
24180
|
-
prompt: QUEEN_BEE_PROMPT + hiveAutoLoadedSkills,
|
|
24555
|
+
prompt: QUEEN_BEE_PROMPT + hiveAutoLoadedSkills + (agentMode === "unified" ? customSubagentAppendix : ""),
|
|
24181
24556
|
permission: {
|
|
24182
24557
|
question: "allow",
|
|
24183
24558
|
skill: "allow",
|
|
@@ -24192,7 +24567,7 @@ ${result.diff}
|
|
|
24192
24567
|
variant: architectUserConfig.variant,
|
|
24193
24568
|
temperature: architectUserConfig.temperature ?? 0.7,
|
|
24194
24569
|
description: "Architect (Planner) - Plans features, interviews, writes plans. NEVER executes.",
|
|
24195
|
-
prompt: ARCHITECT_BEE_PROMPT + architectAutoLoadedSkills,
|
|
24570
|
+
prompt: ARCHITECT_BEE_PROMPT + architectAutoLoadedSkills + (agentMode === "dedicated" ? customSubagentAppendix : ""),
|
|
24196
24571
|
tools: agentTools(["hive_feature_create", "hive_plan_write", "hive_plan_read", "hive_context_write", "hive_status", "hive_skill"]),
|
|
24197
24572
|
permission: {
|
|
24198
24573
|
edit: "deny",
|
|
@@ -24211,7 +24586,7 @@ ${result.diff}
|
|
|
24211
24586
|
variant: swarmUserConfig.variant,
|
|
24212
24587
|
temperature: swarmUserConfig.temperature ?? 0.5,
|
|
24213
24588
|
description: "Swarm (Orchestrator) - Orchestrates execution. Delegates, spawns workers, verifies, merges.",
|
|
24214
|
-
prompt: SWARM_BEE_PROMPT + swarmAutoLoadedSkills,
|
|
24589
|
+
prompt: SWARM_BEE_PROMPT + swarmAutoLoadedSkills + (agentMode === "dedicated" ? customSubagentAppendix : ""),
|
|
24215
24590
|
tools: agentTools([
|
|
24216
24591
|
"hive_feature_create",
|
|
24217
24592
|
"hive_feature_complete",
|
|
@@ -24220,6 +24595,7 @@ ${result.diff}
|
|
|
24220
24595
|
"hive_tasks_sync",
|
|
24221
24596
|
"hive_task_create",
|
|
24222
24597
|
"hive_task_update",
|
|
24598
|
+
"hive_worktree_start",
|
|
24223
24599
|
"hive_worktree_create",
|
|
24224
24600
|
"hive_worktree_discard",
|
|
24225
24601
|
"hive_merge",
|
|
@@ -24286,21 +24662,44 @@ ${result.diff}
|
|
|
24286
24662
|
skill: "allow"
|
|
24287
24663
|
}
|
|
24288
24664
|
};
|
|
24289
|
-
const
|
|
24290
|
-
|
|
24665
|
+
const builtInAgentConfigs = {
|
|
24666
|
+
"hive-master": hiveConfig,
|
|
24667
|
+
"architect-planner": architectConfig,
|
|
24668
|
+
"swarm-orchestrator": swarmConfig,
|
|
24669
|
+
"scout-researcher": scoutConfig,
|
|
24670
|
+
"forager-worker": foragerConfig,
|
|
24671
|
+
"hygienic-reviewer": hygienicConfig
|
|
24672
|
+
};
|
|
24673
|
+
const customAutoLoadedSkills = Object.fromEntries(await Promise.all(Object.entries(customAgentConfigs).map(async ([customAgentName, customAgentConfig]) => {
|
|
24674
|
+
const inheritedBaseSkills = customAgentConfig.baseAgent === "forager-worker" ? foragerUserConfig.autoLoadSkills ?? [] : hygienicUserConfig.autoLoadSkills ?? [];
|
|
24675
|
+
const deltaAutoLoadSkills = (customAgentConfig.autoLoadSkills ?? []).filter((skill) => !inheritedBaseSkills.includes(skill));
|
|
24676
|
+
return [
|
|
24677
|
+
customAgentName,
|
|
24678
|
+
await buildAutoLoadedSkillsContent(customAgentName, configService, directory, deltaAutoLoadSkills)
|
|
24679
|
+
];
|
|
24680
|
+
})));
|
|
24681
|
+
const customSubagents = buildCustomSubagents({
|
|
24682
|
+
customAgents: customAgentConfigs,
|
|
24683
|
+
baseAgents: {
|
|
24684
|
+
"forager-worker": foragerConfig,
|
|
24685
|
+
"hygienic-reviewer": hygienicConfig
|
|
24686
|
+
},
|
|
24687
|
+
autoLoadedSkills: customAutoLoadedSkills
|
|
24688
|
+
});
|
|
24291
24689
|
const allAgents = {};
|
|
24292
24690
|
if (agentMode === "unified") {
|
|
24293
|
-
allAgents["hive-master"] =
|
|
24294
|
-
allAgents["scout-researcher"] =
|
|
24295
|
-
allAgents["forager-worker"] =
|
|
24296
|
-
allAgents["hygienic-reviewer"] =
|
|
24691
|
+
allAgents["hive-master"] = builtInAgentConfigs["hive-master"];
|
|
24692
|
+
allAgents["scout-researcher"] = builtInAgentConfigs["scout-researcher"];
|
|
24693
|
+
allAgents["forager-worker"] = builtInAgentConfigs["forager-worker"];
|
|
24694
|
+
allAgents["hygienic-reviewer"] = builtInAgentConfigs["hygienic-reviewer"];
|
|
24297
24695
|
} else {
|
|
24298
|
-
allAgents["architect-planner"] =
|
|
24299
|
-
allAgents["swarm-orchestrator"] =
|
|
24300
|
-
allAgents["scout-researcher"] =
|
|
24301
|
-
allAgents["forager-worker"] =
|
|
24302
|
-
allAgents["hygienic-reviewer"] =
|
|
24696
|
+
allAgents["architect-planner"] = builtInAgentConfigs["architect-planner"];
|
|
24697
|
+
allAgents["swarm-orchestrator"] = builtInAgentConfigs["swarm-orchestrator"];
|
|
24698
|
+
allAgents["scout-researcher"] = builtInAgentConfigs["scout-researcher"];
|
|
24699
|
+
allAgents["forager-worker"] = builtInAgentConfigs["forager-worker"];
|
|
24700
|
+
allAgents["hygienic-reviewer"] = builtInAgentConfigs["hygienic-reviewer"];
|
|
24303
24701
|
}
|
|
24702
|
+
Object.assign(allAgents, customSubagents);
|
|
24304
24703
|
const configAgent = opencodeConfig.agent;
|
|
24305
24704
|
if (!configAgent) {
|
|
24306
24705
|
opencodeConfig.agent = allAgents;
|