opencode-hive 1.0.7 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -34
- package/dist/agents/architect.d.ts +1 -1
- package/dist/agents/forager.d.ts +1 -1
- package/dist/agents/hive.d.ts +1 -1
- package/dist/agents/scout.d.ts +1 -1
- package/dist/agents/swarm.d.ts +1 -1
- package/dist/index.js +351 -2101
- package/package.json +1 -1
- package/skills/brainstorming/SKILL.md +1 -0
- package/skills/dispatching-parallel-agents/SKILL.md +3 -3
- package/skills/executing-plans/SKILL.md +6 -1
- package/skills/parallel-exploration/SKILL.md +18 -133
- package/dist/background/agent-gate.d.ts +0 -64
- package/dist/background/concurrency.d.ts +0 -102
- package/dist/background/index.d.ts +0 -27
- package/dist/background/manager.d.ts +0 -169
- package/dist/background/poller.d.ts +0 -131
- package/dist/background/store.d.ts +0 -100
- package/dist/background/types.d.ts +0 -246
- package/dist/tools/background-tools.d.ts +0 -37
package/dist/index.js
CHANGED
|
@@ -13,7 +13,6 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
13
13
|
|
|
14
14
|
// src/index.ts
|
|
15
15
|
import * as path7 from "path";
|
|
16
|
-
import * as fs9 from "fs";
|
|
17
16
|
import * as os from "os";
|
|
18
17
|
|
|
19
18
|
// ../../node_modules/zod/v4/classic/external.js
|
|
@@ -12388,7 +12387,8 @@ Start by understanding the current project context, then ask questions one at a
|
|
|
12388
12387
|
- **YAGNI ruthlessly** - Remove unnecessary features from all designs
|
|
12389
12388
|
- **Explore alternatives** - Always propose 2-3 approaches before settling
|
|
12390
12389
|
- **Incremental validation** - Present design in sections, validate each
|
|
12391
|
-
- **Be flexible** - Go back and clarify when something doesn't make sense
|
|
12390
|
+
- **Be flexible** - Go back and clarify when something doesn't make sense
|
|
12391
|
+
- **Challenge assumptions** - Surface fragile assumptions, ask what changes if they fail, offer lean fallback options`
|
|
12392
12392
|
},
|
|
12393
12393
|
{
|
|
12394
12394
|
name: "code-reviewer",
|
|
@@ -12675,9 +12675,9 @@ Each agent gets:
|
|
|
12675
12675
|
|
|
12676
12676
|
\`\`\`typescript
|
|
12677
12677
|
// Using Hive tools for parallel execution
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12678
|
+
hive_worktree_create({ task: "01-fix-abort-tests" })
|
|
12679
|
+
hive_worktree_create({ task: "02-fix-batch-tests" })
|
|
12680
|
+
hive_worktree_create({ task: "03-fix-race-condition-tests" })
|
|
12681
12681
|
// All three run concurrently in isolated worktrees
|
|
12682
12682
|
\`\`\`
|
|
12683
12683
|
|
|
@@ -12832,7 +12832,7 @@ Only \`done\` satisfies dependencies (not \`blocked\`, \`failed\`, \`partial\`,
|
|
|
12832
12832
|
### Step 3: Execute Batch
|
|
12833
12833
|
|
|
12834
12834
|
For each task in the batch:
|
|
12835
|
-
1. Mark as in_progress via \`
|
|
12835
|
+
1. Mark as in_progress via \`hive_worktree_create()\`
|
|
12836
12836
|
2. Follow each step exactly (plan has bite-sized steps)
|
|
12837
12837
|
3. Run verifications as specified
|
|
12838
12838
|
4. Mark as completed
|
|
@@ -12843,6 +12843,11 @@ When batch complete:
|
|
|
12843
12843
|
- Show verification output
|
|
12844
12844
|
- Say: "Ready for feedback."
|
|
12845
12845
|
|
|
12846
|
+
### Step 4.5: Post-Batch Hygienic Review
|
|
12847
|
+
|
|
12848
|
+
After the batch report, ask the operator if they want a Hygienic code review for the batch.
|
|
12849
|
+
If yes, run \`task({ subagent_type: "hygienic", prompt: "Review implementation changes from the latest batch." })\` and apply feedback before starting the next batch.
|
|
12850
|
+
|
|
12846
12851
|
### Step 5: Continue
|
|
12847
12852
|
Based on feedback:
|
|
12848
12853
|
- Apply changes if needed
|
|
@@ -12944,7 +12949,7 @@ Ask one at a time, with the provided options. Store the answers in \`.hive/conte
|
|
|
12944
12949
|
},
|
|
12945
12950
|
{
|
|
12946
12951
|
name: "parallel-exploration",
|
|
12947
|
-
description: "Use when you need parallel, read-only exploration with
|
|
12952
|
+
description: "Use when you need parallel, read-only exploration with task() (Scout fan-out)",
|
|
12948
12953
|
template: `# Parallel Exploration (Scout Fan-Out)
|
|
12949
12954
|
|
|
12950
12955
|
## Overview
|
|
@@ -12955,11 +12960,7 @@ When you need to answer "where/how does X work?" across multiple domains (codeba
|
|
|
12955
12960
|
|
|
12956
12961
|
**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.
|
|
12957
12962
|
|
|
12958
|
-
**This skill is for read-only research.** For parallel implementation work, use \`hive_skill("dispatching-parallel-agents")\` with \`
|
|
12959
|
-
|
|
12960
|
-
**Two valid execution paths:**
|
|
12961
|
-
- **Path A (Hive background tools):** Use \`hive_background_task\`, \`hive_background_output\`, \`hive_background_cancel\` when available.
|
|
12962
|
-
- **Path B (Task mode):** Use native \`task()\` for delegation when background tools are not registered.
|
|
12963
|
+
**This skill is for read-only research.** For parallel implementation work, use \`hive_skill("dispatching-parallel-agents")\` with \`hive_worktree_create\`.
|
|
12963
12964
|
|
|
12964
12965
|
## When to Use
|
|
12965
12966
|
|
|
@@ -12998,51 +12999,16 @@ Split your investigation into 2-4 independent sub-questions. Good decomposition:
|
|
|
12998
12999
|
- "What is X?" then "How is X used?" (second depends on first)
|
|
12999
13000
|
- "Find the bug" then "Fix the bug" (not read-only)
|
|
13000
13001
|
|
|
13001
|
-
### 2. Spawn
|
|
13002
|
+
### 2. Spawn Tasks (Fan-Out)
|
|
13002
13003
|
|
|
13003
13004
|
Launch all tasks before waiting for any results:
|
|
13004
13005
|
|
|
13005
13006
|
\`\`\`typescript
|
|
13006
|
-
// Path A: Hive background tools (when available)
|
|
13007
|
-
// Fan-out: spawn all tasks first
|
|
13008
|
-
hive_background_task({
|
|
13009
|
-
agent: "scout-researcher",
|
|
13010
|
-
description: "Find hive_background_task implementation",
|
|
13011
|
-
prompt: \`Where is hive_background_task implemented and registered?
|
|
13012
|
-
- Find the tool definition
|
|
13013
|
-
- Find the plugin registration
|
|
13014
|
-
- Return file paths with line numbers\`,
|
|
13015
|
-
sync: false
|
|
13016
|
-
})
|
|
13017
|
-
|
|
13018
|
-
hive_background_task({
|
|
13019
|
-
agent: "scout-researcher",
|
|
13020
|
-
description: "Analyze background task concurrency",
|
|
13021
|
-
prompt: \`How does background task concurrency/queueing work?
|
|
13022
|
-
- Find the manager/scheduler code
|
|
13023
|
-
- Document the concurrency model
|
|
13024
|
-
- Return file paths with evidence\`,
|
|
13025
|
-
sync: false
|
|
13026
|
-
})
|
|
13027
|
-
|
|
13028
|
-
hive_background_task({
|
|
13029
|
-
agent: "scout-researcher",
|
|
13030
|
-
description: "Find parent notification mechanism",
|
|
13031
|
-
prompt: \`How does parent notification work for background tasks?
|
|
13032
|
-
- Where is the notification built?
|
|
13033
|
-
- How is it sent to the parent session?
|
|
13034
|
-
- Return file paths with evidence\`,
|
|
13035
|
-
sync: false
|
|
13036
|
-
})
|
|
13037
|
-
\`\`\`
|
|
13038
|
-
|
|
13039
|
-
\`\`\`typescript
|
|
13040
|
-
// Path B: Task mode (native task tool)
|
|
13041
13007
|
// Parallelize by issuing multiple task() calls in the same assistant message.
|
|
13042
13008
|
task({
|
|
13043
13009
|
subagent_type: 'scout-researcher',
|
|
13044
|
-
description: 'Find
|
|
13045
|
-
prompt: \`Where
|
|
13010
|
+
description: 'Find API route implementation',
|
|
13011
|
+
prompt: \`Where are API routes implemented and registered?
|
|
13046
13012
|
- Find the tool definition
|
|
13047
13013
|
- Find the plugin registration
|
|
13048
13014
|
- Return file paths with line numbers\`,
|
|
@@ -13068,8 +13034,7 @@ task({
|
|
|
13068
13034
|
\`\`\`
|
|
13069
13035
|
|
|
13070
13036
|
**Key points:**
|
|
13071
|
-
- Use \`
|
|
13072
|
-
- Use \`sync: false\` to return immediately (non-blocking)
|
|
13037
|
+
- Use \`subagent_type: 'scout-researcher'\` for read-only exploration
|
|
13073
13038
|
- Give each task a clear, focused \`description\`
|
|
13074
13039
|
- Make prompts specific about what evidence to return
|
|
13075
13040
|
|
|
@@ -13084,35 +13049,7 @@ You'll receive a \`<system-reminder>\` notification when each task completes.
|
|
|
13084
13049
|
|
|
13085
13050
|
### 4. Collect Results
|
|
13086
13051
|
|
|
13087
|
-
When
|
|
13088
|
-
|
|
13089
|
-
\`\`\`typescript
|
|
13090
|
-
// Path A: Hive background tools
|
|
13091
|
-
// Get output from completed task
|
|
13092
|
-
hive_background_output({
|
|
13093
|
-
task_id: "task-abc123",
|
|
13094
|
-
block: false // Don't wait, task already done
|
|
13095
|
-
})
|
|
13096
|
-
\`\`\`
|
|
13097
|
-
|
|
13098
|
-
**For incremental output (long-running tasks):**
|
|
13099
|
-
|
|
13100
|
-
\`\`\`typescript
|
|
13101
|
-
// First call - get initial output
|
|
13102
|
-
hive_background_output({
|
|
13103
|
-
task_id: "task-abc123",
|
|
13104
|
-
block: true, // Wait for output
|
|
13105
|
-
timeout: 30000 // 30 second timeout
|
|
13106
|
-
})
|
|
13107
|
-
// Returns: { output: "...", cursor: "5" }
|
|
13108
|
-
|
|
13109
|
-
// Later call - get new output since cursor
|
|
13110
|
-
hive_background_output({
|
|
13111
|
-
task_id: "task-abc123",
|
|
13112
|
-
cursor: "5", // Resume from message 5
|
|
13113
|
-
block: true
|
|
13114
|
-
})
|
|
13115
|
-
\`\`\`
|
|
13052
|
+
When each task completes, its result is returned directly. Collect the outputs from each task and proceed to synthesis.
|
|
13116
13053
|
|
|
13117
13054
|
### 5. Synthesize Findings
|
|
13118
13055
|
|
|
@@ -13123,16 +13060,7 @@ Combine results from all tasks:
|
|
|
13123
13060
|
|
|
13124
13061
|
### 6. Cleanup (If Needed)
|
|
13125
13062
|
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
\`\`\`typescript
|
|
13129
|
-
// Path A: Hive background tools
|
|
13130
|
-
// Cancel specific task
|
|
13131
|
-
hive_background_cancel({ task_id: "task-abc123" })
|
|
13132
|
-
|
|
13133
|
-
// Cancel all your background tasks
|
|
13134
|
-
hive_background_cancel({ all: true })
|
|
13135
|
-
\`\`\`
|
|
13063
|
+
No manual cancellation is required in task mode.
|
|
13136
13064
|
|
|
13137
13065
|
## Prompt Templates
|
|
13138
13066
|
|
|
@@ -13180,48 +13108,20 @@ Return:
|
|
|
13180
13108
|
|
|
13181
13109
|
## Real Example
|
|
13182
13110
|
|
|
13183
|
-
**Investigation:** "How does the
|
|
13111
|
+
**Investigation:** "How does the API routing system work?"
|
|
13184
13112
|
|
|
13185
13113
|
**Decomposition:**
|
|
13186
|
-
1. Implementation: Where
|
|
13187
|
-
2.
|
|
13188
|
-
3. Notifications: How
|
|
13114
|
+
1. Implementation: Where are API routes defined?
|
|
13115
|
+
2. Routing: How does route registration work?
|
|
13116
|
+
3. Notifications: How are errors surfaced to the caller?
|
|
13189
13117
|
|
|
13190
13118
|
**Fan-out:**
|
|
13191
13119
|
\`\`\`typescript
|
|
13192
|
-
// Path A: Hive background tools
|
|
13193
|
-
// Task 1: Implementation
|
|
13194
|
-
hive_background_task({
|
|
13195
|
-
agent: "scout-researcher",
|
|
13196
|
-
description: "Find hive_background_task implementation",
|
|
13197
|
-
prompt: "Where is hive_background_task implemented? Find tool definition and registration.",
|
|
13198
|
-
sync: false
|
|
13199
|
-
})
|
|
13200
|
-
|
|
13201
|
-
// Task 2: Concurrency
|
|
13202
|
-
hive_background_task({
|
|
13203
|
-
agent: "scout-researcher",
|
|
13204
|
-
description: "Analyze concurrency model",
|
|
13205
|
-
prompt: "How does background task concurrency work? Find the manager/scheduler.",
|
|
13206
|
-
sync: false
|
|
13207
|
-
})
|
|
13208
|
-
|
|
13209
|
-
// Task 3: Notifications
|
|
13210
|
-
hive_background_task({
|
|
13211
|
-
agent: "scout-researcher",
|
|
13212
|
-
description: "Find notification mechanism",
|
|
13213
|
-
prompt: "How are parent sessions notified of task completion?",
|
|
13214
|
-
sync: false
|
|
13215
|
-
})
|
|
13216
|
-
\`\`\`
|
|
13217
|
-
|
|
13218
|
-
\`\`\`typescript
|
|
13219
|
-
// Path B: Task mode (native task tool)
|
|
13220
13120
|
// Parallelize by issuing multiple task() calls in the same assistant message.
|
|
13221
13121
|
task({
|
|
13222
13122
|
subagent_type: 'scout-researcher',
|
|
13223
|
-
description: 'Find
|
|
13224
|
-
prompt: 'Where
|
|
13123
|
+
description: 'Find API route implementation',
|
|
13124
|
+
prompt: 'Where are API routes implemented? Find tool definition and registration.',
|
|
13225
13125
|
});
|
|
13226
13126
|
|
|
13227
13127
|
task({
|
|
@@ -13249,20 +13149,12 @@ task({
|
|
|
13249
13149
|
**Spawning sequentially (defeats the purpose):**
|
|
13250
13150
|
\`\`\`typescript
|
|
13251
13151
|
// BAD: Wait for each before spawning next
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
\`\`\`
|
|
13255
|
-
|
|
13256
|
-
\`\`\`typescript
|
|
13257
|
-
// GOOD: Spawn all, then collect
|
|
13258
|
-
hive_background_task({ ..., sync: false }) // Returns immediately
|
|
13259
|
-
hive_background_task({ ..., sync: false }) // Returns immediately
|
|
13260
|
-
hive_background_task({ ..., sync: false }) // Returns immediately
|
|
13261
|
-
// ... later, collect results with hive_background_output
|
|
13152
|
+
await task({ ... });
|
|
13153
|
+
await task({ ... });
|
|
13262
13154
|
\`\`\`
|
|
13263
13155
|
|
|
13264
13156
|
\`\`\`typescript
|
|
13265
|
-
// GOOD
|
|
13157
|
+
// GOOD: Spawn all in the same assistant message
|
|
13266
13158
|
task({ ... });
|
|
13267
13159
|
task({ ... });
|
|
13268
13160
|
task({ ... });
|
|
@@ -13291,9 +13183,7 @@ task({ ... });
|
|
|
13291
13183
|
|
|
13292
13184
|
After using this pattern, verify:
|
|
13293
13185
|
- [ ] All tasks spawned before collecting any results (true fan-out)
|
|
13294
|
-
- [ ]
|
|
13295
|
-
- [ ] Successfully retrieved output with \`hive_background_output\` (Path A)
|
|
13296
|
-
- [ ] Verified \`task()\` fan-out pattern used when in task mode (Path B)
|
|
13186
|
+
- [ ] Verified \`task()\` fan-out pattern used for parallel exploration
|
|
13297
13187
|
- [ ] Synthesized findings into coherent answer`
|
|
13298
13188
|
},
|
|
13299
13189
|
{
|
|
@@ -14374,7 +14264,7 @@ Hybrid agent: plans AND orchestrates. Phase-aware, skills on-demand.
|
|
|
14374
14264
|
|
|
14375
14265
|
## Phase Detection (First Action)
|
|
14376
14266
|
|
|
14377
|
-
Run \`hive_status()\`
|
|
14267
|
+
Run \`hive_status()\` to detect phase:
|
|
14378
14268
|
|
|
14379
14269
|
| Feature State | Phase | Active Section |
|
|
14380
14270
|
|---------------|-------|----------------|
|
|
@@ -14400,18 +14290,16 @@ Run \`hive_status()\` or \`hive_feature_list()\` to detect phase:
|
|
|
14400
14290
|
### Canonical Delegation Threshold
|
|
14401
14291
|
|
|
14402
14292
|
- Delegate to Scout when you cannot name the file path upfront, expect to inspect 2+ files, or the question is open-ended ("how/where does X work?").
|
|
14403
|
-
- Prefer \`
|
|
14293
|
+
- Prefer \`task({ subagent_type: "scout-researcher", prompt: "..." })\` for single investigations.
|
|
14404
14294
|
- Local \`read/grep/glob\` is acceptable only for a single known file and a bounded question.
|
|
14405
14295
|
|
|
14406
14296
|
### Delegation
|
|
14407
14297
|
|
|
14408
|
-
- Single-scout research → \`
|
|
14409
|
-
- Parallel exploration → Load \`hive_skill("parallel-exploration")\` and follow the task
|
|
14410
|
-
- Implementation → \`
|
|
14298
|
+
- Single-scout research → \`task({ subagent_type: "scout-researcher", prompt: "..." })\`
|
|
14299
|
+
- Parallel exploration → Load \`hive_skill("parallel-exploration")\` and follow the task mode delegation guidance.
|
|
14300
|
+
- Implementation → \`hive_worktree_create({ task: "01-task-name" })\` (creates worktree + Forager)
|
|
14411
14301
|
|
|
14412
|
-
|
|
14413
|
-
|
|
14414
|
-
During Planning, default to synchronous exploration (\`sync: true\`). If async/parallel exploration would help, ask the user via \`question()\`.
|
|
14302
|
+
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.
|
|
14415
14303
|
|
|
14416
14304
|
### Context Persistence
|
|
14417
14305
|
|
|
@@ -14420,6 +14308,8 @@ Save discoveries with \`hive_context_write\`:
|
|
|
14420
14308
|
- User preferences
|
|
14421
14309
|
- Research findings
|
|
14422
14310
|
|
|
14311
|
+
When Scout returns substantial findings (3+ files discovered, architecture patterns, or key decisions), persist them to a feature context file via \`hive_context_write\`.
|
|
14312
|
+
|
|
14423
14313
|
### Checkpoints
|
|
14424
14314
|
|
|
14425
14315
|
Before major transitions, verify:
|
|
@@ -14433,7 +14323,7 @@ Load when detailed guidance needed:
|
|
|
14433
14323
|
- \`hive_skill("brainstorming")\` - exploring ideas and requirements
|
|
14434
14324
|
- \`hive_skill("writing-plans")\` - structuring implementation plans
|
|
14435
14325
|
- \`hive_skill("dispatching-parallel-agents")\` - parallel task delegation
|
|
14436
|
-
- \`hive_skill("parallel-exploration")\` - parallel read-only research via task()
|
|
14326
|
+
- \`hive_skill("parallel-exploration")\` - parallel read-only research via task() (Scout fan-out)
|
|
14437
14327
|
- \`hive_skill("executing-plans")\` - step-by-step plan execution
|
|
14438
14328
|
|
|
14439
14329
|
Load ONE skill at a time. Only when you need guidance beyond this prompt.
|
|
@@ -14457,6 +14347,14 @@ Load ONE skill at a time. Only when you need guidance beyond this prompt.
|
|
|
14457
14347
|
| Premature abstraction | "Abstract or inline?" |
|
|
14458
14348
|
| Over-validation | "Minimal or comprehensive checks?" |
|
|
14459
14349
|
|
|
14350
|
+
### Challenge User Assumptions
|
|
14351
|
+
|
|
14352
|
+
When a proposal relies on fragile assumptions, challenge them explicitly:
|
|
14353
|
+
|
|
14354
|
+
- Identify the assumption and state it plainly.
|
|
14355
|
+
- Ask what changes if the assumption is wrong.
|
|
14356
|
+
- Offer a lean fallback that still meets core goals.
|
|
14357
|
+
|
|
14460
14358
|
### Gap Classification
|
|
14461
14359
|
|
|
14462
14360
|
| Gap | Action |
|
|
@@ -14493,7 +14391,7 @@ After review decision, offer execution choice (subagent-driven vs parallel sessi
|
|
|
14493
14391
|
|
|
14494
14392
|
- Research BEFORE asking (use \`hive_skill("parallel-exploration")\` for multi-domain research)
|
|
14495
14393
|
- Save draft as working memory
|
|
14496
|
-
- Don't implement (no edits/worktrees). Read-only exploration is allowed (local tools + Scout via
|
|
14394
|
+
- Don't implement (no edits/worktrees). Read-only exploration is allowed (local tools + Scout via task()).
|
|
14497
14395
|
|
|
14498
14396
|
---
|
|
14499
14397
|
|
|
@@ -14522,24 +14420,15 @@ Use \`hive_status()\` to see **runnable** tasks (dependencies satisfied) and **b
|
|
|
14522
14420
|
### Worker Spawning
|
|
14523
14421
|
|
|
14524
14422
|
\`\`\`
|
|
14525
|
-
|
|
14423
|
+
hive_worktree_create({ task: "01-task-name" }) // Creates worktree + Forager
|
|
14526
14424
|
\`\`\`
|
|
14527
14425
|
|
|
14528
14426
|
### After Delegation
|
|
14529
14427
|
|
|
14530
|
-
1.
|
|
14531
|
-
2.
|
|
14532
|
-
3.
|
|
14533
|
-
4.
|
|
14534
|
-
5. If blocked: \`question()\` → user decision → \`continueFrom: "blocked"\`
|
|
14535
|
-
|
|
14536
|
-
### Observation Polling (Recommended)
|
|
14537
|
-
|
|
14538
|
-
- Prefer completion notifications over polling
|
|
14539
|
-
- Use \`hive_worker_status()\` for observation-based spot checks
|
|
14540
|
-
- Avoid tight loops with \`hive_background_output\`; if needed, wait 30-60s between checks
|
|
14541
|
-
- If you suspect notifications did not deliver, do a single \`hive_worker_status()\` check first
|
|
14542
|
-
- If you need to fetch final results, call \`hive_background_output({ task_id, block: false })\` after the completion notice
|
|
14428
|
+
1. \`task()\` is BLOCKING — when it returns, the worker is DONE
|
|
14429
|
+
2. Immediately call \`hive_status()\` to check the new task state and find next runnable tasks
|
|
14430
|
+
3. If task status is blocked: read blocker info → \`question()\` → user decision → resume with \`continueFrom: "blocked"\`
|
|
14431
|
+
4. Do NOT wait for notifications or poll — the result is already available when \`task()\` returns
|
|
14543
14432
|
|
|
14544
14433
|
### Failure Recovery
|
|
14545
14434
|
|
|
@@ -14549,6 +14438,13 @@ hive_exec_start({ task: "01-task-name" }) // Creates worktree + Forager
|
|
|
14549
14438
|
|
|
14550
14439
|
\`hive_merge({ task: "01-task-name" })\` after verification
|
|
14551
14440
|
|
|
14441
|
+
### Post-Batch Review (Hygienic)
|
|
14442
|
+
|
|
14443
|
+
After completing and merging a batch:
|
|
14444
|
+
1. Ask the user via \`question()\` if they want a Hygienic code review for the batch.
|
|
14445
|
+
2. If yes, run \`task({ subagent_type: "hygienic", prompt: "Review implementation changes from the latest batch." })\`.
|
|
14446
|
+
3. Apply feedback before starting the next batch.
|
|
14447
|
+
|
|
14552
14448
|
### Orchestration Iron Laws
|
|
14553
14449
|
|
|
14554
14450
|
- Delegate by default
|
|
@@ -14587,9 +14483,9 @@ PLANNER, NOT IMPLEMENTER. "Do X" means "create plan for X".
|
|
|
14587
14483
|
| Simple | 1-2 files, <30 min | Light interview → quick plan |
|
|
14588
14484
|
| Complex | 3+ files, review needed | Full discovery → detailed plan |
|
|
14589
14485
|
| Refactor | Existing code changes | Safety: tests, rollback, blast radius |
|
|
14590
|
-
| Greenfield | New feature | Research patterns BEFORE asking. Delegate to Scout via \`
|
|
14486
|
+
| Greenfield | New feature | Research patterns BEFORE asking. Delegate to Scout via \`task({ subagent_type: "scout-researcher", prompt: "..." })\` for single investigations. |
|
|
14591
14487
|
|
|
14592
|
-
During Planning,
|
|
14488
|
+
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.
|
|
14593
14489
|
|
|
14594
14490
|
## Self-Clearance Check (After Every Exchange)
|
|
14595
14491
|
|
|
@@ -14609,6 +14505,7 @@ ANY NO → Ask the unclear thing
|
|
|
14609
14505
|
| Premature abstraction | "Extracted to utility" | "Abstract or inline?" |
|
|
14610
14506
|
| Over-validation | "15 error checks for 3 inputs" | "Minimal or comprehensive error handling?" |
|
|
14611
14507
|
| Documentation bloat | "Added JSDoc everywhere" | "None, minimal, or full docs?" |
|
|
14508
|
+
| Fragile assumption | "Assuming X is always true" | "If X is wrong, what should change?" |
|
|
14612
14509
|
|
|
14613
14510
|
## Gap Classification (Self-Review)
|
|
14614
14511
|
|
|
@@ -14666,9 +14563,9 @@ Each task MUST declare dependencies with **Depends on**:
|
|
|
14666
14563
|
### Canonical Delegation Threshold
|
|
14667
14564
|
|
|
14668
14565
|
- Delegate to Scout when you cannot name the file path upfront, expect to inspect 2+ files, or the question is open-ended ("how/where does X work?").
|
|
14669
|
-
- Prefer \`
|
|
14566
|
+
- Prefer \`task({ subagent_type: "scout-researcher", prompt: "..." })\` for single investigations.
|
|
14670
14567
|
- Local \`read/grep/glob\` is acceptable only for a single known file and a bounded question.
|
|
14671
|
-
- When
|
|
14568
|
+
- When running parallel exploration, align with the skill guidance.
|
|
14672
14569
|
`;
|
|
14673
14570
|
|
|
14674
14571
|
// src/agents/swarm.ts
|
|
@@ -14695,6 +14592,8 @@ Use \`hive_status()\` to see **runnable** tasks (dependencies satisfied) and **b
|
|
|
14695
14592
|
- When 2+ tasks are runnable: ask operator via \`question()\` before parallelizing
|
|
14696
14593
|
- Record execution decisions with \`hive_context_write({ name: "execution-decisions", ... })\`
|
|
14697
14594
|
|
|
14595
|
+
When Scout returns substantial findings (3+ files discovered, architecture patterns, or key decisions), persist them to a feature context file via \`hive_context_write\`.
|
|
14596
|
+
|
|
14698
14597
|
If tasks are missing **Depends on** metadata, ask the planner to revise the plan before executing.
|
|
14699
14598
|
|
|
14700
14599
|
### Standard Checks
|
|
@@ -14703,7 +14602,7 @@ If tasks are missing **Depends on** metadata, ask the planner to revise the plan
|
|
|
14703
14602
|
2. Can I do it myself FOR SURE? REALLY?
|
|
14704
14603
|
3. Does this require external system data (DBs/APIs/3rd-party tools)?
|
|
14705
14604
|
→ If external data needed: Load \`hive_skill("parallel-exploration")\` for parallel Scout fan-out
|
|
14706
|
-
In task mode, use task() for research fan-out
|
|
14605
|
+
In task mode, use task() for research fan-out.
|
|
14707
14606
|
During Planning, default to synchronous exploration. If async exploration would help, ask the user via \`question()\` and follow the onboarding preferences.
|
|
14708
14607
|
→ Default: DELEGATE
|
|
14709
14608
|
|
|
@@ -14721,17 +14620,16 @@ During Planning, default to synchronous exploration. If async exploration would
|
|
|
14721
14620
|
## Worker Spawning
|
|
14722
14621
|
|
|
14723
14622
|
\`\`\`
|
|
14724
|
-
|
|
14725
|
-
// If delegationRequired returned:
|
|
14726
|
-
hive_background_task({ agent: "forager-worker", prompt: "...", sync: false })
|
|
14623
|
+
hive_worktree_create({ task: "01-task-name" })
|
|
14727
14624
|
// If external system data is needed (parallel exploration):
|
|
14728
14625
|
// Load hive_skill("parallel-exploration") for the full playbook, then:
|
|
14729
|
-
// In task mode, use task() for research fan-out
|
|
14626
|
+
// In task mode, use task() for research fan-out.
|
|
14730
14627
|
\`\`\`
|
|
14731
14628
|
|
|
14732
|
-
**
|
|
14733
|
-
- \`
|
|
14734
|
-
-
|
|
14629
|
+
**Delegation Guidance:**
|
|
14630
|
+
- \`task()\` is BLOCKING — returns when the worker is done
|
|
14631
|
+
- Call \`hive_status()\` immediately after to check new state and find next runnable tasks
|
|
14632
|
+
- For parallel fan-out, issue multiple \`task()\` calls in the same message
|
|
14735
14633
|
|
|
14736
14634
|
## After Delegation - ALWAYS VERIFY
|
|
14737
14635
|
|
|
@@ -14742,9 +14640,9 @@ hive_background_task({ agent: "forager-worker", prompt: "...", sync: false })
|
|
|
14742
14640
|
## Blocker Handling
|
|
14743
14641
|
|
|
14744
14642
|
When worker reports blocked:
|
|
14745
|
-
1. \`
|
|
14643
|
+
1. \`hive_status()\` — read blocker info
|
|
14746
14644
|
2. \`question()\` — ask user (NEVER plain text)
|
|
14747
|
-
3. \`
|
|
14645
|
+
3. \`hive_worktree_create({ task, continueFrom: "blocked", decision })\`
|
|
14748
14646
|
|
|
14749
14647
|
## Failure Recovery (After 3 Consecutive Failures)
|
|
14750
14648
|
|
|
@@ -14762,6 +14660,13 @@ hive_merge({ task: "01-task-name", strategy: "merge" })
|
|
|
14762
14660
|
|
|
14763
14661
|
Merge only after verification passes.
|
|
14764
14662
|
|
|
14663
|
+
## Post-Batch Review (Hygienic)
|
|
14664
|
+
|
|
14665
|
+
After completing and merging a batch:
|
|
14666
|
+
1. Ask the user via \`question()\` if they want a Hygienic code review for the batch.
|
|
14667
|
+
2. If yes, run \`task({ subagent_type: "hygienic", prompt: "Review implementation changes from the latest batch." })\`.
|
|
14668
|
+
3. Apply feedback before starting the next batch.
|
|
14669
|
+
|
|
14765
14670
|
## Iron Laws
|
|
14766
14671
|
|
|
14767
14672
|
**Never:**
|
|
@@ -14871,6 +14776,89 @@ When asked to retrieve raw data from external systems (MongoDB/Stripe/etc.):
|
|
|
14871
14776
|
- GitHub: Permalinks with commit SHA
|
|
14872
14777
|
- Docs: URL with section anchor
|
|
14873
14778
|
|
|
14779
|
+
## Persistence
|
|
14780
|
+
|
|
14781
|
+
When operating within a feature context (background task with feature parameter):
|
|
14782
|
+
- If findings are substantial (3+ files discovered, architecture patterns, or key decisions):
|
|
14783
|
+
Use \`hive_context_write\` to persist findings:
|
|
14784
|
+
\`\`\`
|
|
14785
|
+
hive_context_write({
|
|
14786
|
+
name: "research-{topic-slug}",
|
|
14787
|
+
content: "## Research: {Topic}
|
|
14788
|
+
|
|
14789
|
+
Date: {date}
|
|
14790
|
+
|
|
14791
|
+
## Context
|
|
14792
|
+
|
|
14793
|
+
## research-findings
|
|
14794
|
+
|
|
14795
|
+
# Research Findings for Hive Improvements v2
|
|
14796
|
+
|
|
14797
|
+
## Worker Prompt Builder (\`worker-prompt.ts:48\`)
|
|
14798
|
+
- \`buildWorkerPrompt(params: WorkerPromptParams): string\`
|
|
14799
|
+
- Receives: feature, task, taskOrder, worktreePath, branch, plan, contextFiles, spec, previousTasks, continueFrom
|
|
14800
|
+
- Only uses: feature, task, taskOrder, worktreePath, branch, spec, continueFrom
|
|
14801
|
+
- plan/contextFiles/previousTasks passed but NOT used (already embedded in spec)
|
|
14802
|
+
- 10 sections: Assignment, Continuation(optional), Mission(=spec), Blocker Protocol, Completion Protocol, TDD, Debugging, Tools, Guidelines, User Input
|
|
14803
|
+
- **ZERO task-type awareness** — all workers get identical protocols
|
|
14804
|
+
- Budget: 100KB soft limit (advisory, not enforced)
|
|
14805
|
+
|
|
14806
|
+
## Task Completion Flow (\`index.ts:974-1088\`)
|
|
14807
|
+
- \`hive_exec_complete\` accepts: task, summary (string), status (completed|blocked|failed|partial), blocker (optional)
|
|
14808
|
+
- Summary stored in: status.json, report.md, commit message (first 50 chars)
|
|
14809
|
+
- **Summary is free-form string** — no structure enforced
|
|
14810
|
+
- Completed summaries collected for next task: \`allTasks.filter(t => t.status === 'done' && t.summary)\`
|
|
14811
|
+
- Injected into spec as \`## Completed Tasks\` → \`- taskName: summary\`
|
|
14812
|
+
|
|
14813
|
+
## TaskService (\`taskService.ts\`)
|
|
14814
|
+
- \`buildSpecContent()\` (lines 168-225): builds spec with Dependencies, Plan Section, Context, Completed Tasks
|
|
14815
|
+
- \`parseTasksFromPlan()\` (lines 532-602): regex \`/^###\\s+(\\d+)\\.\\s+(.+)$/\` for task headers
|
|
14816
|
+
- \`resolveDependencies()\` (lines 248-268): explicit deps or implicit sequential (N depends on N-1)
|
|
14817
|
+
- Types: TaskStatus has \`summary?: string\`, TaskInfo has \`summary?: string\`
|
|
14818
|
+
|
|
14819
|
+
## Forager Agent (\`forager.ts:8-117\`)
|
|
14820
|
+
- Execution flow: Understand → Implement → Verify → Report
|
|
14821
|
+
- **NO orient/pre-flight phase** — jumps straight to understanding task spec
|
|
14822
|
+
- Can read codebase, use research tools (grep_app, context7, ast_grep)
|
|
14823
|
+
- Cannot: delegate (task/hive_exec_start), modify plan, use hive_merge
|
|
14824
|
+
- Notepads: \`.hive/features/{feature}/notepads/{learnings,issues,decisions}.md\` (append-only)
|
|
14825
|
+
|
|
14826
|
+
## Hygienic Agent (\`hygienic.ts:8-105\`)
|
|
14827
|
+
- Reviews plan DOCUMENTATION quality, not design
|
|
14828
|
+
- 4 criteria: Clarity, Verifiability, Completeness, Big Picture
|
|
14829
|
+
- Verdict: OKAY or REJECT with 4-category assessment
|
|
14830
|
+
- When asked to review implementation → loads \`hive_skill("code-reviewer")\`
|
|
14831
|
+
- **Currently only invoked for plan review** (from Hive and Architect agents)
|
|
14832
|
+
- Cannot delegate/spawn workers
|
|
14833
|
+
|
|
14834
|
+
## Scout Agent (\`scout.ts:8-112\`)
|
|
14835
|
+
- Read-only research agent
|
|
14836
|
+
- Classifies requests: CONCEPTUAL, IMPLEMENTATION, CODEBASE, COMPREHENSIVE
|
|
14837
|
+
- Output format: \`<results><files>...<answer>...<next_steps>...</results>\`
|
|
14838
|
+
- **Does NOT persist findings** — returns to orchestrator only
|
|
14839
|
+
- Parallel execution by default (3+ tools simultaneously)
|
|
14840
|
+
|
|
14841
|
+
## Code-Reviewer Skill (\`skills/code-reviewer/SKILL.md\`)
|
|
14842
|
+
- Loaded by Hygienic when reviewing implementation
|
|
14843
|
+
- Output: APPROVE | REQUEST_CHANGES | NEEDS_DISCUSSION
|
|
14844
|
+
- Reviews: plan adherence, correctness, simplicity/YAGNI, risk
|
|
14845
|
+
- Already exists but underused (Hygienic only loads it when explicitly asked)
|
|
14846
|
+
|
|
14847
|
+
## Plan Format
|
|
14848
|
+
- Headers: \`### N. Task Name\`
|
|
14849
|
+
- Sections: Depends on, What to do, Must NOT do, References (file:lines), Acceptance Criteria
|
|
14850
|
+
- Dependencies: \`none\` | \`1\` | \`1,3\` | implicit sequential
|
|
14851
|
+
|
|
14852
|
+
## Skills (10 total)
|
|
14853
|
+
writing-plans, executing-plans, dispatching-parallel-agents, parallel-exploration, code-reviewer, onboarding, brainstorming, verification-before-completion, test-driven-development, systematic-debugging
|
|
14854
|
+
|
|
14855
|
+
## Notepad System
|
|
14856
|
+
- Location: \`.hive/features/{feature}/notepads/{learnings,issues,decisions}.md\`
|
|
14857
|
+
- Workers append-only
|
|
14858
|
+
- **NOT automatically injected into next batch** — context injection only reads from \`contexts/\` directory"
|
|
14859
|
+
})
|
|
14860
|
+
\`\`\`
|
|
14861
|
+
|
|
14874
14862
|
## Iron Laws
|
|
14875
14863
|
|
|
14876
14864
|
**Never:**
|
|
@@ -14896,7 +14884,7 @@ Execute directly. NEVER delegate implementation. Work in isolation.
|
|
|
14896
14884
|
|
|
14897
14885
|
These tools are FORBIDDEN:
|
|
14898
14886
|
- \`task\` — Orchestrator's job
|
|
14899
|
-
- \`
|
|
14887
|
+
- \`hive_worktree_create\` — You ARE the spawned worker
|
|
14900
14888
|
- \`hive_merge\` — Orchestrator's job
|
|
14901
14889
|
|
|
14902
14890
|
## Allowed Research
|
|
@@ -14914,14 +14902,10 @@ CRITICAL: NEVER MODIFY THE PLAN FILE
|
|
|
14914
14902
|
- MUST NOT edit, modify, or update plan
|
|
14915
14903
|
- Only Orchestrator (Swarm) manages plan
|
|
14916
14904
|
|
|
14917
|
-
##
|
|
14905
|
+
## Persistent Notes
|
|
14918
14906
|
|
|
14919
|
-
|
|
14920
|
-
|
|
14921
|
-
- issues.md: Problems, blockers, gotchas
|
|
14922
|
-
- decisions.md: Architectural choices and rationales
|
|
14923
|
-
|
|
14924
|
-
IMPORTANT: Always APPEND — never overwrite.
|
|
14907
|
+
For substantial discoveries (architecture patterns, key decisions, gotchas that affect multiple tasks):
|
|
14908
|
+
Use \`hive_context_write({ name: "learnings", content: "..." })\` to persist for future workers.
|
|
14925
14909
|
|
|
14926
14910
|
## Execution Flow
|
|
14927
14911
|
|
|
@@ -14932,7 +14916,14 @@ Read spec for:
|
|
|
14932
14916
|
- **Must NOT do** (guardrails)
|
|
14933
14917
|
- **Acceptance criteria**
|
|
14934
14918
|
|
|
14935
|
-
### 2.
|
|
14919
|
+
### 2. Orient (Pre-flight Before Coding)
|
|
14920
|
+
Before writing code:
|
|
14921
|
+
- Confirm dependencies are satisfied and required context is present
|
|
14922
|
+
- Identify the exact files/sections to touch (from references)
|
|
14923
|
+
- Decide the first failing test you will write (TDD)
|
|
14924
|
+
- Plan the minimum change to reach green
|
|
14925
|
+
|
|
14926
|
+
### 3. Implement
|
|
14936
14927
|
Follow spec exactly. Use references for patterns.
|
|
14937
14928
|
|
|
14938
14929
|
\`\`\`
|
|
@@ -14941,28 +14932,28 @@ edit(file, { old: "...", new: "..." }) // Implement
|
|
|
14941
14932
|
bash("npm test") // Verify
|
|
14942
14933
|
\`\`\`
|
|
14943
14934
|
|
|
14944
|
-
###
|
|
14935
|
+
### 4. Verify
|
|
14945
14936
|
Run acceptance criteria:
|
|
14946
14937
|
- Tests pass
|
|
14947
14938
|
- Build succeeds
|
|
14948
14939
|
- lsp_diagnostics clean on changed files
|
|
14949
14940
|
|
|
14950
|
-
###
|
|
14941
|
+
### 5. Report
|
|
14951
14942
|
|
|
14952
14943
|
**Success:**
|
|
14953
14944
|
\`\`\`
|
|
14954
|
-
|
|
14945
|
+
hive_worktree_commit({
|
|
14955
14946
|
task: "current-task",
|
|
14956
14947
|
summary: "Implemented X. Tests pass.",
|
|
14957
14948
|
status: "completed"
|
|
14958
14949
|
})
|
|
14959
14950
|
\`\`\`
|
|
14960
14951
|
|
|
14961
|
-
**CRITICAL: After
|
|
14952
|
+
**CRITICAL: After hive_worktree_commit, STOP IMMEDIATELY.**
|
|
14962
14953
|
|
|
14963
14954
|
**Blocked (need user decision):**
|
|
14964
14955
|
\`\`\`
|
|
14965
|
-
|
|
14956
|
+
hive_worktree_commit({
|
|
14966
14957
|
task: "current-task",
|
|
14967
14958
|
summary: "Progress on X. Blocked on Y.",
|
|
14968
14959
|
status: "blocked",
|
|
@@ -14987,8 +14978,8 @@ After 3 consecutive failures:
|
|
|
14987
14978
|
**Never:**
|
|
14988
14979
|
- Exceed task scope
|
|
14989
14980
|
- Modify plan file
|
|
14990
|
-
- Use \`task\` or \`
|
|
14991
|
-
- Continue after
|
|
14981
|
+
- Use \`task\` or \`hive_worktree_create\`
|
|
14982
|
+
- Continue after hive_worktree_commit
|
|
14992
14983
|
- Skip verification
|
|
14993
14984
|
|
|
14994
14985
|
**Always:**
|
|
@@ -15995,7 +15986,6 @@ var DEFAULT_HIVE_CONFIG = {
|
|
|
15995
15986
|
disableSkills: [],
|
|
15996
15987
|
disableMcps: [],
|
|
15997
15988
|
agentMode: "unified",
|
|
15998
|
-
delegateMode: "task",
|
|
15999
15989
|
agents: {
|
|
16000
15990
|
"hive-master": {
|
|
16001
15991
|
model: DEFAULT_AGENT_MODELS["hive-master"],
|
|
@@ -16049,16 +16039,12 @@ var FEATURE_FILE = "feature.json";
|
|
|
16049
16039
|
var STATUS_FILE = "status.json";
|
|
16050
16040
|
var REPORT_FILE = "report.md";
|
|
16051
16041
|
var APPROVED_FILE = "APPROVED";
|
|
16052
|
-
var JOURNAL_FILE = "journal.md";
|
|
16053
16042
|
function normalizePath(filePath) {
|
|
16054
16043
|
return filePath.replace(/\\/g, "/");
|
|
16055
16044
|
}
|
|
16056
16045
|
function getHivePath(projectRoot) {
|
|
16057
16046
|
return path2.join(projectRoot, HIVE_DIR);
|
|
16058
16047
|
}
|
|
16059
|
-
function getJournalPath(projectRoot) {
|
|
16060
|
-
return path2.join(getHivePath(projectRoot), JOURNAL_FILE);
|
|
16061
|
-
}
|
|
16062
16048
|
function getFeaturesPath(projectRoot) {
|
|
16063
16049
|
return path2.join(getHivePath(projectRoot), FEATURES_DIR);
|
|
16064
16050
|
}
|
|
@@ -16295,22 +16281,6 @@ function listFeatures(projectRoot) {
|
|
|
16295
16281
|
return [];
|
|
16296
16282
|
return fs22.readdirSync(featuresPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
16297
16283
|
}
|
|
16298
|
-
var JOURNAL_TEMPLATE = `# Hive Journal
|
|
16299
|
-
|
|
16300
|
-
Audit trail of project learnings. Updated when trouble is resolved.
|
|
16301
|
-
|
|
16302
|
-
---
|
|
16303
|
-
|
|
16304
|
-
<!-- Entry template:
|
|
16305
|
-
### YYYY-MM-DD: feature-name
|
|
16306
|
-
|
|
16307
|
-
**Trouble**: What went wrong
|
|
16308
|
-
**Resolution**: How it was fixed
|
|
16309
|
-
**Constraint**: Never/Always rule derived (add to Iron Laws if recurring)
|
|
16310
|
-
**See**: .hive/features/feature-name/plan.md
|
|
16311
|
-
-->
|
|
16312
|
-
`;
|
|
16313
|
-
|
|
16314
16284
|
class FeatureService {
|
|
16315
16285
|
projectRoot;
|
|
16316
16286
|
constructor(projectRoot) {
|
|
@@ -16324,10 +16294,6 @@ class FeatureService {
|
|
|
16324
16294
|
ensureDir(featurePath);
|
|
16325
16295
|
ensureDir(getContextPath(this.projectRoot, name));
|
|
16326
16296
|
ensureDir(getTasksPath(this.projectRoot, name));
|
|
16327
|
-
const journalPath = getJournalPath(this.projectRoot);
|
|
16328
|
-
if (!fileExists(journalPath)) {
|
|
16329
|
-
fs3.writeFileSync(journalPath, JOURNAL_TEMPLATE);
|
|
16330
|
-
}
|
|
16331
16297
|
const feature = {
|
|
16332
16298
|
name,
|
|
16333
16299
|
status: "planning",
|
|
@@ -16596,6 +16562,27 @@ class TaskService {
|
|
|
16596
16562
|
}
|
|
16597
16563
|
buildSpecContent(params) {
|
|
16598
16564
|
const { featureName, task, dependsOn, allTasks, planContent, contextFiles = [], completedTasks = [] } = params;
|
|
16565
|
+
const getTaskType = (planSection2, taskName) => {
|
|
16566
|
+
if (!planSection2) {
|
|
16567
|
+
return null;
|
|
16568
|
+
}
|
|
16569
|
+
const fileTypeMatches = Array.from(planSection2.matchAll(/-\s*(Create|Modify|Test):/gi)).map((match) => match[1].toLowerCase());
|
|
16570
|
+
const fileTypes = new Set(fileTypeMatches);
|
|
16571
|
+
if (fileTypes.size === 0) {
|
|
16572
|
+
return taskName.toLowerCase().includes("test") ? "testing" : null;
|
|
16573
|
+
}
|
|
16574
|
+
if (fileTypes.size === 1) {
|
|
16575
|
+
const onlyType = Array.from(fileTypes)[0];
|
|
16576
|
+
if (onlyType === "create")
|
|
16577
|
+
return "greenfield";
|
|
16578
|
+
if (onlyType === "test")
|
|
16579
|
+
return "testing";
|
|
16580
|
+
}
|
|
16581
|
+
if (fileTypes.has("modify")) {
|
|
16582
|
+
return "modification";
|
|
16583
|
+
}
|
|
16584
|
+
return null;
|
|
16585
|
+
};
|
|
16599
16586
|
const specLines = [
|
|
16600
16587
|
`# Task: ${task.folder}`,
|
|
16601
16588
|
"",
|
|
@@ -16624,6 +16611,10 @@ class TaskService {
|
|
|
16624
16611
|
specLines.push("_No plan section available._");
|
|
16625
16612
|
}
|
|
16626
16613
|
specLines.push("");
|
|
16614
|
+
const taskType = getTaskType(planSection, task.name);
|
|
16615
|
+
if (taskType) {
|
|
16616
|
+
specLines.push("## Task Type", "", taskType, "");
|
|
16617
|
+
}
|
|
16627
16618
|
if (contextFiles.length > 0) {
|
|
16628
16619
|
const contextCompiled = contextFiles.map((f) => `## ${f.name}
|
|
16629
16620
|
|
|
@@ -21632,13 +21623,6 @@ class ConfigService {
|
|
|
21632
21623
|
const config2 = this.get();
|
|
21633
21624
|
return config2.disableMcps ?? [];
|
|
21634
21625
|
}
|
|
21635
|
-
getDelegateMode() {
|
|
21636
|
-
const config2 = this.get();
|
|
21637
|
-
return config2.delegateMode ?? "task";
|
|
21638
|
-
}
|
|
21639
|
-
isHiveBackgroundEnabled() {
|
|
21640
|
-
return this.getDelegateMode() === "hive";
|
|
21641
|
-
}
|
|
21642
21626
|
}
|
|
21643
21627
|
function computeRunnableAndBlocked(tasks) {
|
|
21644
21628
|
const statusByFolder = new Map;
|
|
@@ -21747,16 +21731,26 @@ ${spec}
|
|
|
21747
21731
|
|
|
21748
21732
|
---
|
|
21749
21733
|
|
|
21734
|
+
## Pre-implementation Checklist
|
|
21735
|
+
|
|
21736
|
+
Before writing code, confirm:
|
|
21737
|
+
1. Dependencies are satisfied and required context is present.
|
|
21738
|
+
2. The exact files/sections to touch (from references) are identified.
|
|
21739
|
+
3. The first failing test to write is clear (TDD).
|
|
21740
|
+
4. The minimal change needed to reach green is planned.
|
|
21741
|
+
|
|
21742
|
+
---
|
|
21743
|
+
|
|
21750
21744
|
## Blocker Protocol
|
|
21751
21745
|
|
|
21752
21746
|
If you hit a blocker requiring human decision, **DO NOT** use the question tool directly.
|
|
21753
21747
|
Instead, escalate via the blocker protocol:
|
|
21754
21748
|
|
|
21755
21749
|
1. **Save your progress** to the worktree (commit if appropriate)
|
|
21756
|
-
2. **Call
|
|
21750
|
+
2. **Call hive_worktree_commit** with blocker info:
|
|
21757
21751
|
|
|
21758
21752
|
\`\`\`
|
|
21759
|
-
|
|
21753
|
+
hive_worktree_commit({
|
|
21760
21754
|
task: "${task}",
|
|
21761
21755
|
feature: "${feature}",
|
|
21762
21756
|
status: "blocked",
|
|
@@ -21770,7 +21764,7 @@ hive_exec_complete({
|
|
|
21770
21764
|
})
|
|
21771
21765
|
\`\`\`
|
|
21772
21766
|
|
|
21773
|
-
**After calling
|
|
21767
|
+
**After calling hive_worktree_commit with blocked status, STOP IMMEDIATELY.**
|
|
21774
21768
|
|
|
21775
21769
|
The Hive Master will:
|
|
21776
21770
|
1. Receive your blocker info
|
|
@@ -21786,7 +21780,7 @@ This keeps the user focused on ONE conversation (Hive Master) instead of multipl
|
|
|
21786
21780
|
When your task is **fully complete**:
|
|
21787
21781
|
|
|
21788
21782
|
\`\`\`
|
|
21789
|
-
|
|
21783
|
+
hive_worktree_commit({
|
|
21790
21784
|
task: "${task}",
|
|
21791
21785
|
feature: "${feature}",
|
|
21792
21786
|
status: "completed",
|
|
@@ -21794,14 +21788,20 @@ hive_exec_complete({
|
|
|
21794
21788
|
})
|
|
21795
21789
|
\`\`\`
|
|
21796
21790
|
|
|
21797
|
-
**CRITICAL: After calling
|
|
21791
|
+
**CRITICAL: After calling hive_worktree_commit, you MUST STOP IMMEDIATELY.**
|
|
21798
21792
|
Do NOT continue working. Do NOT respond further. Your session is DONE.
|
|
21799
21793
|
The Hive Master will take over from here.
|
|
21800
21794
|
|
|
21795
|
+
**Summary Guidance** (used verbatim for downstream task context):
|
|
21796
|
+
1. Start with **what changed** (files/areas touched).
|
|
21797
|
+
2. Mention **why** if it affects future tasks.
|
|
21798
|
+
3. Note **verification evidence** (tests/build/lint) or explicitly say "Not run".
|
|
21799
|
+
4. Keep it **2-4 sentences** max.
|
|
21800
|
+
|
|
21801
21801
|
If you encounter an **unrecoverable error**:
|
|
21802
21802
|
|
|
21803
21803
|
\`\`\`
|
|
21804
|
-
|
|
21804
|
+
hive_worktree_commit({
|
|
21805
21805
|
task: "${task}",
|
|
21806
21806
|
feature: "${feature}",
|
|
21807
21807
|
status: "failed",
|
|
@@ -21812,7 +21812,7 @@ hive_exec_complete({
|
|
|
21812
21812
|
If you made **partial progress** but can't continue:
|
|
21813
21813
|
|
|
21814
21814
|
\`\`\`
|
|
21815
|
-
|
|
21815
|
+
hive_worktree_commit({
|
|
21816
21816
|
task: "${task}",
|
|
21817
21817
|
feature: "${feature}",
|
|
21818
21818
|
status: "partial",
|
|
@@ -21846,16 +21846,16 @@ After 3 failed attempts at same fix: STOP and report blocker.
|
|
|
21846
21846
|
|
|
21847
21847
|
**You have access to:**
|
|
21848
21848
|
- All standard tools (read, write, edit, bash, glob, grep)
|
|
21849
|
-
- \`
|
|
21850
|
-
- \`
|
|
21849
|
+
- \`hive_worktree_commit\` - Signal task done/blocked/failed
|
|
21850
|
+
- \`hive_worktree_discard\` - Abort and discard changes
|
|
21851
21851
|
- \`hive_plan_read\` - Re-read plan if needed
|
|
21852
21852
|
- \`hive_context_write\` - Save learnings for future tasks
|
|
21853
21853
|
|
|
21854
21854
|
**You do NOT have access to (or should not use):**
|
|
21855
21855
|
- \`question\` - Escalate via blocker protocol instead
|
|
21856
|
-
- \`
|
|
21856
|
+
- \`hive_worktree_create\` - No spawning sub-workers
|
|
21857
21857
|
- \`hive_merge\` - Only Hive Master merges
|
|
21858
|
-
- \`
|
|
21858
|
+
- \`task\` - No recursive delegation
|
|
21859
21859
|
|
|
21860
21860
|
---
|
|
21861
21861
|
|
|
@@ -21865,7 +21865,7 @@ After 3 failed attempts at same fix: STOP and report blocker.
|
|
|
21865
21865
|
2. **Stay in scope** - Only do what the spec asks
|
|
21866
21866
|
3. **Escalate blockers** - Don't guess on important decisions
|
|
21867
21867
|
4. **Save context** - Use hive_context_write for discoveries
|
|
21868
|
-
5. **Complete cleanly** - Always call
|
|
21868
|
+
5. **Complete cleanly** - Always call hive_worktree_commit when done
|
|
21869
21869
|
|
|
21870
21870
|
---
|
|
21871
21871
|
|
|
@@ -22107,63 +22107,6 @@ function applyContextBudget(files, config2 = {}) {
|
|
|
22107
22107
|
// src/utils/prompt-file.ts
|
|
22108
22108
|
import * as fs6 from "fs";
|
|
22109
22109
|
import * as path5 from "path";
|
|
22110
|
-
function findWorkspaceRoot(startDir) {
|
|
22111
|
-
try {
|
|
22112
|
-
let current = path5.resolve(startDir);
|
|
22113
|
-
while (true) {
|
|
22114
|
-
const hivePath = path5.join(current, ".hive");
|
|
22115
|
-
if (fs6.existsSync(hivePath) && fs6.statSync(hivePath).isDirectory()) {
|
|
22116
|
-
return current;
|
|
22117
|
-
}
|
|
22118
|
-
const parent = path5.dirname(current);
|
|
22119
|
-
if (parent === current) {
|
|
22120
|
-
return null;
|
|
22121
|
-
}
|
|
22122
|
-
current = parent;
|
|
22123
|
-
}
|
|
22124
|
-
} catch {
|
|
22125
|
-
return null;
|
|
22126
|
-
}
|
|
22127
|
-
}
|
|
22128
|
-
function isValidPromptFilePath(filePath, workspaceRoot) {
|
|
22129
|
-
try {
|
|
22130
|
-
const normalizedFilePath = path5.resolve(filePath);
|
|
22131
|
-
const normalizedWorkspace = path5.resolve(workspaceRoot);
|
|
22132
|
-
let normalizedFilePathForCompare = normalizePath(normalizedFilePath);
|
|
22133
|
-
let normalizedWorkspaceForCompare = normalizePath(normalizedWorkspace);
|
|
22134
|
-
if (process.platform === "win32") {
|
|
22135
|
-
normalizedFilePathForCompare = normalizedFilePathForCompare.toLowerCase();
|
|
22136
|
-
normalizedWorkspaceForCompare = normalizedWorkspaceForCompare.toLowerCase();
|
|
22137
|
-
}
|
|
22138
|
-
if (!normalizedFilePathForCompare.startsWith(normalizedWorkspaceForCompare + "/") && normalizedFilePathForCompare !== normalizedWorkspaceForCompare) {
|
|
22139
|
-
return false;
|
|
22140
|
-
}
|
|
22141
|
-
return true;
|
|
22142
|
-
} catch {
|
|
22143
|
-
return false;
|
|
22144
|
-
}
|
|
22145
|
-
}
|
|
22146
|
-
async function resolvePromptFromFile(promptFilePath, workspaceRoot) {
|
|
22147
|
-
if (!isValidPromptFilePath(promptFilePath, workspaceRoot)) {
|
|
22148
|
-
return {
|
|
22149
|
-
error: `Prompt file path "${promptFilePath}" is outside the workspace. ` + `Only files within "${workspaceRoot}" are allowed.`
|
|
22150
|
-
};
|
|
22151
|
-
}
|
|
22152
|
-
const resolvedPath = path5.resolve(promptFilePath);
|
|
22153
|
-
if (!fs6.existsSync(resolvedPath)) {
|
|
22154
|
-
return {
|
|
22155
|
-
error: `Prompt file not found: "${resolvedPath}"`
|
|
22156
|
-
};
|
|
22157
|
-
}
|
|
22158
|
-
try {
|
|
22159
|
-
const content = fs6.readFileSync(resolvedPath, "utf-8");
|
|
22160
|
-
return { content };
|
|
22161
|
-
} catch (err) {
|
|
22162
|
-
return {
|
|
22163
|
-
error: `Failed to read prompt file: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
22164
|
-
};
|
|
22165
|
-
}
|
|
22166
|
-
}
|
|
22167
22110
|
function writeWorkerPromptFile(feature, task, prompt, hiveDir) {
|
|
22168
22111
|
const promptDir = path5.join(hiveDir, "features", feature, "tasks", task);
|
|
22169
22112
|
const promptPath = path5.join(promptDir, "worker-prompt.md");
|
|
@@ -22174,1470 +22117,77 @@ function writeWorkerPromptFile(feature, task, prompt, hiveDir) {
|
|
|
22174
22117
|
return promptPath;
|
|
22175
22118
|
}
|
|
22176
22119
|
|
|
22177
|
-
// src/
|
|
22178
|
-
var
|
|
22179
|
-
|
|
22180
|
-
|
|
22181
|
-
|
|
22182
|
-
|
|
22183
|
-
|
|
22184
|
-
|
|
22185
|
-
|
|
22186
|
-
|
|
22187
|
-
|
|
22188
|
-
|
|
22189
|
-
|
|
22190
|
-
|
|
22191
|
-
function isValidTransition(from, to) {
|
|
22192
|
-
return VALID_TRANSITIONS[from].includes(to);
|
|
22193
|
-
}
|
|
22194
|
-
// src/background/store.ts
|
|
22195
|
-
function generateTaskId() {
|
|
22196
|
-
const timestamp = Date.now().toString(36);
|
|
22197
|
-
const random = Math.random().toString(36).substring(2, 8);
|
|
22198
|
-
return `task-${timestamp}-${random}`;
|
|
22199
|
-
}
|
|
22200
|
-
|
|
22201
|
-
class BackgroundTaskStore {
|
|
22202
|
-
tasks = new Map;
|
|
22203
|
-
idempotencyIndex = new Map;
|
|
22204
|
-
create(options) {
|
|
22205
|
-
if (options.idempotencyKey) {
|
|
22206
|
-
const existingId = this.idempotencyIndex.get(options.idempotencyKey);
|
|
22207
|
-
if (existingId) {
|
|
22208
|
-
throw new Error(`Idempotency key "${options.idempotencyKey}" already exists for task "${existingId}". ` + `Use getByIdempotencyKey() to retrieve the existing task.`);
|
|
22209
|
-
}
|
|
22210
|
-
}
|
|
22211
|
-
const taskId = generateTaskId();
|
|
22212
|
-
const now = new Date().toISOString();
|
|
22213
|
-
const record2 = {
|
|
22214
|
-
taskId,
|
|
22215
|
-
sessionId: options.sessionId,
|
|
22216
|
-
agent: options.agent,
|
|
22217
|
-
description: options.description,
|
|
22218
|
-
status: "spawned",
|
|
22219
|
-
provider: "hive",
|
|
22220
|
-
idempotencyKey: options.idempotencyKey,
|
|
22221
|
-
createdAt: now,
|
|
22222
|
-
lastActiveAt: now,
|
|
22223
|
-
parentSessionId: options.parentSessionId,
|
|
22224
|
-
parentMessageId: options.parentMessageId,
|
|
22225
|
-
parentAgent: options.parentAgent,
|
|
22226
|
-
notifyParent: options.notifyParent,
|
|
22227
|
-
hiveFeature: options.hiveFeature,
|
|
22228
|
-
hiveTaskFolder: options.hiveTaskFolder,
|
|
22229
|
-
workdir: options.workdir
|
|
22230
|
-
};
|
|
22231
|
-
this.tasks.set(taskId, record2);
|
|
22232
|
-
if (options.idempotencyKey) {
|
|
22233
|
-
this.idempotencyIndex.set(options.idempotencyKey, taskId);
|
|
22234
|
-
}
|
|
22235
|
-
return record2;
|
|
22236
|
-
}
|
|
22237
|
-
get(taskId) {
|
|
22238
|
-
return this.tasks.get(taskId);
|
|
22239
|
-
}
|
|
22240
|
-
getByIdempotencyKey(key) {
|
|
22241
|
-
const taskId = this.idempotencyIndex.get(key);
|
|
22242
|
-
if (!taskId)
|
|
22243
|
-
return;
|
|
22244
|
-
return this.tasks.get(taskId);
|
|
22245
|
-
}
|
|
22246
|
-
getByHiveTask(feature, taskFolder) {
|
|
22247
|
-
for (const task of this.tasks.values()) {
|
|
22248
|
-
if (task.hiveFeature === feature && task.hiveTaskFolder === taskFolder) {
|
|
22249
|
-
return task;
|
|
22250
|
-
}
|
|
22251
|
-
}
|
|
22120
|
+
// src/hooks/variant-hook.ts
|
|
22121
|
+
var HIVE_AGENT_NAMES = [
|
|
22122
|
+
"hive-master",
|
|
22123
|
+
"architect-planner",
|
|
22124
|
+
"swarm-orchestrator",
|
|
22125
|
+
"scout-researcher",
|
|
22126
|
+
"forager-worker",
|
|
22127
|
+
"hygienic-reviewer"
|
|
22128
|
+
];
|
|
22129
|
+
function isHiveAgent(agent) {
|
|
22130
|
+
return agent !== undefined && HIVE_AGENT_NAMES.includes(agent);
|
|
22131
|
+
}
|
|
22132
|
+
function normalizeVariant(variant) {
|
|
22133
|
+
if (variant === undefined)
|
|
22252
22134
|
return;
|
|
22253
|
-
|
|
22254
|
-
|
|
22255
|
-
const task = this.tasks.get(taskId);
|
|
22256
|
-
if (!task) {
|
|
22257
|
-
throw new Error(`Task "${taskId}" not found`);
|
|
22258
|
-
}
|
|
22259
|
-
if (!isValidTransition(task.status, newStatus)) {
|
|
22260
|
-
throw new Error(`Invalid state transition: ${task.status} -> ${newStatus} for task "${taskId}"`);
|
|
22261
|
-
}
|
|
22262
|
-
const now = new Date().toISOString();
|
|
22263
|
-
if (newStatus === "running" && !task.startedAt) {
|
|
22264
|
-
task.startedAt = now;
|
|
22265
|
-
}
|
|
22266
|
-
if (isTerminalStatus(newStatus) && !task.completedAt) {
|
|
22267
|
-
task.completedAt = now;
|
|
22268
|
-
}
|
|
22269
|
-
task.status = newStatus;
|
|
22270
|
-
task.lastActiveAt = now;
|
|
22271
|
-
if (updates?.errorMessage !== undefined) {
|
|
22272
|
-
task.errorMessage = updates.errorMessage;
|
|
22273
|
-
}
|
|
22274
|
-
if (updates?.progress !== undefined) {
|
|
22275
|
-
task.progress = { ...task.progress, ...updates.progress };
|
|
22276
|
-
}
|
|
22277
|
-
return task;
|
|
22278
|
-
}
|
|
22279
|
-
updateProgress(taskId, progress) {
|
|
22280
|
-
const task = this.tasks.get(taskId);
|
|
22281
|
-
if (!task) {
|
|
22282
|
-
throw new Error(`Task "${taskId}" not found`);
|
|
22283
|
-
}
|
|
22284
|
-
const now = new Date().toISOString();
|
|
22285
|
-
task.lastActiveAt = now;
|
|
22286
|
-
task.progress = { ...task.progress, ...progress };
|
|
22287
|
-
return task;
|
|
22288
|
-
}
|
|
22289
|
-
delete(taskId) {
|
|
22290
|
-
const task = this.tasks.get(taskId);
|
|
22291
|
-
if (!task)
|
|
22292
|
-
return false;
|
|
22293
|
-
if (task.idempotencyKey) {
|
|
22294
|
-
this.idempotencyIndex.delete(task.idempotencyKey);
|
|
22295
|
-
}
|
|
22296
|
-
return this.tasks.delete(taskId);
|
|
22297
|
-
}
|
|
22298
|
-
list(filter) {
|
|
22299
|
-
let tasks = Array.from(this.tasks.values());
|
|
22300
|
-
if (filter?.status) {
|
|
22301
|
-
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
22302
|
-
tasks = tasks.filter((t) => statuses.includes(t.status));
|
|
22303
|
-
}
|
|
22304
|
-
if (filter?.parentSessionId) {
|
|
22305
|
-
tasks = tasks.filter((t) => t.parentSessionId === filter.parentSessionId);
|
|
22306
|
-
}
|
|
22307
|
-
if (filter?.hiveFeature) {
|
|
22308
|
-
tasks = tasks.filter((t) => t.hiveFeature === filter.hiveFeature);
|
|
22309
|
-
}
|
|
22310
|
-
return tasks.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
22311
|
-
}
|
|
22312
|
-
getActive() {
|
|
22313
|
-
return this.list().filter((t) => !isTerminalStatus(t.status));
|
|
22314
|
-
}
|
|
22315
|
-
countByStatus() {
|
|
22316
|
-
const counts = {
|
|
22317
|
-
spawned: 0,
|
|
22318
|
-
pending: 0,
|
|
22319
|
-
running: 0,
|
|
22320
|
-
completed: 0,
|
|
22321
|
-
error: 0,
|
|
22322
|
-
cancelled: 0,
|
|
22323
|
-
blocked: 0,
|
|
22324
|
-
failed: 0
|
|
22325
|
-
};
|
|
22326
|
-
for (const task of this.tasks.values()) {
|
|
22327
|
-
counts[task.status]++;
|
|
22328
|
-
}
|
|
22329
|
-
return counts;
|
|
22330
|
-
}
|
|
22331
|
-
clear() {
|
|
22332
|
-
this.tasks.clear();
|
|
22333
|
-
this.idempotencyIndex.clear();
|
|
22334
|
-
}
|
|
22335
|
-
get size() {
|
|
22336
|
-
return this.tasks.size;
|
|
22337
|
-
}
|
|
22135
|
+
const trimmed2 = variant.trim();
|
|
22136
|
+
return trimmed2.length > 0 ? trimmed2 : undefined;
|
|
22338
22137
|
}
|
|
22339
|
-
|
|
22340
|
-
|
|
22341
|
-
|
|
22342
|
-
|
|
22343
|
-
|
|
22344
|
-
|
|
22138
|
+
|
|
22139
|
+
// src/index.ts
|
|
22140
|
+
function formatSkillsXml(skills) {
|
|
22141
|
+
if (skills.length === 0)
|
|
22142
|
+
return "";
|
|
22143
|
+
const skillsXml = skills.map((skill) => {
|
|
22144
|
+
return [
|
|
22145
|
+
" <skill>",
|
|
22146
|
+
` <name>${skill.name}</name>`,
|
|
22147
|
+
` <description>(hive - Skill) ${skill.description}</description>`,
|
|
22148
|
+
" </skill>"
|
|
22149
|
+
].join(`
|
|
22150
|
+
`);
|
|
22151
|
+
}).join(`
|
|
22152
|
+
`);
|
|
22153
|
+
return `
|
|
22154
|
+
|
|
22155
|
+
<available_skills>
|
|
22156
|
+
${skillsXml}
|
|
22157
|
+
</available_skills>`;
|
|
22345
22158
|
}
|
|
22346
|
-
|
|
22347
|
-
|
|
22348
|
-
|
|
22349
|
-
|
|
22350
|
-
|
|
22351
|
-
"swarm-orchestrator",
|
|
22352
|
-
"conductor",
|
|
22353
|
-
"main"
|
|
22354
|
-
]);
|
|
22355
|
-
var RESTRICTED_AGENTS = new Set([
|
|
22356
|
-
"admin",
|
|
22357
|
-
"root",
|
|
22358
|
-
"superuser"
|
|
22359
|
-
]);
|
|
22360
|
-
|
|
22361
|
-
class AgentGate {
|
|
22362
|
-
client;
|
|
22363
|
-
cachedAgents = null;
|
|
22364
|
-
cacheExpiry = 0;
|
|
22365
|
-
cacheTtlMs = 30000;
|
|
22366
|
-
constructor(client) {
|
|
22367
|
-
this.client = client;
|
|
22368
|
-
}
|
|
22369
|
-
async discoverAgents() {
|
|
22370
|
-
const now = Date.now();
|
|
22371
|
-
if (this.cachedAgents && now < this.cacheExpiry) {
|
|
22372
|
-
return this.cachedAgents;
|
|
22373
|
-
}
|
|
22374
|
-
try {
|
|
22375
|
-
const result = await this.client.app.agents({});
|
|
22376
|
-
const agents = result.data ?? [];
|
|
22377
|
-
this.cachedAgents = agents;
|
|
22378
|
-
this.cacheExpiry = now + this.cacheTtlMs;
|
|
22379
|
-
return agents;
|
|
22380
|
-
} catch (error45) {
|
|
22381
|
-
if (this.cachedAgents) {
|
|
22382
|
-
return this.cachedAgents;
|
|
22383
|
-
}
|
|
22384
|
-
throw new Error(`Failed to discover agents: ${error45 instanceof Error ? error45.message : "Unknown error"}`);
|
|
22385
|
-
}
|
|
22159
|
+
async function buildAutoLoadedSkillsContent(agentName, configService, projectRoot) {
|
|
22160
|
+
const agentConfig = configService.getAgentConfig(agentName);
|
|
22161
|
+
const autoLoadSkills = agentConfig.autoLoadSkills ?? [];
|
|
22162
|
+
if (autoLoadSkills.length === 0) {
|
|
22163
|
+
return "";
|
|
22386
22164
|
}
|
|
22387
|
-
|
|
22388
|
-
|
|
22389
|
-
|
|
22390
|
-
|
|
22391
|
-
|
|
22392
|
-
|
|
22393
|
-
|
|
22394
|
-
}
|
|
22395
|
-
if (options.additionalBlocked?.includes(name)) {
|
|
22396
|
-
return {
|
|
22397
|
-
valid: false,
|
|
22398
|
-
error: `Agent "${agentName}" is blocked by configuration.`
|
|
22399
|
-
};
|
|
22400
|
-
}
|
|
22401
|
-
if (RESTRICTED_AGENTS.has(name) && !options.allowRestricted) {
|
|
22402
|
-
return {
|
|
22403
|
-
valid: false,
|
|
22404
|
-
error: `Agent "${agentName}" is restricted and requires explicit allowance. ` + `Set allowRestricted: true to spawn this agent.`
|
|
22405
|
-
};
|
|
22406
|
-
}
|
|
22407
|
-
let agents;
|
|
22408
|
-
try {
|
|
22409
|
-
agents = await this.discoverAgents();
|
|
22410
|
-
} catch (error45) {
|
|
22411
|
-
return {
|
|
22412
|
-
valid: false,
|
|
22413
|
-
error: error45 instanceof Error ? error45.message : "Failed to discover agents"
|
|
22414
|
-
};
|
|
22165
|
+
const homeDir = process.env.HOME || os.homedir();
|
|
22166
|
+
const skillTemplates = [];
|
|
22167
|
+
for (const skillId of autoLoadSkills) {
|
|
22168
|
+
const builtinSkill = BUILTIN_SKILLS.find((entry) => entry.name === skillId);
|
|
22169
|
+
if (builtinSkill) {
|
|
22170
|
+
skillTemplates.push(builtinSkill.template);
|
|
22171
|
+
continue;
|
|
22415
22172
|
}
|
|
22416
|
-
const
|
|
22417
|
-
if (
|
|
22418
|
-
|
|
22419
|
-
|
|
22420
|
-
return {
|
|
22421
|
-
valid: false,
|
|
22422
|
-
error: `Agent "${agentName}" not found in registry.
|
|
22423
|
-
|
|
22424
|
-
Available agents:
|
|
22425
|
-
${available || "(none)"}`
|
|
22426
|
-
};
|
|
22173
|
+
const fileResult = await loadFileSkill(skillId, projectRoot, homeDir);
|
|
22174
|
+
if (fileResult.found && fileResult.skill) {
|
|
22175
|
+
skillTemplates.push(fileResult.skill.template);
|
|
22176
|
+
continue;
|
|
22427
22177
|
}
|
|
22428
|
-
|
|
22429
|
-
valid: true,
|
|
22430
|
-
agent
|
|
22431
|
-
};
|
|
22432
|
-
}
|
|
22433
|
-
async getWorkerAgents() {
|
|
22434
|
-
const agents = await this.discoverAgents();
|
|
22435
|
-
return agents.filter((a) => {
|
|
22436
|
-
const name = a.name.toLowerCase();
|
|
22437
|
-
return !BLOCKED_AGENTS.has(name) && !RESTRICTED_AGENTS.has(name);
|
|
22438
|
-
});
|
|
22439
|
-
}
|
|
22440
|
-
clearCache() {
|
|
22441
|
-
this.cachedAgents = null;
|
|
22442
|
-
this.cacheExpiry = 0;
|
|
22443
|
-
}
|
|
22444
|
-
isBlocked(agentName) {
|
|
22445
|
-
return BLOCKED_AGENTS.has(agentName.trim().toLowerCase());
|
|
22178
|
+
console.warn(`[hive] Unknown skill id "${skillId}" for agent "${agentName}"`);
|
|
22446
22179
|
}
|
|
22447
|
-
|
|
22448
|
-
return
|
|
22180
|
+
if (skillTemplates.length === 0) {
|
|
22181
|
+
return "";
|
|
22449
22182
|
}
|
|
22183
|
+
return `
|
|
22184
|
+
|
|
22185
|
+
` + skillTemplates.join(`
|
|
22186
|
+
|
|
22187
|
+
`);
|
|
22450
22188
|
}
|
|
22451
|
-
function
|
|
22452
|
-
|
|
22453
|
-
}
|
|
22454
|
-
// src/background/concurrency.ts
|
|
22455
|
-
var DEFAULT_CONFIG = {
|
|
22456
|
-
defaultLimit: 3,
|
|
22457
|
-
agentLimits: {},
|
|
22458
|
-
modelLimits: {},
|
|
22459
|
-
queueTimeoutMs: 5 * 60 * 1000,
|
|
22460
|
-
minDelayBetweenStartsMs: 1000
|
|
22461
|
-
};
|
|
22462
|
-
|
|
22463
|
-
class ConcurrencyManager {
|
|
22464
|
-
config;
|
|
22465
|
-
counts = new Map;
|
|
22466
|
-
queues = new Map;
|
|
22467
|
-
lastStartTimes = new Map;
|
|
22468
|
-
constructor(config2 = {}) {
|
|
22469
|
-
this.config = { ...DEFAULT_CONFIG, ...config2 };
|
|
22470
|
-
}
|
|
22471
|
-
getLimit(key) {
|
|
22472
|
-
if (key.includes("/")) {
|
|
22473
|
-
const modelLimit = this.config.modelLimits[key];
|
|
22474
|
-
if (modelLimit !== undefined) {
|
|
22475
|
-
return modelLimit === 0 ? Infinity : modelLimit;
|
|
22476
|
-
}
|
|
22477
|
-
const provider = key.split("/")[0];
|
|
22478
|
-
const providerLimit = this.config.modelLimits[provider];
|
|
22479
|
-
if (providerLimit !== undefined) {
|
|
22480
|
-
return providerLimit === 0 ? Infinity : providerLimit;
|
|
22481
|
-
}
|
|
22482
|
-
}
|
|
22483
|
-
const agentLimit = this.config.agentLimits[key];
|
|
22484
|
-
if (agentLimit !== undefined) {
|
|
22485
|
-
return agentLimit === 0 ? Infinity : agentLimit;
|
|
22486
|
-
}
|
|
22487
|
-
return this.config.defaultLimit === 0 ? Infinity : this.config.defaultLimit;
|
|
22488
|
-
}
|
|
22489
|
-
async acquire(key) {
|
|
22490
|
-
const limit = this.getLimit(key);
|
|
22491
|
-
if (limit === Infinity) {
|
|
22492
|
-
return;
|
|
22493
|
-
}
|
|
22494
|
-
await this.enforceRateLimit(key);
|
|
22495
|
-
const current = this.counts.get(key) ?? 0;
|
|
22496
|
-
if (current < limit) {
|
|
22497
|
-
this.counts.set(key, current + 1);
|
|
22498
|
-
this.lastStartTimes.set(key, Date.now());
|
|
22499
|
-
return;
|
|
22500
|
-
}
|
|
22501
|
-
return new Promise((resolve2, reject) => {
|
|
22502
|
-
const queue = this.queues.get(key) ?? [];
|
|
22503
|
-
const entry = {
|
|
22504
|
-
resolve: () => {
|
|
22505
|
-
if (entry.settled)
|
|
22506
|
-
return;
|
|
22507
|
-
entry.settled = true;
|
|
22508
|
-
clearTimeout(entry.timeoutId);
|
|
22509
|
-
this.lastStartTimes.set(key, Date.now());
|
|
22510
|
-
resolve2();
|
|
22511
|
-
},
|
|
22512
|
-
reject: (error45) => {
|
|
22513
|
-
if (entry.settled)
|
|
22514
|
-
return;
|
|
22515
|
-
entry.settled = true;
|
|
22516
|
-
clearTimeout(entry.timeoutId);
|
|
22517
|
-
reject(error45);
|
|
22518
|
-
},
|
|
22519
|
-
settled: false,
|
|
22520
|
-
enqueuedAt: Date.now(),
|
|
22521
|
-
timeoutId: setTimeout(() => {
|
|
22522
|
-
if (!entry.settled) {
|
|
22523
|
-
entry.settled = true;
|
|
22524
|
-
const q = this.queues.get(key);
|
|
22525
|
-
if (q) {
|
|
22526
|
-
const idx = q.indexOf(entry);
|
|
22527
|
-
if (idx !== -1)
|
|
22528
|
-
q.splice(idx, 1);
|
|
22529
|
-
}
|
|
22530
|
-
reject(new Error(`Concurrency queue timeout for key "${key}" after ${this.config.queueTimeoutMs}ms`));
|
|
22531
|
-
}
|
|
22532
|
-
}, this.config.queueTimeoutMs)
|
|
22533
|
-
};
|
|
22534
|
-
queue.push(entry);
|
|
22535
|
-
this.queues.set(key, queue);
|
|
22536
|
-
});
|
|
22537
|
-
}
|
|
22538
|
-
release(key) {
|
|
22539
|
-
const limit = this.getLimit(key);
|
|
22540
|
-
if (limit === Infinity) {
|
|
22541
|
-
return;
|
|
22542
|
-
}
|
|
22543
|
-
const queue = this.queues.get(key);
|
|
22544
|
-
while (queue && queue.length > 0) {
|
|
22545
|
-
const next = queue.shift();
|
|
22546
|
-
if (!next.settled) {
|
|
22547
|
-
next.resolve();
|
|
22548
|
-
return;
|
|
22549
|
-
}
|
|
22550
|
-
}
|
|
22551
|
-
const current = this.counts.get(key) ?? 0;
|
|
22552
|
-
if (current > 0) {
|
|
22553
|
-
this.counts.set(key, current - 1);
|
|
22554
|
-
}
|
|
22555
|
-
}
|
|
22556
|
-
tryAcquire(key) {
|
|
22557
|
-
const limit = this.getLimit(key);
|
|
22558
|
-
if (limit === Infinity) {
|
|
22559
|
-
return true;
|
|
22560
|
-
}
|
|
22561
|
-
const current = this.counts.get(key) ?? 0;
|
|
22562
|
-
if (current < limit) {
|
|
22563
|
-
this.counts.set(key, current + 1);
|
|
22564
|
-
this.lastStartTimes.set(key, Date.now());
|
|
22565
|
-
return true;
|
|
22566
|
-
}
|
|
22567
|
-
return false;
|
|
22568
|
-
}
|
|
22569
|
-
cancelWaiters(key) {
|
|
22570
|
-
const queue = this.queues.get(key);
|
|
22571
|
-
if (!queue)
|
|
22572
|
-
return 0;
|
|
22573
|
-
let cancelled = 0;
|
|
22574
|
-
for (const entry of queue) {
|
|
22575
|
-
if (!entry.settled) {
|
|
22576
|
-
entry.reject(new Error(`Concurrency queue cancelled for key: ${key}`));
|
|
22577
|
-
cancelled++;
|
|
22578
|
-
}
|
|
22579
|
-
}
|
|
22580
|
-
this.queues.delete(key);
|
|
22581
|
-
return cancelled;
|
|
22582
|
-
}
|
|
22583
|
-
clear() {
|
|
22584
|
-
for (const key of this.queues.keys()) {
|
|
22585
|
-
this.cancelWaiters(key);
|
|
22586
|
-
}
|
|
22587
|
-
this.counts.clear();
|
|
22588
|
-
this.queues.clear();
|
|
22589
|
-
this.lastStartTimes.clear();
|
|
22590
|
-
}
|
|
22591
|
-
getCount(key) {
|
|
22592
|
-
return this.counts.get(key) ?? 0;
|
|
22593
|
-
}
|
|
22594
|
-
getQueueLength(key) {
|
|
22595
|
-
const queue = this.queues.get(key);
|
|
22596
|
-
if (!queue)
|
|
22597
|
-
return 0;
|
|
22598
|
-
return queue.filter((e) => !e.settled).length;
|
|
22599
|
-
}
|
|
22600
|
-
getAvailable(key) {
|
|
22601
|
-
const limit = this.getLimit(key);
|
|
22602
|
-
if (limit === Infinity)
|
|
22603
|
-
return Infinity;
|
|
22604
|
-
const current = this.counts.get(key) ?? 0;
|
|
22605
|
-
return Math.max(0, limit - current);
|
|
22606
|
-
}
|
|
22607
|
-
isAtCapacity(key) {
|
|
22608
|
-
const limit = this.getLimit(key);
|
|
22609
|
-
if (limit === Infinity)
|
|
22610
|
-
return false;
|
|
22611
|
-
const current = this.counts.get(key) ?? 0;
|
|
22612
|
-
return current >= limit;
|
|
22613
|
-
}
|
|
22614
|
-
getStatus() {
|
|
22615
|
-
const status = {};
|
|
22616
|
-
for (const [key, count] of this.counts.entries()) {
|
|
22617
|
-
status[key] = {
|
|
22618
|
-
count,
|
|
22619
|
-
limit: this.getLimit(key),
|
|
22620
|
-
queued: this.getQueueLength(key)
|
|
22621
|
-
};
|
|
22622
|
-
}
|
|
22623
|
-
for (const key of this.queues.keys()) {
|
|
22624
|
-
if (!status[key]) {
|
|
22625
|
-
status[key] = {
|
|
22626
|
-
count: 0,
|
|
22627
|
-
limit: this.getLimit(key),
|
|
22628
|
-
queued: this.getQueueLength(key)
|
|
22629
|
-
};
|
|
22630
|
-
}
|
|
22631
|
-
}
|
|
22632
|
-
return status;
|
|
22633
|
-
}
|
|
22634
|
-
async enforceRateLimit(key) {
|
|
22635
|
-
const lastStart = this.lastStartTimes.get(key);
|
|
22636
|
-
if (!lastStart)
|
|
22637
|
-
return;
|
|
22638
|
-
const elapsed = Date.now() - lastStart;
|
|
22639
|
-
const delay2 = this.config.minDelayBetweenStartsMs - elapsed;
|
|
22640
|
-
if (delay2 > 0) {
|
|
22641
|
-
await new Promise((resolve2) => setTimeout(resolve2, delay2));
|
|
22642
|
-
}
|
|
22643
|
-
}
|
|
22644
|
-
}
|
|
22645
|
-
function createConcurrencyManager(config2) {
|
|
22646
|
-
return new ConcurrencyManager(config2);
|
|
22647
|
-
}
|
|
22648
|
-
// src/background/poller.ts
|
|
22649
|
-
var DEFAULT_CONFIG2 = {
|
|
22650
|
-
pollIntervalMs: 5000,
|
|
22651
|
-
maxPollIntervalMs: 30000,
|
|
22652
|
-
stuckThresholdMs: 10 * 60 * 1000,
|
|
22653
|
-
minRuntimeBeforeStuckMs: 30 * 1000,
|
|
22654
|
-
stableCountThreshold: 3
|
|
22655
|
-
};
|
|
22656
|
-
|
|
22657
|
-
class BackgroundPoller {
|
|
22658
|
-
store;
|
|
22659
|
-
client;
|
|
22660
|
-
config;
|
|
22661
|
-
handlers;
|
|
22662
|
-
pollingState = new Map;
|
|
22663
|
-
pollInterval = null;
|
|
22664
|
-
isPolling = false;
|
|
22665
|
-
currentIntervalMs;
|
|
22666
|
-
constructor(store, client, config2 = {}, handlers = {}) {
|
|
22667
|
-
this.store = store;
|
|
22668
|
-
this.client = client;
|
|
22669
|
-
this.config = { ...DEFAULT_CONFIG2, ...config2 };
|
|
22670
|
-
this.handlers = handlers;
|
|
22671
|
-
this.currentIntervalMs = this.config.pollIntervalMs;
|
|
22672
|
-
}
|
|
22673
|
-
start() {
|
|
22674
|
-
if (this.pollInterval)
|
|
22675
|
-
return;
|
|
22676
|
-
this.pollInterval = setInterval(() => {
|
|
22677
|
-
this.poll().catch((err) => {
|
|
22678
|
-
console.warn("[BackgroundPoller] Poll error:", err);
|
|
22679
|
-
});
|
|
22680
|
-
}, this.currentIntervalMs);
|
|
22681
|
-
this.pollInterval.unref();
|
|
22682
|
-
}
|
|
22683
|
-
stop() {
|
|
22684
|
-
if (this.pollInterval) {
|
|
22685
|
-
clearInterval(this.pollInterval);
|
|
22686
|
-
this.pollInterval = null;
|
|
22687
|
-
}
|
|
22688
|
-
this.isPolling = false;
|
|
22689
|
-
}
|
|
22690
|
-
isRunning() {
|
|
22691
|
-
return this.pollInterval !== null;
|
|
22692
|
-
}
|
|
22693
|
-
getObservations() {
|
|
22694
|
-
const observations = [];
|
|
22695
|
-
const activeTasks = this.store.getActive();
|
|
22696
|
-
for (const task of activeTasks) {
|
|
22697
|
-
const state = this.pollingState.get(task.taskId);
|
|
22698
|
-
const observation = this.buildObservation(task, state);
|
|
22699
|
-
observations.push(observation);
|
|
22700
|
-
}
|
|
22701
|
-
return observations;
|
|
22702
|
-
}
|
|
22703
|
-
getTaskObservation(taskId) {
|
|
22704
|
-
const task = this.store.get(taskId);
|
|
22705
|
-
if (!task)
|
|
22706
|
-
return null;
|
|
22707
|
-
const state = this.pollingState.get(taskId);
|
|
22708
|
-
return this.buildObservation(task, state);
|
|
22709
|
-
}
|
|
22710
|
-
async poll() {
|
|
22711
|
-
if (this.isPolling)
|
|
22712
|
-
return;
|
|
22713
|
-
this.isPolling = true;
|
|
22714
|
-
try {
|
|
22715
|
-
const activeTasks = this.store.getActive();
|
|
22716
|
-
if (activeTasks.length === 0) {
|
|
22717
|
-
this.stop();
|
|
22718
|
-
return;
|
|
22719
|
-
}
|
|
22720
|
-
let sessionStatuses = {};
|
|
22721
|
-
try {
|
|
22722
|
-
const statusResult = await this.client.session.status?.();
|
|
22723
|
-
sessionStatuses = statusResult?.data ?? {};
|
|
22724
|
-
} catch {}
|
|
22725
|
-
for (const task of activeTasks) {
|
|
22726
|
-
if (task.status !== "running")
|
|
22727
|
-
continue;
|
|
22728
|
-
await this.pollTask(task, sessionStatuses);
|
|
22729
|
-
}
|
|
22730
|
-
this.adjustPollingInterval(activeTasks);
|
|
22731
|
-
} finally {
|
|
22732
|
-
this.isPolling = false;
|
|
22733
|
-
}
|
|
22734
|
-
}
|
|
22735
|
-
async pollTask(task, sessionStatuses) {
|
|
22736
|
-
const state = this.pollingState.get(task.taskId) ?? {
|
|
22737
|
-
lastMessageCount: 0,
|
|
22738
|
-
stablePolls: 0,
|
|
22739
|
-
lastPollAt: Date.now(),
|
|
22740
|
-
consecutiveErrors: 0
|
|
22741
|
-
};
|
|
22742
|
-
try {
|
|
22743
|
-
const sessionType = sessionStatuses[task.sessionId]?.type;
|
|
22744
|
-
const messagesResult = await this.client.session.messages({
|
|
22745
|
-
path: { id: task.sessionId }
|
|
22746
|
-
});
|
|
22747
|
-
const messages = messagesResult.data ?? [];
|
|
22748
|
-
const currentMessageCount = messages.length;
|
|
22749
|
-
const now = new Date().toISOString();
|
|
22750
|
-
if (currentMessageCount > state.lastMessageCount) {
|
|
22751
|
-
state.stablePolls = 0;
|
|
22752
|
-
this.store.updateProgress(task.taskId, {
|
|
22753
|
-
messageCount: currentMessageCount,
|
|
22754
|
-
lastMessageAt: now
|
|
22755
|
-
});
|
|
22756
|
-
} else {
|
|
22757
|
-
state.stablePolls++;
|
|
22758
|
-
}
|
|
22759
|
-
state.lastMessageCount = currentMessageCount;
|
|
22760
|
-
state.lastPollAt = Date.now();
|
|
22761
|
-
state.consecutiveErrors = 0;
|
|
22762
|
-
this.pollingState.set(task.taskId, state);
|
|
22763
|
-
const isIdle = sessionType === "idle" || sessionType === "completed";
|
|
22764
|
-
if (isIdle) {
|
|
22765
|
-
this.handlers.onSessionIdle?.(task.sessionId, "status");
|
|
22766
|
-
return;
|
|
22767
|
-
}
|
|
22768
|
-
if (!sessionType && currentMessageCount > 0 && state.stablePolls >= this.config.stableCountThreshold) {
|
|
22769
|
-
try {
|
|
22770
|
-
const session = await this.client.session.get({ path: { id: task.sessionId } });
|
|
22771
|
-
const status = session.data?.status;
|
|
22772
|
-
if (status === "idle" || status === "completed") {
|
|
22773
|
-
this.handlers.onSessionIdle?.(task.sessionId, "status");
|
|
22774
|
-
return;
|
|
22775
|
-
}
|
|
22776
|
-
} catch {}
|
|
22777
|
-
this.handlers.onSessionIdle?.(task.sessionId, "stable");
|
|
22778
|
-
}
|
|
22779
|
-
} catch (error45) {
|
|
22780
|
-
state.consecutiveErrors++;
|
|
22781
|
-
state.lastPollAt = Date.now();
|
|
22782
|
-
this.pollingState.set(task.taskId, state);
|
|
22783
|
-
if (state.consecutiveErrors >= 3) {
|
|
22784
|
-
console.warn(`[BackgroundPoller] Multiple errors polling task ${task.taskId}:`, error45);
|
|
22785
|
-
}
|
|
22786
|
-
}
|
|
22787
|
-
}
|
|
22788
|
-
buildObservation(task, state) {
|
|
22789
|
-
const now = Date.now();
|
|
22790
|
-
const stablePolls = state?.stablePolls ?? 0;
|
|
22791
|
-
const lastActivityAt = task.progress?.lastMessageAt ?? task.lastActiveAt ?? null;
|
|
22792
|
-
let maybeStuck = false;
|
|
22793
|
-
if (task.status === "running" && task.startedAt) {
|
|
22794
|
-
const startedAt = new Date(task.startedAt).getTime();
|
|
22795
|
-
const runtime = now - startedAt;
|
|
22796
|
-
if (runtime >= this.config.minRuntimeBeforeStuckMs) {
|
|
22797
|
-
const lastActivity = lastActivityAt ? new Date(lastActivityAt).getTime() : startedAt;
|
|
22798
|
-
const timeSinceActivity = now - lastActivity;
|
|
22799
|
-
maybeStuck = timeSinceActivity >= this.config.stuckThresholdMs;
|
|
22800
|
-
}
|
|
22801
|
-
}
|
|
22802
|
-
return {
|
|
22803
|
-
taskId: task.taskId,
|
|
22804
|
-
sessionId: task.sessionId,
|
|
22805
|
-
status: task.status,
|
|
22806
|
-
messageCount: task.progress?.messageCount ?? 0,
|
|
22807
|
-
lastActivityAt,
|
|
22808
|
-
maybeStuck,
|
|
22809
|
-
stablePolls,
|
|
22810
|
-
isStable: stablePolls >= this.config.stableCountThreshold
|
|
22811
|
-
};
|
|
22812
|
-
}
|
|
22813
|
-
adjustPollingInterval(activeTasks) {
|
|
22814
|
-
const now = Date.now();
|
|
22815
|
-
let recentActivityCount = 0;
|
|
22816
|
-
for (const task of activeTasks) {
|
|
22817
|
-
const state = this.pollingState.get(task.taskId);
|
|
22818
|
-
if (state && state.stablePolls < 2) {
|
|
22819
|
-
recentActivityCount++;
|
|
22820
|
-
}
|
|
22821
|
-
}
|
|
22822
|
-
const stableRatio = activeTasks.length > 0 ? (activeTasks.length - recentActivityCount) / activeTasks.length : 0;
|
|
22823
|
-
if (stableRatio > 0.8) {
|
|
22824
|
-
this.currentIntervalMs = Math.min(this.currentIntervalMs * 1.5, this.config.maxPollIntervalMs);
|
|
22825
|
-
} else if (recentActivityCount > 0) {
|
|
22826
|
-
this.currentIntervalMs = this.config.pollIntervalMs;
|
|
22827
|
-
}
|
|
22828
|
-
if (this.pollInterval && Math.abs(this.currentIntervalMs - this.config.pollIntervalMs) > 1000) {
|
|
22829
|
-
this.stop();
|
|
22830
|
-
this.start();
|
|
22831
|
-
}
|
|
22832
|
-
}
|
|
22833
|
-
cleanupTask(taskId) {
|
|
22834
|
-
this.pollingState.delete(taskId);
|
|
22835
|
-
}
|
|
22836
|
-
clear() {
|
|
22837
|
-
this.stop();
|
|
22838
|
-
this.pollingState.clear();
|
|
22839
|
-
}
|
|
22840
|
-
}
|
|
22841
|
-
function createPoller(store, client, config2, handlers) {
|
|
22842
|
-
return new BackgroundPoller(store, client, config2, handlers);
|
|
22843
|
-
}
|
|
22844
|
-
// src/background/manager.ts
|
|
22845
|
-
class BackgroundManager {
|
|
22846
|
-
store;
|
|
22847
|
-
agentGate;
|
|
22848
|
-
client;
|
|
22849
|
-
taskService;
|
|
22850
|
-
concurrencyManager;
|
|
22851
|
-
poller;
|
|
22852
|
-
enforceHiveSequential;
|
|
22853
|
-
constructor(options) {
|
|
22854
|
-
this.client = options.client;
|
|
22855
|
-
this.store = options.store ?? getStore();
|
|
22856
|
-
this.agentGate = createAgentGate(options.client);
|
|
22857
|
-
this.taskService = new TaskService(options.projectRoot);
|
|
22858
|
-
this.concurrencyManager = createConcurrencyManager(options.concurrency);
|
|
22859
|
-
this.poller = createPoller(this.store, options.client, options.poller, {
|
|
22860
|
-
onSessionIdle: (sessionId) => this.handleSessionIdle(sessionId)
|
|
22861
|
-
});
|
|
22862
|
-
this.enforceHiveSequential = options.enforceHiveSequential ?? true;
|
|
22863
|
-
}
|
|
22864
|
-
async spawn(options) {
|
|
22865
|
-
if (options.idempotencyKey) {
|
|
22866
|
-
const existing = this.store.getByIdempotencyKey(options.idempotencyKey);
|
|
22867
|
-
if (existing) {
|
|
22868
|
-
return {
|
|
22869
|
-
task: existing,
|
|
22870
|
-
wasExisting: true
|
|
22871
|
-
};
|
|
22872
|
-
}
|
|
22873
|
-
}
|
|
22874
|
-
if (options.hiveFeature && options.hiveTaskFolder) {
|
|
22875
|
-
const existing = this.store.getByHiveTask(options.hiveFeature, options.hiveTaskFolder);
|
|
22876
|
-
if (existing && !isTerminalStatus(existing.status)) {
|
|
22877
|
-
return {
|
|
22878
|
-
task: existing,
|
|
22879
|
-
wasExisting: true
|
|
22880
|
-
};
|
|
22881
|
-
}
|
|
22882
|
-
if (this.enforceHiveSequential) {
|
|
22883
|
-
const orderingCheck = this.checkHiveTaskOrdering(options.hiveFeature, options.hiveTaskFolder);
|
|
22884
|
-
if (!orderingCheck.allowed) {
|
|
22885
|
-
return {
|
|
22886
|
-
task: null,
|
|
22887
|
-
wasExisting: false,
|
|
22888
|
-
error: orderingCheck.error
|
|
22889
|
-
};
|
|
22890
|
-
}
|
|
22891
|
-
}
|
|
22892
|
-
}
|
|
22893
|
-
const validation = await this.agentGate.validate(options.agent);
|
|
22894
|
-
if (!validation.valid) {
|
|
22895
|
-
return {
|
|
22896
|
-
task: null,
|
|
22897
|
-
wasExisting: false,
|
|
22898
|
-
error: validation.error
|
|
22899
|
-
};
|
|
22900
|
-
}
|
|
22901
|
-
const concurrencyKey = options.agent;
|
|
22902
|
-
try {
|
|
22903
|
-
await this.concurrencyManager.acquire(concurrencyKey);
|
|
22904
|
-
} catch (error45) {
|
|
22905
|
-
return {
|
|
22906
|
-
task: null,
|
|
22907
|
-
wasExisting: false,
|
|
22908
|
-
error: `Concurrency limit reached: ${error45 instanceof Error ? error45.message : "Unknown error"}`
|
|
22909
|
-
};
|
|
22910
|
-
}
|
|
22911
|
-
let sessionId;
|
|
22912
|
-
try {
|
|
22913
|
-
const sessionResult = await this.client.session.create({
|
|
22914
|
-
body: {
|
|
22915
|
-
title: `Background: ${options.description}`,
|
|
22916
|
-
parentID: options.parentSessionId
|
|
22917
|
-
}
|
|
22918
|
-
});
|
|
22919
|
-
if (!sessionResult.data?.id) {
|
|
22920
|
-
this.concurrencyManager.release(concurrencyKey);
|
|
22921
|
-
return {
|
|
22922
|
-
task: null,
|
|
22923
|
-
wasExisting: false,
|
|
22924
|
-
error: "Failed to create OpenCode session"
|
|
22925
|
-
};
|
|
22926
|
-
}
|
|
22927
|
-
sessionId = sessionResult.data.id;
|
|
22928
|
-
} catch (error45) {
|
|
22929
|
-
this.concurrencyManager.release(concurrencyKey);
|
|
22930
|
-
return {
|
|
22931
|
-
task: null,
|
|
22932
|
-
wasExisting: false,
|
|
22933
|
-
error: `Failed to create session: ${error45 instanceof Error ? error45.message : "Unknown error"}`
|
|
22934
|
-
};
|
|
22935
|
-
}
|
|
22936
|
-
const notifyParent = options.notifyParent ?? !options.sync;
|
|
22937
|
-
const task = this.store.create({
|
|
22938
|
-
agent: options.agent,
|
|
22939
|
-
description: options.description,
|
|
22940
|
-
sessionId,
|
|
22941
|
-
idempotencyKey: options.idempotencyKey,
|
|
22942
|
-
parentSessionId: options.parentSessionId,
|
|
22943
|
-
parentMessageId: options.parentMessageId,
|
|
22944
|
-
parentAgent: options.parentAgent,
|
|
22945
|
-
notifyParent,
|
|
22946
|
-
hiveFeature: options.hiveFeature,
|
|
22947
|
-
hiveTaskFolder: options.hiveTaskFolder,
|
|
22948
|
-
workdir: options.workdir
|
|
22949
|
-
});
|
|
22950
|
-
if (options.hiveFeature && options.hiveTaskFolder) {
|
|
22951
|
-
try {
|
|
22952
|
-
const attempt = options.attempt ?? 1;
|
|
22953
|
-
this.taskService.patchBackgroundFields(options.hiveFeature, options.hiveTaskFolder, {
|
|
22954
|
-
idempotencyKey: options.idempotencyKey,
|
|
22955
|
-
workerSession: {
|
|
22956
|
-
taskId: task.taskId,
|
|
22957
|
-
sessionId: task.sessionId,
|
|
22958
|
-
agent: task.agent,
|
|
22959
|
-
mode: "delegate",
|
|
22960
|
-
attempt
|
|
22961
|
-
}
|
|
22962
|
-
});
|
|
22963
|
-
} catch (error45) {
|
|
22964
|
-
console.warn(`[BackgroundManager] Failed to persist to .hive: ${error45 instanceof Error ? error45.message : "Unknown"}`);
|
|
22965
|
-
}
|
|
22966
|
-
}
|
|
22967
|
-
this.store.updateStatus(task.taskId, "running");
|
|
22968
|
-
this.poller.start();
|
|
22969
|
-
const normalizedVariant = options.variant?.trim() || undefined;
|
|
22970
|
-
this.client.session.prompt({
|
|
22971
|
-
path: { id: sessionId },
|
|
22972
|
-
body: {
|
|
22973
|
-
agent: options.agent,
|
|
22974
|
-
parts: [{ type: "text", text: options.prompt }],
|
|
22975
|
-
tools: {
|
|
22976
|
-
background_task: false,
|
|
22977
|
-
delegate: false,
|
|
22978
|
-
hive_background_task: false,
|
|
22979
|
-
hive_background_output: false,
|
|
22980
|
-
hive_background_cancel: false,
|
|
22981
|
-
task: false
|
|
22982
|
-
},
|
|
22983
|
-
...normalizedVariant !== undefined && { variant: normalizedVariant }
|
|
22984
|
-
}
|
|
22985
|
-
}).catch((error45) => {
|
|
22986
|
-
this.updateStatus(task.taskId, "error", { errorMessage: error45.message });
|
|
22987
|
-
this.concurrencyManager.release(concurrencyKey);
|
|
22988
|
-
this.poller.cleanupTask(task.taskId);
|
|
22989
|
-
});
|
|
22990
|
-
return {
|
|
22991
|
-
task: this.store.get(task.taskId),
|
|
22992
|
-
wasExisting: false
|
|
22993
|
-
};
|
|
22994
|
-
}
|
|
22995
|
-
checkHiveTaskOrdering(feature, taskFolder) {
|
|
22996
|
-
const taskStatus = this.taskService.getRawStatus(feature, taskFolder);
|
|
22997
|
-
if (taskStatus?.dependsOn !== undefined) {
|
|
22998
|
-
return this.checkDependencies(feature, taskFolder, taskStatus.dependsOn);
|
|
22999
|
-
}
|
|
23000
|
-
return this.checkNumericOrdering(feature, taskFolder);
|
|
23001
|
-
}
|
|
23002
|
-
checkDependencies(feature, taskFolder, dependsOn) {
|
|
23003
|
-
if (dependsOn.length === 0) {
|
|
23004
|
-
return { allowed: true };
|
|
23005
|
-
}
|
|
23006
|
-
const unmetDeps = [];
|
|
23007
|
-
for (const depFolder of dependsOn) {
|
|
23008
|
-
const depStatus = this.taskService.getRawStatus(feature, depFolder);
|
|
23009
|
-
if (!depStatus || depStatus.status !== "done") {
|
|
23010
|
-
unmetDeps.push({
|
|
23011
|
-
folder: depFolder,
|
|
23012
|
-
status: depStatus?.status ?? "unknown"
|
|
23013
|
-
});
|
|
23014
|
-
}
|
|
23015
|
-
}
|
|
23016
|
-
if (unmetDeps.length > 0) {
|
|
23017
|
-
const depList = unmetDeps.map((d) => `"${d.folder}" (${d.status})`).join(", ");
|
|
23018
|
-
return {
|
|
23019
|
-
allowed: false,
|
|
23020
|
-
error: `Dependency constraint: Task "${taskFolder}" cannot start - dependencies not done: ${depList}. ` + `Only tasks with status 'done' satisfy dependencies.`
|
|
23021
|
-
};
|
|
23022
|
-
}
|
|
23023
|
-
return { allowed: true };
|
|
23024
|
-
}
|
|
23025
|
-
checkNumericOrdering(feature, taskFolder) {
|
|
23026
|
-
const orderMatch = taskFolder.match(/^(\d+)-/);
|
|
23027
|
-
if (!orderMatch) {
|
|
23028
|
-
return { allowed: true };
|
|
23029
|
-
}
|
|
23030
|
-
const taskOrder = parseInt(orderMatch[1], 10);
|
|
23031
|
-
if (taskOrder <= 1) {
|
|
23032
|
-
return { allowed: true };
|
|
23033
|
-
}
|
|
23034
|
-
const activeTasks = this.store.list({
|
|
23035
|
-
hiveFeature: feature,
|
|
23036
|
-
status: ["spawned", "pending", "running"]
|
|
23037
|
-
});
|
|
23038
|
-
for (const activeTask of activeTasks) {
|
|
23039
|
-
if (!activeTask.hiveTaskFolder)
|
|
23040
|
-
continue;
|
|
23041
|
-
const activeOrderMatch = activeTask.hiveTaskFolder.match(/^(\d+)-/);
|
|
23042
|
-
if (!activeOrderMatch)
|
|
23043
|
-
continue;
|
|
23044
|
-
const activeOrder = parseInt(activeOrderMatch[1], 10);
|
|
23045
|
-
if (activeOrder < taskOrder) {
|
|
23046
|
-
return {
|
|
23047
|
-
allowed: false,
|
|
23048
|
-
error: `Sequential ordering enforced: Task "${taskFolder}" cannot start while earlier task "${activeTask.hiveTaskFolder}" is still ${activeTask.status}. ` + `Complete or cancel the earlier task first. ` + `(Hive default: sequential execution for safety)`
|
|
23049
|
-
};
|
|
23050
|
-
}
|
|
23051
|
-
}
|
|
23052
|
-
return { allowed: true };
|
|
23053
|
-
}
|
|
23054
|
-
getTask(taskId) {
|
|
23055
|
-
return this.store.get(taskId);
|
|
23056
|
-
}
|
|
23057
|
-
getTaskByIdempotencyKey(key) {
|
|
23058
|
-
return this.store.getByIdempotencyKey(key);
|
|
23059
|
-
}
|
|
23060
|
-
getTaskByHiveTask(feature, taskFolder) {
|
|
23061
|
-
return this.store.getByHiveTask(feature, taskFolder);
|
|
23062
|
-
}
|
|
23063
|
-
updateStatus(taskId, status, options) {
|
|
23064
|
-
const task = this.store.updateStatus(taskId, status, options);
|
|
23065
|
-
if (task.hiveFeature && task.hiveTaskFolder) {
|
|
23066
|
-
try {
|
|
23067
|
-
this.taskService.patchBackgroundFields(task.hiveFeature, task.hiveTaskFolder, {
|
|
23068
|
-
workerSession: {
|
|
23069
|
-
sessionId: task.sessionId,
|
|
23070
|
-
lastHeartbeatAt: new Date().toISOString()
|
|
23071
|
-
}
|
|
23072
|
-
});
|
|
23073
|
-
} catch {}
|
|
23074
|
-
}
|
|
23075
|
-
if (isTerminalStatus(task.status) && task.parentSessionId && task.notifyParent !== false) {
|
|
23076
|
-
this.notifyParentSession(task);
|
|
23077
|
-
}
|
|
23078
|
-
return task;
|
|
23079
|
-
}
|
|
23080
|
-
async notifyParentSession(task) {
|
|
23081
|
-
if (!task.parentSessionId)
|
|
23082
|
-
return;
|
|
23083
|
-
const statusLabel = task.status.toUpperCase();
|
|
23084
|
-
const errorLine = task.errorMessage ? `
|
|
23085
|
-
**Error:** ${task.errorMessage}` : "";
|
|
23086
|
-
const notification = `<system-reminder>
|
|
23087
|
-
[BACKGROUND TASK ${statusLabel}]
|
|
23088
|
-
|
|
23089
|
-
**ID:** \`${task.taskId}\`
|
|
23090
|
-
**Description:** ${task.description}
|
|
23091
|
-
**Agent:** ${task.agent}${errorLine}
|
|
23092
|
-
|
|
23093
|
-
Use \`hive_background_output({ task_id: "${task.taskId}" })\` to retrieve the result.
|
|
23094
|
-
</system-reminder>`;
|
|
23095
|
-
try {
|
|
23096
|
-
await this.client.session.prompt({
|
|
23097
|
-
path: { id: task.parentSessionId },
|
|
23098
|
-
body: {
|
|
23099
|
-
agent: task.parentAgent || "hive",
|
|
23100
|
-
parts: [{ type: "text", text: notification }]
|
|
23101
|
-
}
|
|
23102
|
-
});
|
|
23103
|
-
} catch {}
|
|
23104
|
-
}
|
|
23105
|
-
async cancel(taskId) {
|
|
23106
|
-
const task = this.store.get(taskId);
|
|
23107
|
-
if (!task) {
|
|
23108
|
-
throw new Error(`Task "${taskId}" not found`);
|
|
23109
|
-
}
|
|
23110
|
-
if (isTerminalStatus(task.status)) {
|
|
23111
|
-
throw new Error(`Cannot cancel task in terminal status: ${task.status}`);
|
|
23112
|
-
}
|
|
23113
|
-
try {
|
|
23114
|
-
await this.client.session.abort({
|
|
23115
|
-
path: { id: task.sessionId }
|
|
23116
|
-
});
|
|
23117
|
-
} catch {}
|
|
23118
|
-
this.concurrencyManager.release(task.agent);
|
|
23119
|
-
this.poller.cleanupTask(taskId);
|
|
23120
|
-
return this.updateStatus(taskId, "cancelled");
|
|
23121
|
-
}
|
|
23122
|
-
async cancelAll(parentSessionId) {
|
|
23123
|
-
const tasks = this.store.list({
|
|
23124
|
-
parentSessionId,
|
|
23125
|
-
status: ["spawned", "pending", "running"]
|
|
23126
|
-
});
|
|
23127
|
-
const results = [];
|
|
23128
|
-
for (const task of tasks) {
|
|
23129
|
-
try {
|
|
23130
|
-
const cancelled = await this.cancel(task.taskId);
|
|
23131
|
-
results.push(cancelled);
|
|
23132
|
-
} catch {}
|
|
23133
|
-
}
|
|
23134
|
-
return results;
|
|
23135
|
-
}
|
|
23136
|
-
list(filter) {
|
|
23137
|
-
return this.store.list(filter);
|
|
23138
|
-
}
|
|
23139
|
-
getActive() {
|
|
23140
|
-
return this.store.getActive();
|
|
23141
|
-
}
|
|
23142
|
-
handleSessionIdle(sessionId) {
|
|
23143
|
-
const tasks = this.store.list();
|
|
23144
|
-
const task = tasks.find((t) => t.sessionId === sessionId);
|
|
23145
|
-
if (task && task.status === "running") {
|
|
23146
|
-
this.concurrencyManager.release(task.agent);
|
|
23147
|
-
this.poller.cleanupTask(task.taskId);
|
|
23148
|
-
this.updateStatus(task.taskId, "completed");
|
|
23149
|
-
}
|
|
23150
|
-
}
|
|
23151
|
-
handleMessageEvent(sessionId, messageText) {
|
|
23152
|
-
const tasks = this.store.list();
|
|
23153
|
-
const task = tasks.find((t) => t.sessionId === sessionId);
|
|
23154
|
-
if (task && task.status === "running") {
|
|
23155
|
-
this.store.updateProgress(task.taskId, {
|
|
23156
|
-
lastMessage: messageText?.slice(0, 200),
|
|
23157
|
-
lastMessageAt: new Date().toISOString(),
|
|
23158
|
-
messageCount: (task.progress?.messageCount ?? 0) + 1
|
|
23159
|
-
});
|
|
23160
|
-
}
|
|
23161
|
-
}
|
|
23162
|
-
getAgentGate() {
|
|
23163
|
-
return this.agentGate;
|
|
23164
|
-
}
|
|
23165
|
-
getConcurrencyManager() {
|
|
23166
|
-
return this.concurrencyManager;
|
|
23167
|
-
}
|
|
23168
|
-
getPoller() {
|
|
23169
|
-
return this.poller;
|
|
23170
|
-
}
|
|
23171
|
-
getObservations() {
|
|
23172
|
-
return this.poller.getObservations();
|
|
23173
|
-
}
|
|
23174
|
-
getTaskObservation(taskId) {
|
|
23175
|
-
return this.poller.getTaskObservation(taskId);
|
|
23176
|
-
}
|
|
23177
|
-
getCounts() {
|
|
23178
|
-
return this.store.countByStatus();
|
|
23179
|
-
}
|
|
23180
|
-
shutdown() {
|
|
23181
|
-
this.poller.stop();
|
|
23182
|
-
this.concurrencyManager.clear();
|
|
23183
|
-
}
|
|
23184
|
-
}
|
|
23185
|
-
function createBackgroundManager(options) {
|
|
23186
|
-
return new BackgroundManager(options);
|
|
23187
|
-
}
|
|
23188
|
-
// src/utils/format.ts
|
|
23189
|
-
function formatElapsed(ms) {
|
|
23190
|
-
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
|
|
23191
|
-
if (totalSeconds < 60) {
|
|
23192
|
-
return `${totalSeconds}s`;
|
|
23193
|
-
}
|
|
23194
|
-
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
23195
|
-
const seconds = totalSeconds % 60;
|
|
23196
|
-
if (totalMinutes < 60) {
|
|
23197
|
-
return `${totalMinutes}m ${seconds}s`;
|
|
23198
|
-
}
|
|
23199
|
-
const hours = Math.floor(totalMinutes / 60);
|
|
23200
|
-
const minutes = totalMinutes % 60;
|
|
23201
|
-
return `${hours}h ${minutes}m`;
|
|
23202
|
-
}
|
|
23203
|
-
function formatRelativeTime(isoDate) {
|
|
23204
|
-
const timestamp = new Date(isoDate).getTime();
|
|
23205
|
-
const now = Date.now();
|
|
23206
|
-
const elapsedMs = Math.max(0, now - timestamp);
|
|
23207
|
-
const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1000));
|
|
23208
|
-
if (totalSeconds < 60) {
|
|
23209
|
-
return `${totalSeconds}s ago`;
|
|
23210
|
-
}
|
|
23211
|
-
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
23212
|
-
if (totalMinutes < 60) {
|
|
23213
|
-
return `${totalMinutes}m ago`;
|
|
23214
|
-
}
|
|
23215
|
-
const totalHours = Math.floor(totalMinutes / 60);
|
|
23216
|
-
return `${totalHours}h ago`;
|
|
23217
|
-
}
|
|
23218
|
-
|
|
23219
|
-
// src/hooks/variant-hook.ts
|
|
23220
|
-
var HIVE_AGENT_NAMES = [
|
|
23221
|
-
"hive-master",
|
|
23222
|
-
"architect-planner",
|
|
23223
|
-
"swarm-orchestrator",
|
|
23224
|
-
"scout-researcher",
|
|
23225
|
-
"forager-worker",
|
|
23226
|
-
"hygienic-reviewer"
|
|
23227
|
-
];
|
|
23228
|
-
function isHiveAgent(agent) {
|
|
23229
|
-
return agent !== undefined && HIVE_AGENT_NAMES.includes(agent);
|
|
23230
|
-
}
|
|
23231
|
-
function normalizeVariant(variant) {
|
|
23232
|
-
if (variant === undefined)
|
|
23233
|
-
return;
|
|
23234
|
-
const trimmed2 = variant.trim();
|
|
23235
|
-
return trimmed2.length > 0 ? trimmed2 : undefined;
|
|
23236
|
-
}
|
|
23237
|
-
|
|
23238
|
-
// src/tools/background-tools.ts
|
|
23239
|
-
function createBackgroundTools(manager, client, configService) {
|
|
23240
|
-
async function maybeFinalizeIfIdle(sessionId) {
|
|
23241
|
-
try {
|
|
23242
|
-
const statusFn = client.session.status;
|
|
23243
|
-
if (statusFn) {
|
|
23244
|
-
const statusResult = await statusFn();
|
|
23245
|
-
const entry = statusResult.data?.[sessionId];
|
|
23246
|
-
const type = entry?.type;
|
|
23247
|
-
if (type === "idle" || type === "completed") {
|
|
23248
|
-
manager.handleSessionIdle(sessionId);
|
|
23249
|
-
return;
|
|
23250
|
-
}
|
|
23251
|
-
}
|
|
23252
|
-
} catch {}
|
|
23253
|
-
try {
|
|
23254
|
-
const sessionResult = await client.session.get({ path: { id: sessionId } });
|
|
23255
|
-
const data = sessionResult.data;
|
|
23256
|
-
const status = data?.status ?? data?.type;
|
|
23257
|
-
if (status === "idle" || status === "completed") {
|
|
23258
|
-
manager.handleSessionIdle(sessionId);
|
|
23259
|
-
}
|
|
23260
|
-
} catch {}
|
|
23261
|
-
}
|
|
23262
|
-
return {
|
|
23263
|
-
hive_background_task: tool({
|
|
23264
|
-
description: "Spawn a background agent task. Use sync=true to wait for completion (returns output). If sync=false (default), the parent session receives a completion <system-reminder> and you can call hive_background_output to fetch the result.",
|
|
23265
|
-
args: {
|
|
23266
|
-
agent: tool.schema.string().describe('Agent to use (e.g., "forager-worker", "scout-researcher")'),
|
|
23267
|
-
prompt: tool.schema.string().optional().describe("Task instructions/prompt (required if promptFile not provided)"),
|
|
23268
|
-
promptFile: tool.schema.string().optional().describe("Path to file containing prompt (alternative to inline prompt)"),
|
|
23269
|
-
description: tool.schema.string().describe("Human-readable task description"),
|
|
23270
|
-
sync: tool.schema.boolean().optional().describe("Wait for completion (default: false)"),
|
|
23271
|
-
idempotencyKey: tool.schema.string().optional().describe("Key for safe retries"),
|
|
23272
|
-
workdir: tool.schema.string().optional().describe("Working directory for task"),
|
|
23273
|
-
feature: tool.schema.string().optional().describe("Hive feature name (for Hive-linked tasks)"),
|
|
23274
|
-
task: tool.schema.string().optional().describe("Hive task folder (for Hive-linked tasks)"),
|
|
23275
|
-
attempt: tool.schema.number().optional().describe("Hive attempt number (for Hive-linked tasks)")
|
|
23276
|
-
},
|
|
23277
|
-
async execute({
|
|
23278
|
-
agent,
|
|
23279
|
-
prompt,
|
|
23280
|
-
promptFile,
|
|
23281
|
-
description,
|
|
23282
|
-
sync = false,
|
|
23283
|
-
idempotencyKey,
|
|
23284
|
-
workdir,
|
|
23285
|
-
feature,
|
|
23286
|
-
task: hiveTask,
|
|
23287
|
-
attempt
|
|
23288
|
-
}, toolContext) {
|
|
23289
|
-
const ctx = toolContext;
|
|
23290
|
-
const ALLOWED_CALLERS = new Set([
|
|
23291
|
-
"hive-master",
|
|
23292
|
-
"architect-planner",
|
|
23293
|
-
"swarm-orchestrator"
|
|
23294
|
-
]);
|
|
23295
|
-
const callerAgent = ctx?.agent;
|
|
23296
|
-
if (!callerAgent || !ALLOWED_CALLERS.has(callerAgent)) {
|
|
23297
|
-
const output = {
|
|
23298
|
-
provider: "hive",
|
|
23299
|
-
task_id: "",
|
|
23300
|
-
session_id: "",
|
|
23301
|
-
status: "error",
|
|
23302
|
-
error: `Agent "${callerAgent ?? "unknown"}" is not allowed to spawn background tasks. Only orchestrator agents (${[...ALLOWED_CALLERS].join(", ")}) can delegate.`
|
|
23303
|
-
};
|
|
23304
|
-
return JSON.stringify(output, null, 2);
|
|
23305
|
-
}
|
|
23306
|
-
let resolvedPrompt = prompt;
|
|
23307
|
-
if (promptFile) {
|
|
23308
|
-
const baseDir = workdir || process.cwd();
|
|
23309
|
-
const workspaceRoot = findWorkspaceRoot(baseDir) ?? baseDir;
|
|
23310
|
-
const fileResult = await resolvePromptFromFile(promptFile, workspaceRoot);
|
|
23311
|
-
if (fileResult.error) {
|
|
23312
|
-
const output = {
|
|
23313
|
-
provider: "hive",
|
|
23314
|
-
task_id: "",
|
|
23315
|
-
session_id: "",
|
|
23316
|
-
status: "error",
|
|
23317
|
-
error: `Failed to read prompt file: ${fileResult.error}`
|
|
23318
|
-
};
|
|
23319
|
-
return JSON.stringify(output, null, 2);
|
|
23320
|
-
}
|
|
23321
|
-
resolvedPrompt = fileResult.content;
|
|
23322
|
-
}
|
|
23323
|
-
if (!resolvedPrompt) {
|
|
23324
|
-
const output = {
|
|
23325
|
-
provider: "hive",
|
|
23326
|
-
task_id: "",
|
|
23327
|
-
session_id: "",
|
|
23328
|
-
status: "error",
|
|
23329
|
-
error: "Either prompt or promptFile is required"
|
|
23330
|
-
};
|
|
23331
|
-
return JSON.stringify(output, null, 2);
|
|
23332
|
-
}
|
|
23333
|
-
let variant;
|
|
23334
|
-
if (configService && isHiveAgent(agent)) {
|
|
23335
|
-
const agentConfig = configService.getAgentConfig(agent);
|
|
23336
|
-
variant = normalizeVariant(agentConfig.variant);
|
|
23337
|
-
}
|
|
23338
|
-
const result = await manager.spawn({
|
|
23339
|
-
agent,
|
|
23340
|
-
prompt: resolvedPrompt,
|
|
23341
|
-
description,
|
|
23342
|
-
idempotencyKey,
|
|
23343
|
-
workdir,
|
|
23344
|
-
parentSessionId: ctx?.sessionID,
|
|
23345
|
-
parentMessageId: ctx?.messageID,
|
|
23346
|
-
parentAgent: ctx?.agent,
|
|
23347
|
-
notifyParent: !sync,
|
|
23348
|
-
hiveFeature: feature,
|
|
23349
|
-
hiveTaskFolder: hiveTask,
|
|
23350
|
-
sync,
|
|
23351
|
-
attempt,
|
|
23352
|
-
variant
|
|
23353
|
-
});
|
|
23354
|
-
if (result.error) {
|
|
23355
|
-
const output = {
|
|
23356
|
-
provider: "hive",
|
|
23357
|
-
task_id: "",
|
|
23358
|
-
session_id: "",
|
|
23359
|
-
status: "error",
|
|
23360
|
-
error: result.error
|
|
23361
|
-
};
|
|
23362
|
-
return JSON.stringify(output, null, 2);
|
|
23363
|
-
}
|
|
23364
|
-
const taskRecord = result.task;
|
|
23365
|
-
if (!sync) {
|
|
23366
|
-
const output = {
|
|
23367
|
-
provider: "hive",
|
|
23368
|
-
task_id: taskRecord.taskId,
|
|
23369
|
-
session_id: taskRecord.sessionId,
|
|
23370
|
-
status: taskRecord.status
|
|
23371
|
-
};
|
|
23372
|
-
return JSON.stringify(output, null, 2);
|
|
23373
|
-
}
|
|
23374
|
-
const pollInterval = 1000;
|
|
23375
|
-
const maxWait = 30 * 60 * 1000;
|
|
23376
|
-
const startTime = Date.now();
|
|
23377
|
-
while (true) {
|
|
23378
|
-
let current = manager.getTask(taskRecord.taskId);
|
|
23379
|
-
if (!current) {
|
|
23380
|
-
return JSON.stringify({
|
|
23381
|
-
provider: "hive",
|
|
23382
|
-
task_id: taskRecord.taskId,
|
|
23383
|
-
session_id: taskRecord.sessionId,
|
|
23384
|
-
status: "error",
|
|
23385
|
-
error: "Task disappeared from store"
|
|
23386
|
-
}, null, 2);
|
|
23387
|
-
}
|
|
23388
|
-
if (!isTerminalStatus(current.status)) {
|
|
23389
|
-
await maybeFinalizeIfIdle(current.sessionId);
|
|
23390
|
-
current = manager.getTask(taskRecord.taskId);
|
|
23391
|
-
}
|
|
23392
|
-
if (current && isTerminalStatus(current.status)) {
|
|
23393
|
-
const outputText = await getTaskOutput(client, current.sessionId);
|
|
23394
|
-
const output = {
|
|
23395
|
-
provider: "hive",
|
|
23396
|
-
task_id: current.taskId,
|
|
23397
|
-
session_id: current.sessionId,
|
|
23398
|
-
status: current.status,
|
|
23399
|
-
output: outputText,
|
|
23400
|
-
done: true
|
|
23401
|
-
};
|
|
23402
|
-
if (current.errorMessage) {
|
|
23403
|
-
output.error = current.errorMessage;
|
|
23404
|
-
}
|
|
23405
|
-
return JSON.stringify(output, null, 2);
|
|
23406
|
-
}
|
|
23407
|
-
if (Date.now() - startTime > maxWait) {
|
|
23408
|
-
return JSON.stringify({
|
|
23409
|
-
provider: "hive",
|
|
23410
|
-
task_id: current?.taskId ?? taskRecord.taskId,
|
|
23411
|
-
session_id: current?.sessionId ?? taskRecord.sessionId,
|
|
23412
|
-
status: current?.status ?? "unknown",
|
|
23413
|
-
error: "Sync wait timed out after 30 minutes",
|
|
23414
|
-
done: false
|
|
23415
|
-
}, null, 2);
|
|
23416
|
-
}
|
|
23417
|
-
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
23418
|
-
}
|
|
23419
|
-
}
|
|
23420
|
-
}),
|
|
23421
|
-
hive_background_output: tool({
|
|
23422
|
-
description: "Get output from a background task. For sync=false tasks, wait for the completion <system-reminder> and then call with block=false to fetch the result; use block=true only when you need interim output. When blocking, pick a timeout based on task complexity (typically 30-120s).",
|
|
23423
|
-
args: {
|
|
23424
|
-
task_id: tool.schema.string().describe("Task ID to get output from"),
|
|
23425
|
-
block: tool.schema.boolean().optional().describe("Block waiting for new output (default: false)"),
|
|
23426
|
-
timeout: tool.schema.number().optional().describe("Timeout in ms when blocking (default: 60000)"),
|
|
23427
|
-
cursor: tool.schema.string().optional().describe("Cursor for incremental output (message count)")
|
|
23428
|
-
},
|
|
23429
|
-
async execute({ task_id, block = false, timeout = 60000, cursor }) {
|
|
23430
|
-
const task = manager.getTask(task_id);
|
|
23431
|
-
if (!task) {
|
|
23432
|
-
return JSON.stringify({
|
|
23433
|
-
error: `Task "${task_id}" not found`,
|
|
23434
|
-
task_id
|
|
23435
|
-
}, null, 2);
|
|
23436
|
-
}
|
|
23437
|
-
const cursorCount = cursor ? parseInt(cursor, 10) : 0;
|
|
23438
|
-
if (block && !isTerminalStatus(task.status)) {
|
|
23439
|
-
const startTime = Date.now();
|
|
23440
|
-
const pollInterval = 1000;
|
|
23441
|
-
while (Date.now() - startTime < timeout) {
|
|
23442
|
-
const current2 = manager.getTask(task_id);
|
|
23443
|
-
if (!current2)
|
|
23444
|
-
break;
|
|
23445
|
-
const currentCount = current2.progress?.messageCount ?? 0;
|
|
23446
|
-
if (currentCount > cursorCount || isTerminalStatus(current2.status)) {
|
|
23447
|
-
break;
|
|
23448
|
-
}
|
|
23449
|
-
await maybeFinalizeIfIdle(current2.sessionId);
|
|
23450
|
-
const afterFinalize = manager.getTask(task_id);
|
|
23451
|
-
if (afterFinalize && isTerminalStatus(afterFinalize.status)) {
|
|
23452
|
-
break;
|
|
23453
|
-
}
|
|
23454
|
-
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
23455
|
-
}
|
|
23456
|
-
}
|
|
23457
|
-
const current = manager.getTask(task_id);
|
|
23458
|
-
if (!current) {
|
|
23459
|
-
return JSON.stringify({
|
|
23460
|
-
error: `Task "${task_id}" disappeared`,
|
|
23461
|
-
task_id
|
|
23462
|
-
}, null, 2);
|
|
23463
|
-
}
|
|
23464
|
-
if (!isTerminalStatus(current.status)) {
|
|
23465
|
-
await maybeFinalizeIfIdle(current.sessionId);
|
|
23466
|
-
}
|
|
23467
|
-
const finalized = manager.getTask(task_id);
|
|
23468
|
-
if (!finalized) {
|
|
23469
|
-
return JSON.stringify({
|
|
23470
|
-
error: `Task "${task_id}" disappeared`,
|
|
23471
|
-
task_id
|
|
23472
|
-
}, null, 2);
|
|
23473
|
-
}
|
|
23474
|
-
const output = await getTaskOutput(client, finalized.sessionId, cursorCount);
|
|
23475
|
-
const messageCount = finalized.progress?.messageCount ?? 0;
|
|
23476
|
-
const observationSnapshot = manager.getTaskObservation(task_id);
|
|
23477
|
-
const lastActivityAt = observationSnapshot?.lastActivityAt ?? null;
|
|
23478
|
-
const startedAt = finalized.startedAt ? new Date(finalized.startedAt).getTime() : Date.now();
|
|
23479
|
-
const elapsedMs = Date.now() - startedAt;
|
|
23480
|
-
const lastMessage = finalized.progress?.lastMessage;
|
|
23481
|
-
const lastMessagePreview = lastMessage ? `${lastMessage.slice(0, 200)}${lastMessage.length > 200 ? "..." : ""}` : null;
|
|
23482
|
-
const observation = {
|
|
23483
|
-
elapsedMs,
|
|
23484
|
-
elapsedFormatted: formatElapsed(elapsedMs),
|
|
23485
|
-
messageCount: observationSnapshot?.messageCount ?? 0,
|
|
23486
|
-
lastActivityAgo: lastActivityAt ? formatRelativeTime(lastActivityAt) : "never",
|
|
23487
|
-
lastActivityAt,
|
|
23488
|
-
lastMessagePreview,
|
|
23489
|
-
maybeStuck: observationSnapshot?.maybeStuck ?? false
|
|
23490
|
-
};
|
|
23491
|
-
return JSON.stringify({
|
|
23492
|
-
task_id: finalized.taskId,
|
|
23493
|
-
session_id: finalized.sessionId,
|
|
23494
|
-
status: finalized.status,
|
|
23495
|
-
done: isTerminalStatus(finalized.status),
|
|
23496
|
-
output,
|
|
23497
|
-
cursor: messageCount.toString(),
|
|
23498
|
-
progress: finalized.progress,
|
|
23499
|
-
observation
|
|
23500
|
-
}, null, 2);
|
|
23501
|
-
}
|
|
23502
|
-
}),
|
|
23503
|
-
hive_background_cancel: tool({
|
|
23504
|
-
description: "Cancel running background task(s). Use all=true to cancel all tasks for current session.",
|
|
23505
|
-
args: {
|
|
23506
|
-
task_id: tool.schema.string().optional().describe("Specific task ID to cancel"),
|
|
23507
|
-
idempotencyKey: tool.schema.string().optional().describe("Cancel task by idempotency key"),
|
|
23508
|
-
all: tool.schema.boolean().optional().describe("Cancel all tasks for current session")
|
|
23509
|
-
},
|
|
23510
|
-
async execute({ task_id, idempotencyKey, all }, toolContext) {
|
|
23511
|
-
const ctx = toolContext;
|
|
23512
|
-
if (all) {
|
|
23513
|
-
if (!ctx?.sessionID) {
|
|
23514
|
-
return JSON.stringify({
|
|
23515
|
-
error: "Cannot cancel all: no parent session context",
|
|
23516
|
-
cancelled: 0
|
|
23517
|
-
}, null, 2);
|
|
23518
|
-
}
|
|
23519
|
-
const cancelled2 = await manager.cancelAll(ctx.sessionID);
|
|
23520
|
-
return JSON.stringify({
|
|
23521
|
-
cancelled: cancelled2.length,
|
|
23522
|
-
tasks: cancelled2.map((t) => ({
|
|
23523
|
-
task_id: t.taskId,
|
|
23524
|
-
status: t.status
|
|
23525
|
-
}))
|
|
23526
|
-
}, null, 2);
|
|
23527
|
-
}
|
|
23528
|
-
let task;
|
|
23529
|
-
if (task_id) {
|
|
23530
|
-
task = manager.getTask(task_id);
|
|
23531
|
-
} else if (idempotencyKey) {
|
|
23532
|
-
task = manager.getTaskByIdempotencyKey(idempotencyKey);
|
|
23533
|
-
}
|
|
23534
|
-
if (!task) {
|
|
23535
|
-
return JSON.stringify({
|
|
23536
|
-
error: task_id ? `Task "${task_id}" not found` : idempotencyKey ? `No task found with idempotency key "${idempotencyKey}"` : "Must provide task_id, idempotencyKey, or all=true"
|
|
23537
|
-
}, null, 2);
|
|
23538
|
-
}
|
|
23539
|
-
if (isTerminalStatus(task.status)) {
|
|
23540
|
-
return JSON.stringify({
|
|
23541
|
-
task_id: task.taskId,
|
|
23542
|
-
status: task.status,
|
|
23543
|
-
message: `Task already in terminal status: ${task.status}`
|
|
23544
|
-
}, null, 2);
|
|
23545
|
-
}
|
|
23546
|
-
const cancelled = await manager.cancel(task.taskId);
|
|
23547
|
-
return JSON.stringify({
|
|
23548
|
-
task_id: cancelled.taskId,
|
|
23549
|
-
status: cancelled.status,
|
|
23550
|
-
message: "Task cancelled successfully"
|
|
23551
|
-
}, null, 2);
|
|
23552
|
-
}
|
|
23553
|
-
})
|
|
23554
|
-
};
|
|
23555
|
-
}
|
|
23556
|
-
async function getTaskOutput(client, sessionId, afterCount = 0) {
|
|
23557
|
-
try {
|
|
23558
|
-
const messagesResult = await client.session.messages({
|
|
23559
|
-
path: { id: sessionId }
|
|
23560
|
-
});
|
|
23561
|
-
const messages = messagesResult.data ?? [];
|
|
23562
|
-
const newMessages = messages.slice(afterCount);
|
|
23563
|
-
const outputParts = [];
|
|
23564
|
-
for (const msg of newMessages) {
|
|
23565
|
-
const message = msg;
|
|
23566
|
-
if (!message.parts)
|
|
23567
|
-
continue;
|
|
23568
|
-
for (const part of message.parts) {
|
|
23569
|
-
if (part.type === "text" && part.text) {
|
|
23570
|
-
outputParts.push(part.text);
|
|
23571
|
-
} else if (part.type === "tool-result" && part.result) {
|
|
23572
|
-
const result = typeof part.result === "string" ? part.result : JSON.stringify(part.result);
|
|
23573
|
-
if (result.length > 500) {
|
|
23574
|
-
outputParts.push(`[Tool: ${part.name}] ${result.slice(0, 500)}...`);
|
|
23575
|
-
} else {
|
|
23576
|
-
outputParts.push(`[Tool: ${part.name}] ${result}`);
|
|
23577
|
-
}
|
|
23578
|
-
}
|
|
23579
|
-
}
|
|
23580
|
-
}
|
|
23581
|
-
return outputParts.join(`
|
|
23582
|
-
|
|
23583
|
-
`);
|
|
23584
|
-
} catch (error45) {
|
|
23585
|
-
return `[Error fetching output: ${error45 instanceof Error ? error45.message : "Unknown"}]`;
|
|
23586
|
-
}
|
|
23587
|
-
}
|
|
23588
|
-
|
|
23589
|
-
// src/index.ts
|
|
23590
|
-
function formatSkillsXml(skills) {
|
|
23591
|
-
if (skills.length === 0)
|
|
23592
|
-
return "";
|
|
23593
|
-
const skillsXml = skills.map((skill) => {
|
|
23594
|
-
return [
|
|
23595
|
-
" <skill>",
|
|
23596
|
-
` <name>${skill.name}</name>`,
|
|
23597
|
-
` <description>(hive - Skill) ${skill.description}</description>`,
|
|
23598
|
-
" </skill>"
|
|
23599
|
-
].join(`
|
|
23600
|
-
`);
|
|
23601
|
-
}).join(`
|
|
23602
|
-
`);
|
|
23603
|
-
return `
|
|
23604
|
-
|
|
23605
|
-
<available_skills>
|
|
23606
|
-
${skillsXml}
|
|
23607
|
-
</available_skills>`;
|
|
23608
|
-
}
|
|
23609
|
-
async function buildAutoLoadedSkillsContent(agentName, configService, projectRoot) {
|
|
23610
|
-
const agentConfig = configService.getAgentConfig(agentName);
|
|
23611
|
-
const autoLoadSkills = agentConfig.autoLoadSkills ?? [];
|
|
23612
|
-
if (autoLoadSkills.length === 0) {
|
|
23613
|
-
return "";
|
|
23614
|
-
}
|
|
23615
|
-
const homeDir = process.env.HOME || os.homedir();
|
|
23616
|
-
const skillTemplates = [];
|
|
23617
|
-
for (const skillId of autoLoadSkills) {
|
|
23618
|
-
const builtinSkill = BUILTIN_SKILLS.find((entry) => entry.name === skillId);
|
|
23619
|
-
if (builtinSkill) {
|
|
23620
|
-
skillTemplates.push(builtinSkill.template);
|
|
23621
|
-
continue;
|
|
23622
|
-
}
|
|
23623
|
-
const fileResult = await loadFileSkill(skillId, projectRoot, homeDir);
|
|
23624
|
-
if (fileResult.found && fileResult.skill) {
|
|
23625
|
-
skillTemplates.push(fileResult.skill.template);
|
|
23626
|
-
continue;
|
|
23627
|
-
}
|
|
23628
|
-
console.warn(`[hive] Unknown skill id "${skillId}" for agent "${agentName}"`);
|
|
23629
|
-
}
|
|
23630
|
-
if (skillTemplates.length === 0) {
|
|
23631
|
-
return "";
|
|
23632
|
-
}
|
|
23633
|
-
return `
|
|
23634
|
-
|
|
23635
|
-
` + skillTemplates.join(`
|
|
23636
|
-
|
|
23637
|
-
`);
|
|
23638
|
-
}
|
|
23639
|
-
function createHiveSkillTool(filteredSkills) {
|
|
23640
|
-
const base = `Load a Hive skill to get detailed instructions for a specific workflow.
|
|
22189
|
+
function createHiveSkillTool(filteredSkills) {
|
|
22190
|
+
const base = `Load a Hive skill to get detailed instructions for a specific workflow.
|
|
23641
22191
|
|
|
23642
22192
|
Use this when a task matches an available skill's description. The descriptions below ("Use when...", "Use before...") are triggers; when one applies, you MUST load that skill before proceeding.`;
|
|
23643
22193
|
const description = filteredSkills.length === 0 ? base + `
|
|
@@ -23676,16 +22226,15 @@ var HIVE_SYSTEM_PROMPT = `
|
|
|
23676
22226
|
|
|
23677
22227
|
Plan-first development: Write plan → User reviews → Approve → Execute tasks
|
|
23678
22228
|
|
|
23679
|
-
### Tools (
|
|
22229
|
+
### Tools (14 total)
|
|
23680
22230
|
|
|
23681
22231
|
| Domain | Tools |
|
|
23682
22232
|
|--------|-------|
|
|
23683
|
-
| Feature | hive_feature_create,
|
|
22233
|
+
| Feature | hive_feature_create, hive_feature_complete |
|
|
23684
22234
|
| Plan | hive_plan_write, hive_plan_read, hive_plan_approve |
|
|
23685
22235
|
| Task | hive_tasks_sync, hive_task_create, hive_task_update |
|
|
23686
|
-
|
|
|
23687
|
-
|
|
|
23688
|
-
| Merge | hive_merge, hive_worktree_list |
|
|
22236
|
+
| Worktree | hive_worktree_create, hive_worktree_commit, hive_worktree_discard |
|
|
22237
|
+
| Merge | hive_merge |
|
|
23689
22238
|
| Context | hive_context_write |
|
|
23690
22239
|
| Status | hive_status |
|
|
23691
22240
|
| Skill | hive_skill |
|
|
@@ -23697,42 +22246,40 @@ Plan-first development: Write plan → User reviews → Approve → Execute task
|
|
|
23697
22246
|
3. User adds comments in VSCode → \`hive_plan_read\` to see them
|
|
23698
22247
|
4. Revise plan → User approves
|
|
23699
22248
|
5. \`hive_tasks_sync()\` - Generate tasks from plan
|
|
23700
|
-
6. \`
|
|
22249
|
+
6. \`hive_worktree_create(task)\` → work in worktree → \`hive_worktree_commit(task, summary)\`
|
|
23701
22250
|
7. \`hive_merge(task)\` - Merge task branch into main (when ready)
|
|
23702
22251
|
|
|
23703
|
-
**Important:** \`
|
|
22252
|
+
**Important:** \`hive_worktree_commit\` commits changes to task branch but does NOT merge.
|
|
23704
22253
|
Use \`hive_merge\` to explicitly integrate changes. Worktrees persist until manually removed.
|
|
23705
22254
|
|
|
23706
22255
|
### Delegated Execution
|
|
23707
22256
|
|
|
23708
|
-
\`
|
|
22257
|
+
\`hive_worktree_create\` creates worktree and spawns worker automatically:
|
|
23709
22258
|
|
|
23710
|
-
1. \`
|
|
23711
|
-
2. Worker executes → calls \`
|
|
23712
|
-
3. Worker blocked → calls \`
|
|
22259
|
+
1. \`hive_worktree_create(task)\` → Creates worktree + spawns Forager (Worker/Coder) worker
|
|
22260
|
+
2. Worker executes → calls \`hive_worktree_commit(status: "completed")\`
|
|
22261
|
+
3. Worker blocked → calls \`hive_worktree_commit(status: "blocked", blocker: {...})\`
|
|
23713
22262
|
|
|
23714
22263
|
**Handling blocked workers:**
|
|
23715
|
-
1. Check blockers with \`
|
|
22264
|
+
1. Check blockers with \`hive_status()\`
|
|
23716
22265
|
2. Read the blocker info (reason, options, recommendation, context)
|
|
23717
22266
|
3. Ask user via \`question()\` tool - NEVER plain text
|
|
23718
|
-
4. Resume with \`
|
|
22267
|
+
4. Resume with \`hive_worktree_create(task, continueFrom: "blocked", decision: answer)\`
|
|
23719
22268
|
|
|
23720
22269
|
**CRITICAL**: When resuming, a NEW worker spawns in the SAME worktree.
|
|
23721
22270
|
The previous worker's progress is preserved. Include the user's decision in the \`decision\` parameter.
|
|
23722
22271
|
|
|
23723
|
-
**
|
|
23724
|
-
-
|
|
23725
|
-
-
|
|
23726
|
-
-
|
|
23727
|
-
- If you suspect notifications did not deliver, do a single \`hive_worker_status()\` check first
|
|
23728
|
-
- If you need final results, call \`hive_background_output({ task_id, block: false })\` after the completion notice
|
|
22272
|
+
**After task() Returns:**
|
|
22273
|
+
- task() is BLOCKING — when it returns, the worker is DONE
|
|
22274
|
+
- Call \`hive_status()\` immediately to check the new task state and find next runnable tasks
|
|
22275
|
+
- No notifications or polling needed — the result is already available
|
|
23729
22276
|
|
|
23730
22277
|
**For research**, use MCP tools or parallel exploration:
|
|
23731
22278
|
- \`grep_app_searchGitHub\` - Find code in OSS
|
|
23732
22279
|
- \`context7_query-docs\` - Library documentation
|
|
23733
22280
|
- \`websearch_web_search_exa\` - Web search via Exa
|
|
23734
22281
|
- \`ast_grep_search\` - AST-based search
|
|
23735
|
-
- For exploratory fan-out, load \`hive_skill("parallel-exploration")\` and use \`
|
|
22282
|
+
- For exploratory fan-out, load \`hive_skill("parallel-exploration")\` and use multiple \`task()\` calls in the same message
|
|
23736
22283
|
|
|
23737
22284
|
### Planning Phase - Context Management REQUIRED
|
|
23738
22285
|
|
|
@@ -23743,7 +22290,6 @@ As you research and plan, CONTINUOUSLY save findings using \`hive_context_write\
|
|
|
23743
22290
|
- Architecture decisions ("auth lives in /lib/auth")
|
|
23744
22291
|
|
|
23745
22292
|
**Update existing context files** when new info emerges - dont create duplicates.
|
|
23746
|
-
Workers depend on context for background. Without it, they work blind.
|
|
23747
22293
|
|
|
23748
22294
|
\`hive_tasks_sync\` parses \`### N. Task Name\` headers.
|
|
23749
22295
|
|
|
@@ -23770,13 +22316,6 @@ var plugin = async (ctx) => {
|
|
|
23770
22316
|
baseDir: directory,
|
|
23771
22317
|
hiveDir: path7.join(directory, ".hive")
|
|
23772
22318
|
});
|
|
23773
|
-
const backgroundManager = createBackgroundManager({
|
|
23774
|
-
client,
|
|
23775
|
-
projectRoot: directory
|
|
23776
|
-
});
|
|
23777
|
-
const delegateMode = configService.getDelegateMode();
|
|
23778
|
-
const useHiveBackground = delegateMode === "hive";
|
|
23779
|
-
const backgroundTools = createBackgroundTools(backgroundManager, client, configService);
|
|
23780
22319
|
const isOmoSlimEnabled = () => {
|
|
23781
22320
|
return configService.isOmoSlimEnabled();
|
|
23782
22321
|
};
|
|
@@ -23801,10 +22340,10 @@ var plugin = async (ctx) => {
|
|
|
23801
22340
|
}
|
|
23802
22341
|
};
|
|
23803
22342
|
const checkBlocked = (feature) => {
|
|
23804
|
-
const
|
|
22343
|
+
const fs9 = __require("fs");
|
|
23805
22344
|
const blockedPath = path7.join(directory, ".hive", "features", feature, "BLOCKED");
|
|
23806
|
-
if (
|
|
23807
|
-
const reason =
|
|
22345
|
+
if (fs9.existsSync(blockedPath)) {
|
|
22346
|
+
const reason = fs9.readFileSync(blockedPath, "utf-8").trim();
|
|
23808
22347
|
return `⛔ BLOCKED by Beekeeper
|
|
23809
22348
|
|
|
23810
22349
|
${reason || "(No reason provided)"}
|
|
@@ -23890,11 +22429,6 @@ To unblock: Remove .hive/features/${feature}/BLOCKED`;
|
|
|
23890
22429
|
mcp: builtinMcps,
|
|
23891
22430
|
tool: {
|
|
23892
22431
|
hive_skill: createHiveSkillTool(filteredSkills),
|
|
23893
|
-
...useHiveBackground && {
|
|
23894
|
-
hive_background_task: backgroundTools.hive_background_task,
|
|
23895
|
-
hive_background_output: backgroundTools.hive_background_output,
|
|
23896
|
-
hive_background_cancel: backgroundTools.hive_background_cancel
|
|
23897
|
-
},
|
|
23898
22432
|
hive_feature_create: tool({
|
|
23899
22433
|
description: "Create a new feature and set it as active",
|
|
23900
22434
|
args: {
|
|
@@ -23939,22 +22473,6 @@ These prevent scope creep and re-proposing rejected solutions.
|
|
|
23939
22473
|
NEXT: Ask your first clarifying question about this feature.`;
|
|
23940
22474
|
}
|
|
23941
22475
|
}),
|
|
23942
|
-
hive_feature_list: tool({
|
|
23943
|
-
description: "List all features",
|
|
23944
|
-
args: {},
|
|
23945
|
-
async execute() {
|
|
23946
|
-
const features = featureService.list();
|
|
23947
|
-
const active = resolveFeature();
|
|
23948
|
-
if (features.length === 0)
|
|
23949
|
-
return "No features found.";
|
|
23950
|
-
const list = features.map((f) => {
|
|
23951
|
-
const info = featureService.getInfo(f);
|
|
23952
|
-
return `${f === active ? "* " : " "}${f} (${info?.status || "unknown"})`;
|
|
23953
|
-
});
|
|
23954
|
-
return list.join(`
|
|
23955
|
-
`);
|
|
23956
|
-
}
|
|
23957
|
-
}),
|
|
23958
22476
|
hive_feature_complete: tool({
|
|
23959
22477
|
description: "Mark feature as completed (irreversible)",
|
|
23960
22478
|
args: { name: tool.schema.string().optional().describe("Feature name (defaults to active)") },
|
|
@@ -23966,34 +22484,6 @@ NEXT: Ask your first clarifying question about this feature.`;
|
|
|
23966
22484
|
return `Feature "${feature}" marked as completed`;
|
|
23967
22485
|
}
|
|
23968
22486
|
}),
|
|
23969
|
-
hive_journal_append: tool({
|
|
23970
|
-
description: "Append entry to .hive/journal.md for audit trail",
|
|
23971
|
-
args: {
|
|
23972
|
-
feature: tool.schema.string().describe("Feature name for context"),
|
|
23973
|
-
trouble: tool.schema.string().describe("What went wrong"),
|
|
23974
|
-
resolution: tool.schema.string().describe("How it was fixed"),
|
|
23975
|
-
constraint: tool.schema.string().optional().describe("Never/Always rule derived")
|
|
23976
|
-
},
|
|
23977
|
-
async execute({ feature, trouble, resolution, constraint }) {
|
|
23978
|
-
const journalPath = path7.join(directory, ".hive", "journal.md");
|
|
23979
|
-
if (!fs9.existsSync(journalPath)) {
|
|
23980
|
-
return `Error: journal.md not found. Create a feature first to initialize the journal.`;
|
|
23981
|
-
}
|
|
23982
|
-
const date5 = new Date().toISOString().split("T")[0];
|
|
23983
|
-
const entry = `
|
|
23984
|
-
### ${date5}: ${feature}
|
|
23985
|
-
|
|
23986
|
-
**Trouble**: ${trouble}
|
|
23987
|
-
**Resolution**: ${resolution}
|
|
23988
|
-
${constraint ? `**Constraint**: ${constraint}` : ""}
|
|
23989
|
-
**See**: .hive/features/${feature}/plan.md
|
|
23990
|
-
|
|
23991
|
-
---
|
|
23992
|
-
`;
|
|
23993
|
-
fs9.appendFileSync(journalPath, entry);
|
|
23994
|
-
return `Journal entry added for ${feature}. ${constraint ? `Constraint: "${constraint}"` : ""}`;
|
|
23995
|
-
}
|
|
23996
|
-
}),
|
|
23997
22487
|
hive_plan_write: tool({
|
|
23998
22488
|
description: "Write plan.md (clears existing comments)",
|
|
23999
22489
|
args: {
|
|
@@ -24087,7 +22577,7 @@ Add this section to your plan content and try again.`;
|
|
|
24087
22577
|
return "Error: No feature specified. Create a feature or provide feature param.";
|
|
24088
22578
|
const folder = taskService.create(feature, name, order);
|
|
24089
22579
|
return `Manual task created: ${folder}
|
|
24090
|
-
Reminder: start work with
|
|
22580
|
+
Reminder: start work with hive_worktree_create to use its worktree, and ensure any subagents work in that worktree too.`;
|
|
24091
22581
|
}
|
|
24092
22582
|
}),
|
|
24093
22583
|
hive_task_update: tool({
|
|
@@ -24109,7 +22599,7 @@ Reminder: start work with hive_exec_start to use its worktree, and ensure any su
|
|
|
24109
22599
|
return `Task "${task}" updated: status=${updated.status}`;
|
|
24110
22600
|
}
|
|
24111
22601
|
}),
|
|
24112
|
-
|
|
22602
|
+
hive_worktree_create: tool({
|
|
24113
22603
|
description: "Create worktree and begin work on task. Spawns Forager worker automatically.",
|
|
24114
22604
|
args: {
|
|
24115
22605
|
task: tool.schema.string().describe("Task folder name"),
|
|
@@ -24239,16 +22729,6 @@ Reminder: start work with hive_exec_start to use its worktree, and ensure any su
|
|
|
24239
22729
|
const relativePromptPath = normalizePath(path7.relative(directory, workerPromptPath));
|
|
24240
22730
|
const PREVIEW_MAX_LENGTH = 200;
|
|
24241
22731
|
const workerPromptPreview = workerPrompt.length > PREVIEW_MAX_LENGTH ? workerPrompt.slice(0, PREVIEW_MAX_LENGTH) + "..." : workerPrompt;
|
|
24242
|
-
const hiveBackgroundInstructions = `## Delegation Required
|
|
24243
|
-
|
|
24244
|
-
Call the hive_background_task tool to spawn a Forager (Worker/Coder) worker.
|
|
24245
|
-
|
|
24246
|
-
\`backgroundTaskCall\` contains the canonical tool arguments.
|
|
24247
|
-
|
|
24248
|
-
- Add \`sync: true\` if you need the result in this session.
|
|
24249
|
-
- Otherwise omit \`sync\`. Wait for the completion notification (no polling required). After the <system-reminder> arrives, call \`hive_background_output({ task_id: "<id>", block: false })\` once to fetch the final result.
|
|
24250
|
-
|
|
24251
|
-
Troubleshooting: if you see "Unknown parameter: workdir", your hive_background_task tool is not Hive's provider. Ensure agent-hive loads after other background_* tool providers, then re-run hive_exec_start.`;
|
|
24252
22732
|
const taskToolPrompt = `Follow instructions in @${relativePromptPath}`;
|
|
24253
22733
|
const taskToolInstructions = `## Delegation Required
|
|
24254
22734
|
|
|
@@ -24264,39 +22744,22 @@ task({
|
|
|
24264
22744
|
|
|
24265
22745
|
Use the \`@path\` attachment syntax in the prompt to reference the file. Do not inline the file contents.
|
|
24266
22746
|
|
|
24267
|
-
|
|
24268
|
-
const delegationInstructions = useHiveBackground ? hiveBackgroundInstructions : taskToolInstructions;
|
|
22747
|
+
`;
|
|
24269
22748
|
const responseBase = {
|
|
24270
22749
|
worktreePath: worktree.path,
|
|
24271
22750
|
branch: worktree.branch,
|
|
24272
22751
|
mode: "delegate",
|
|
24273
|
-
delegateMode,
|
|
24274
22752
|
agent,
|
|
24275
22753
|
delegationRequired: true,
|
|
24276
22754
|
workerPromptPath: relativePromptPath,
|
|
24277
22755
|
workerPromptPreview,
|
|
24278
|
-
|
|
24279
|
-
|
|
24280
|
-
|
|
24281
|
-
|
|
24282
|
-
|
|
24283
|
-
promptFile: workerPromptPath,
|
|
24284
|
-
description: `Hive: ${task}`,
|
|
24285
|
-
workdir: worktree.path,
|
|
24286
|
-
idempotencyKey,
|
|
24287
|
-
feature,
|
|
24288
|
-
task,
|
|
24289
|
-
attempt
|
|
24290
|
-
}
|
|
22756
|
+
taskPromptMode: "opencode-at-file",
|
|
22757
|
+
taskToolCall: {
|
|
22758
|
+
subagent_type: agent,
|
|
22759
|
+
description: `Hive: ${task}`,
|
|
22760
|
+
prompt: taskToolPrompt
|
|
24291
22761
|
},
|
|
24292
|
-
|
|
24293
|
-
taskToolCall: {
|
|
24294
|
-
subagent_type: agent,
|
|
24295
|
-
description: `Hive: ${task}`,
|
|
24296
|
-
prompt: taskToolPrompt
|
|
24297
|
-
}
|
|
24298
|
-
},
|
|
24299
|
-
instructions: delegationInstructions
|
|
22762
|
+
instructions: taskToolInstructions
|
|
24300
22763
|
};
|
|
24301
22764
|
const jsonPayload = JSON.stringify(responseBase, null, 2);
|
|
24302
22765
|
const payloadMeta = calculatePayloadMeta({
|
|
@@ -24330,7 +22793,7 @@ Note: delegateMode is set to 'task' in agent_hive.json. To use Hive's background
|
|
|
24330
22793
|
}, null, 2);
|
|
24331
22794
|
}
|
|
24332
22795
|
}),
|
|
24333
|
-
|
|
22796
|
+
hive_worktree_commit: tool({
|
|
24334
22797
|
description: "Complete task: commit changes to branch, write report. Supports blocked/failed/partial status for worker communication.",
|
|
24335
22798
|
args: {
|
|
24336
22799
|
task: tool.schema.string().describe("Task folder name"),
|
|
@@ -24383,7 +22846,7 @@ Re-run with updated summary showing verification results.`;
|
|
|
24383
22846
|
summary,
|
|
24384
22847
|
blocker,
|
|
24385
22848
|
worktreePath: worktree2?.path,
|
|
24386
|
-
message: 'Task blocked. Hive Master will ask user and resume with
|
|
22849
|
+
message: 'Task blocked. Hive Master will ask user and resume with hive_worktree_create(continueFrom: "blocked", decision: answer)'
|
|
24387
22850
|
}, null, 2);
|
|
24388
22851
|
}
|
|
24389
22852
|
const commitResult = await worktreeService.commitChanges(feature, task, `hive(${task}): ${summary.slice(0, 50)}`);
|
|
@@ -24425,7 +22888,7 @@ Re-run with updated summary showing verification results.`;
|
|
|
24425
22888
|
Use hive_merge to integrate changes. Worktree preserved at ${worktree?.path || "unknown"}.`;
|
|
24426
22889
|
}
|
|
24427
22890
|
}),
|
|
24428
|
-
|
|
22891
|
+
hive_worktree_discard: tool({
|
|
24429
22892
|
description: "Abort task: discard changes, reset status",
|
|
24430
22893
|
args: {
|
|
24431
22894
|
task: tool.schema.string().describe("Task folder name"),
|
|
@@ -24440,102 +22903,6 @@ Use hive_merge to integrate changes. Worktree preserved at ${worktree?.path || "
|
|
|
24440
22903
|
return `Task "${task}" aborted. Status reset to pending.`;
|
|
24441
22904
|
}
|
|
24442
22905
|
}),
|
|
24443
|
-
hive_worker_status: tool({
|
|
24444
|
-
description: "Check status of delegated workers. Shows running workers, blockers, and progress.",
|
|
24445
|
-
args: {
|
|
24446
|
-
task: tool.schema.string().optional().describe("Specific task to check, or omit for all"),
|
|
24447
|
-
feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
|
|
24448
|
-
},
|
|
24449
|
-
async execute({ task: specificTask, feature: explicitFeature }) {
|
|
24450
|
-
const feature = resolveFeature(explicitFeature);
|
|
24451
|
-
if (!feature)
|
|
24452
|
-
return "Error: No feature specified. Create a feature or provide feature param.";
|
|
24453
|
-
const STUCK_THRESHOLD = 10 * 60 * 1000;
|
|
24454
|
-
const HEARTBEAT_STALE_THRESHOLD = 5 * 60 * 1000;
|
|
24455
|
-
const now = Date.now();
|
|
24456
|
-
const PREVIEW_MAX_LENGTH = 200;
|
|
24457
|
-
const tasks = taskService.list(feature);
|
|
24458
|
-
const inProgressTasks = tasks.filter((t) => (t.status === "in_progress" || t.status === "blocked") && (!specificTask || t.folder === specificTask));
|
|
24459
|
-
if (inProgressTasks.length === 0) {
|
|
24460
|
-
return specificTask ? `No active worker for task "${specificTask}"` : "No active workers.";
|
|
24461
|
-
}
|
|
24462
|
-
const workers = await Promise.all(inProgressTasks.map(async (t) => {
|
|
24463
|
-
const worktree = await worktreeService.get(feature, t.folder);
|
|
24464
|
-
const rawStatus = taskService.getRawStatus(feature, t.folder);
|
|
24465
|
-
const workerSession = rawStatus?.workerSession;
|
|
24466
|
-
const backgroundRecord = backgroundManager.getTaskByHiveTask(feature, t.folder);
|
|
24467
|
-
const observation = backgroundRecord ? backgroundManager.getTaskObservation(backgroundRecord.taskId) : null;
|
|
24468
|
-
let maybeStuck = false;
|
|
24469
|
-
let lastActivityAt = null;
|
|
24470
|
-
if (observation?.lastActivityAt) {
|
|
24471
|
-
lastActivityAt = new Date(observation.lastActivityAt).getTime();
|
|
24472
|
-
} else if (workerSession?.lastHeartbeatAt) {
|
|
24473
|
-
lastActivityAt = new Date(workerSession.lastHeartbeatAt).getTime();
|
|
24474
|
-
const heartbeatStale = now - lastActivityAt > HEARTBEAT_STALE_THRESHOLD;
|
|
24475
|
-
const noRecentMessages = !workerSession.messageCount || workerSession.messageCount === 0;
|
|
24476
|
-
maybeStuck = heartbeatStale && t.status === "in_progress";
|
|
24477
|
-
} else if (rawStatus?.startedAt) {
|
|
24478
|
-
lastActivityAt = new Date(rawStatus.startedAt).getTime();
|
|
24479
|
-
maybeStuck = now - lastActivityAt > STUCK_THRESHOLD && t.status === "in_progress";
|
|
24480
|
-
}
|
|
24481
|
-
if (typeof observation?.maybeStuck === "boolean") {
|
|
24482
|
-
maybeStuck = observation.maybeStuck;
|
|
24483
|
-
}
|
|
24484
|
-
const startedAtIso = backgroundRecord?.startedAt || rawStatus?.startedAt || null;
|
|
24485
|
-
const startedAtMs = startedAtIso ? new Date(startedAtIso).getTime() : null;
|
|
24486
|
-
const elapsedMs = startedAtMs ? Math.max(0, now - startedAtMs) : 0;
|
|
24487
|
-
const lastActivityIso = observation?.lastActivityAt || workerSession?.lastHeartbeatAt || rawStatus?.startedAt || null;
|
|
24488
|
-
const lastActivityAgo = lastActivityIso ? formatRelativeTime(lastActivityIso) : "never";
|
|
24489
|
-
const messageCount = observation?.messageCount ?? backgroundRecord?.progress?.messageCount ?? workerSession?.messageCount ?? 0;
|
|
24490
|
-
const lastMessagePreview = backgroundRecord?.progress?.lastMessage ? backgroundRecord.progress.lastMessage.slice(0, PREVIEW_MAX_LENGTH) : null;
|
|
24491
|
-
return {
|
|
24492
|
-
task: t.folder,
|
|
24493
|
-
name: t.name,
|
|
24494
|
-
status: t.status,
|
|
24495
|
-
taskId: backgroundRecord?.taskId || workerSession?.taskId || null,
|
|
24496
|
-
description: backgroundRecord?.description || null,
|
|
24497
|
-
startedAt: startedAtIso,
|
|
24498
|
-
workerSession: workerSession || null,
|
|
24499
|
-
sessionId: workerSession?.sessionId || null,
|
|
24500
|
-
agent: workerSession?.agent || "inline",
|
|
24501
|
-
mode: workerSession?.mode || "inline",
|
|
24502
|
-
attempt: workerSession?.attempt || 1,
|
|
24503
|
-
messageCount: workerSession?.messageCount || 0,
|
|
24504
|
-
lastHeartbeatAt: workerSession?.lastHeartbeatAt || null,
|
|
24505
|
-
workerId: workerSession?.workerId || null,
|
|
24506
|
-
worktreePath: worktree?.path || null,
|
|
24507
|
-
branch: worktree?.branch || null,
|
|
24508
|
-
maybeStuck,
|
|
24509
|
-
activity: {
|
|
24510
|
-
elapsedMs,
|
|
24511
|
-
elapsedFormatted: formatElapsed(elapsedMs),
|
|
24512
|
-
messageCount,
|
|
24513
|
-
lastActivityAgo,
|
|
24514
|
-
lastMessagePreview,
|
|
24515
|
-
maybeStuck
|
|
24516
|
-
},
|
|
24517
|
-
blocker: rawStatus?.blocker || null,
|
|
24518
|
-
summary: t.summary || null
|
|
24519
|
-
};
|
|
24520
|
-
}));
|
|
24521
|
-
const stuckWorkers = workers.filter((worker) => worker.activity?.maybeStuck).length;
|
|
24522
|
-
const hint = workers.some((w) => w.status === "blocked") ? 'Use hive_exec_start(task, continueFrom: "blocked", decision: answer) to resume blocked workers' : workers.some((w) => w.maybeStuck) ? "Some workers may be stuck. Use hive_background_output({ task_id }) to check output, or abort with hive_exec_abort." : "Workers in progress. Wait for the completion notification (no polling required). Use hive_worker_status for spot checks; use hive_background_output only if interim output is explicitly needed.";
|
|
24523
|
-
const guidance = stuckWorkers > 0 ? `
|
|
24524
|
-
|
|
24525
|
-
⚠️ ${stuckWorkers} worker(s) may be stuck (no activity for 10+ minutes). Consider cancelling or investigating.` : "";
|
|
24526
|
-
return JSON.stringify({
|
|
24527
|
-
feature,
|
|
24528
|
-
delegateMode,
|
|
24529
|
-
omoSlimEnabled: isOmoSlimEnabled(),
|
|
24530
|
-
backgroundTaskProvider: useHiveBackground ? "hive" : "task",
|
|
24531
|
-
workers,
|
|
24532
|
-
summary: {
|
|
24533
|
-
stuckWorkers
|
|
24534
|
-
},
|
|
24535
|
-
hint: hint + guidance
|
|
24536
|
-
}, null, 2);
|
|
24537
|
-
}
|
|
24538
|
-
}),
|
|
24539
22906
|
hive_merge: tool({
|
|
24540
22907
|
description: "Merge completed task branch into current branch (explicit integration)",
|
|
24541
22908
|
args: {
|
|
@@ -24551,7 +22918,7 @@ Use hive_merge to integrate changes. Worktree preserved at ${worktree?.path || "
|
|
|
24551
22918
|
if (!taskInfo)
|
|
24552
22919
|
return `Error: Task "${task}" not found`;
|
|
24553
22920
|
if (taskInfo.status !== "done")
|
|
24554
|
-
return "Error: Task must be completed before merging. Use
|
|
22921
|
+
return "Error: Task must be completed before merging. Use hive_worktree_commit first.";
|
|
24555
22922
|
const result = await worktreeService.merge(feature, task, strategy);
|
|
24556
22923
|
if (!result.success) {
|
|
24557
22924
|
if (result.conflicts && result.conflicts.length > 0) {
|
|
@@ -24568,27 +22935,6 @@ Commit: ${result.sha}
|
|
|
24568
22935
|
Files changed: ${result.filesChanged?.length || 0}`;
|
|
24569
22936
|
}
|
|
24570
22937
|
}),
|
|
24571
|
-
hive_worktree_list: tool({
|
|
24572
|
-
description: "List all worktrees for current feature",
|
|
24573
|
-
args: {
|
|
24574
|
-
feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
|
|
24575
|
-
},
|
|
24576
|
-
async execute({ feature: explicitFeature }) {
|
|
24577
|
-
const feature = resolveFeature(explicitFeature);
|
|
24578
|
-
if (!feature)
|
|
24579
|
-
return "Error: No feature specified. Create a feature or provide feature param.";
|
|
24580
|
-
const worktrees = await worktreeService.list(feature);
|
|
24581
|
-
if (worktrees.length === 0)
|
|
24582
|
-
return "No worktrees found for this feature.";
|
|
24583
|
-
const lines = ["| Task | Branch | Has Changes |", "|------|--------|-------------|"];
|
|
24584
|
-
for (const wt of worktrees) {
|
|
24585
|
-
const hasChanges = await worktreeService.hasUncommittedChanges(wt.feature, wt.step);
|
|
24586
|
-
lines.push(`| ${wt.step} | ${wt.branch} | ${hasChanges ? "Yes" : "No"} |`);
|
|
24587
|
-
}
|
|
24588
|
-
return lines.join(`
|
|
24589
|
-
`);
|
|
24590
|
-
}
|
|
24591
|
-
}),
|
|
24592
22938
|
hive_context_write: tool({
|
|
24593
22939
|
description: "Write a context file for the feature. Context files store persistent notes, decisions, and reference material.",
|
|
24594
22940
|
args: {
|
|
@@ -24630,16 +22976,22 @@ Files changed: ${result.filesChanged?.length || 0}`;
|
|
|
24630
22976
|
const plan = planService.read(feature);
|
|
24631
22977
|
const tasks = taskService.list(feature);
|
|
24632
22978
|
const contextFiles = contextService.list(feature);
|
|
24633
|
-
const tasksSummary = tasks.map((t) => {
|
|
22979
|
+
const tasksSummary = await Promise.all(tasks.map(async (t) => {
|
|
24634
22980
|
const rawStatus = taskService.getRawStatus(feature, t.folder);
|
|
22981
|
+
const worktree = await worktreeService.get(feature, t.folder);
|
|
22982
|
+
const hasChanges = worktree ? await worktreeService.hasUncommittedChanges(worktree.feature, worktree.step) : null;
|
|
24635
22983
|
return {
|
|
24636
22984
|
folder: t.folder,
|
|
24637
22985
|
name: t.name,
|
|
24638
22986
|
status: t.status,
|
|
24639
22987
|
origin: t.origin || "plan",
|
|
24640
|
-
dependsOn: rawStatus?.dependsOn ?? null
|
|
22988
|
+
dependsOn: rawStatus?.dependsOn ?? null,
|
|
22989
|
+
worktree: worktree ? {
|
|
22990
|
+
branch: worktree.branch,
|
|
22991
|
+
hasChanges
|
|
22992
|
+
} : null
|
|
24641
22993
|
};
|
|
24642
|
-
});
|
|
22994
|
+
}));
|
|
24643
22995
|
const contextSummary = contextFiles.map((c) => ({
|
|
24644
22996
|
name: c.name,
|
|
24645
22997
|
chars: c.content.length,
|
|
@@ -24677,7 +23029,7 @@ Files changed: ${result.filesChanged?.length || 0}`;
|
|
|
24677
23029
|
return `${runnableTasks.length} tasks are ready to start in parallel: ${runnableTasks.join(", ")}`;
|
|
24678
23030
|
}
|
|
24679
23031
|
if (runnableTasks.length === 1) {
|
|
24680
|
-
return `Start next task with
|
|
23032
|
+
return `Start next task with hive_worktree_create: ${runnableTasks[0]}`;
|
|
24681
23033
|
}
|
|
24682
23034
|
const pending = tasks2.find((t) => t.status === "pending");
|
|
24683
23035
|
if (pending) {
|
|
@@ -24714,81 +23066,6 @@ Files changed: ${result.filesChanged?.length || 0}`;
|
|
|
24714
23066
|
nextAction: getNextAction(planStatus, tasksSummary, runnable)
|
|
24715
23067
|
});
|
|
24716
23068
|
}
|
|
24717
|
-
}),
|
|
24718
|
-
hive_request_review: tool({
|
|
24719
|
-
description: "Request human review of completed task. BLOCKS until human approves or requests changes. Call after completing work, before merging.",
|
|
24720
|
-
args: {
|
|
24721
|
-
task: tool.schema.string().describe("Task folder name"),
|
|
24722
|
-
summary: tool.schema.string().describe("Summary of what you did for human to review"),
|
|
24723
|
-
feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
|
|
24724
|
-
},
|
|
24725
|
-
async execute({ task, summary, feature: explicitFeature }) {
|
|
24726
|
-
const feature = resolveFeature(explicitFeature);
|
|
24727
|
-
if (!feature)
|
|
24728
|
-
return "Error: No feature specified.";
|
|
24729
|
-
const taskDir = path7.join(directory, ".hive", "features", feature, "tasks", task);
|
|
24730
|
-
if (!fs9.existsSync(taskDir)) {
|
|
24731
|
-
return `Error: Task '${task}' not found in feature '${feature}'`;
|
|
24732
|
-
}
|
|
24733
|
-
const reportPath = path7.join(taskDir, "report.md");
|
|
24734
|
-
const existingReport = fs9.existsSync(reportPath) ? fs9.readFileSync(reportPath, "utf-8") : `# Task Report
|
|
24735
|
-
`;
|
|
24736
|
-
const attemptCount = (existingReport.match(/## Attempt \d+/g) || []).length + 1;
|
|
24737
|
-
const timestamp = new Date().toISOString();
|
|
24738
|
-
const newContent = existingReport + `
|
|
24739
|
-
## Attempt ${attemptCount}
|
|
24740
|
-
|
|
24741
|
-
**Requested**: ${timestamp}
|
|
24742
|
-
|
|
24743
|
-
### Summary
|
|
24744
|
-
|
|
24745
|
-
${summary}
|
|
24746
|
-
|
|
24747
|
-
`;
|
|
24748
|
-
fs9.writeFileSync(reportPath, newContent);
|
|
24749
|
-
const pendingPath = path7.join(taskDir, "PENDING_REVIEW");
|
|
24750
|
-
fs9.writeFileSync(pendingPath, JSON.stringify({
|
|
24751
|
-
attempt: attemptCount,
|
|
24752
|
-
requestedAt: timestamp,
|
|
24753
|
-
summary: summary.substring(0, 200) + (summary.length > 200 ? "..." : "")
|
|
24754
|
-
}, null, 2));
|
|
24755
|
-
const pollInterval = 2000;
|
|
24756
|
-
const maxWait = 30 * 60 * 1000;
|
|
24757
|
-
const startTime = Date.now();
|
|
24758
|
-
while (fs9.existsSync(pendingPath)) {
|
|
24759
|
-
if (Date.now() - startTime > maxWait) {
|
|
24760
|
-
return "Review timed out after 30 minutes. Human did not respond.";
|
|
24761
|
-
}
|
|
24762
|
-
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
24763
|
-
}
|
|
24764
|
-
const resultPath = path7.join(taskDir, "REVIEW_RESULT");
|
|
24765
|
-
if (!fs9.existsSync(resultPath)) {
|
|
24766
|
-
return "Review cancelled (PENDING_REVIEW removed but no REVIEW_RESULT).";
|
|
24767
|
-
}
|
|
24768
|
-
const result = fs9.readFileSync(resultPath, "utf-8").trim();
|
|
24769
|
-
fs9.appendFileSync(reportPath, `### Review Result
|
|
24770
|
-
|
|
24771
|
-
${result}
|
|
24772
|
-
|
|
24773
|
-
---
|
|
24774
|
-
|
|
24775
|
-
`);
|
|
24776
|
-
if (result.toUpperCase() === "APPROVED") {
|
|
24777
|
-
return `✅ APPROVED
|
|
24778
|
-
|
|
24779
|
-
Your work has been approved. You may now merge:
|
|
24780
|
-
|
|
24781
|
-
hive_merge(task="${task}")
|
|
24782
|
-
|
|
24783
|
-
After merging, proceed to the next task.`;
|
|
24784
|
-
} else {
|
|
24785
|
-
return `\uD83D\uDD04 Changes Requested
|
|
24786
|
-
|
|
24787
|
-
${result}
|
|
24788
|
-
|
|
24789
|
-
Make the requested changes, then call hive_request_review again.`;
|
|
24790
|
-
}
|
|
24791
|
-
}
|
|
24792
23069
|
})
|
|
24793
23070
|
},
|
|
24794
23071
|
command: {
|
|
@@ -24802,27 +23079,13 @@ Make the requested changes, then call hive_request_review again.`;
|
|
|
24802
23079
|
}
|
|
24803
23080
|
}
|
|
24804
23081
|
},
|
|
24805
|
-
event: async ({ event }) => {
|
|
24806
|
-
if (event.type === "session.idle") {
|
|
24807
|
-
const sessionId = event.properties.sessionID;
|
|
24808
|
-
if (sessionId) {
|
|
24809
|
-
backgroundManager.handleSessionIdle(sessionId);
|
|
24810
|
-
}
|
|
24811
|
-
}
|
|
24812
|
-
if (event.type === "message.updated") {
|
|
24813
|
-
const info = event.properties.info;
|
|
24814
|
-
const sessionId = info?.sessionID;
|
|
24815
|
-
if (sessionId) {
|
|
24816
|
-
backgroundManager.handleMessageEvent(sessionId);
|
|
24817
|
-
}
|
|
24818
|
-
}
|
|
24819
|
-
},
|
|
24820
23082
|
config: async (opencodeConfig) => {
|
|
24821
23083
|
configService.init();
|
|
24822
23084
|
const hiveUserConfig = configService.getAgentConfig("hive-master");
|
|
24823
23085
|
const hiveAutoLoadedSkills = await buildAutoLoadedSkillsContent("hive-master", configService, directory);
|
|
24824
23086
|
const hiveConfig = {
|
|
24825
23087
|
model: hiveUserConfig.model,
|
|
23088
|
+
variant: hiveUserConfig.variant,
|
|
24826
23089
|
temperature: hiveUserConfig.temperature ?? 0.5,
|
|
24827
23090
|
description: "Hive (Hybrid) - Plans + orchestrates. Detects phase, loads skills on-demand.",
|
|
24828
23091
|
prompt: QUEEN_BEE_PROMPT + hiveAutoLoadedSkills,
|
|
@@ -24830,16 +23093,14 @@ Make the requested changes, then call hive_request_review again.`;
|
|
|
24830
23093
|
question: "allow",
|
|
24831
23094
|
skill: "allow",
|
|
24832
23095
|
todowrite: "allow",
|
|
24833
|
-
todoread: "allow"
|
|
24834
|
-
hive_background_task: "allow",
|
|
24835
|
-
hive_background_output: "allow",
|
|
24836
|
-
hive_background_cancel: "allow"
|
|
23096
|
+
todoread: "allow"
|
|
24837
23097
|
}
|
|
24838
23098
|
};
|
|
24839
23099
|
const architectUserConfig = configService.getAgentConfig("architect-planner");
|
|
24840
23100
|
const architectAutoLoadedSkills = await buildAutoLoadedSkillsContent("architect-planner", configService, directory);
|
|
24841
23101
|
const architectConfig = {
|
|
24842
23102
|
model: architectUserConfig.model,
|
|
23103
|
+
variant: architectUserConfig.variant,
|
|
24843
23104
|
temperature: architectUserConfig.temperature ?? 0.7,
|
|
24844
23105
|
description: "Architect (Planner) - Plans features, interviews, writes plans. NEVER executes.",
|
|
24845
23106
|
prompt: ARCHITECT_BEE_PROMPT + architectAutoLoadedSkills,
|
|
@@ -24850,16 +23111,14 @@ Make the requested changes, then call hive_request_review again.`;
|
|
|
24850
23111
|
skill: "allow",
|
|
24851
23112
|
todowrite: "allow",
|
|
24852
23113
|
todoread: "allow",
|
|
24853
|
-
webfetch: "allow"
|
|
24854
|
-
hive_background_task: "allow",
|
|
24855
|
-
hive_background_output: "allow",
|
|
24856
|
-
hive_background_cancel: "allow"
|
|
23114
|
+
webfetch: "allow"
|
|
24857
23115
|
}
|
|
24858
23116
|
};
|
|
24859
23117
|
const swarmUserConfig = configService.getAgentConfig("swarm-orchestrator");
|
|
24860
23118
|
const swarmAutoLoadedSkills = await buildAutoLoadedSkillsContent("swarm-orchestrator", configService, directory);
|
|
24861
23119
|
const swarmConfig = {
|
|
24862
23120
|
model: swarmUserConfig.model,
|
|
23121
|
+
variant: swarmUserConfig.variant,
|
|
24863
23122
|
temperature: swarmUserConfig.temperature ?? 0.5,
|
|
24864
23123
|
description: "Swarm (Orchestrator) - Orchestrates execution. Delegates, spawns workers, verifies, merges.",
|
|
24865
23124
|
prompt: SWARM_BEE_PROMPT + swarmAutoLoadedSkills,
|
|
@@ -24867,25 +23126,20 @@ Make the requested changes, then call hive_request_review again.`;
|
|
|
24867
23126
|
question: "allow",
|
|
24868
23127
|
skill: "allow",
|
|
24869
23128
|
todowrite: "allow",
|
|
24870
|
-
todoread: "allow"
|
|
24871
|
-
hive_background_task: "allow",
|
|
24872
|
-
hive_background_output: "allow",
|
|
24873
|
-
hive_background_cancel: "allow"
|
|
23129
|
+
todoread: "allow"
|
|
24874
23130
|
}
|
|
24875
23131
|
};
|
|
24876
23132
|
const scoutUserConfig = configService.getAgentConfig("scout-researcher");
|
|
24877
23133
|
const scoutAutoLoadedSkills = await buildAutoLoadedSkillsContent("scout-researcher", configService, directory);
|
|
24878
23134
|
const scoutConfig = {
|
|
24879
23135
|
model: scoutUserConfig.model,
|
|
23136
|
+
variant: scoutUserConfig.variant,
|
|
24880
23137
|
temperature: scoutUserConfig.temperature ?? 0.5,
|
|
24881
23138
|
mode: "subagent",
|
|
24882
23139
|
description: "Scout (Explorer/Researcher/Retrieval) - Researches codebase + external docs/data.",
|
|
24883
23140
|
prompt: SCOUT_BEE_PROMPT + scoutAutoLoadedSkills,
|
|
24884
23141
|
permission: {
|
|
24885
23142
|
edit: "deny",
|
|
24886
|
-
hive_background_task: "deny",
|
|
24887
|
-
hive_background_output: "deny",
|
|
24888
|
-
hive_background_cancel: "deny",
|
|
24889
23143
|
task: "deny",
|
|
24890
23144
|
delegate: "deny",
|
|
24891
23145
|
skill: "allow",
|
|
@@ -24896,14 +23150,12 @@ Make the requested changes, then call hive_request_review again.`;
|
|
|
24896
23150
|
const foragerAutoLoadedSkills = await buildAutoLoadedSkillsContent("forager-worker", configService, directory);
|
|
24897
23151
|
const foragerConfig = {
|
|
24898
23152
|
model: foragerUserConfig.model,
|
|
23153
|
+
variant: foragerUserConfig.variant,
|
|
24899
23154
|
temperature: foragerUserConfig.temperature ?? 0.3,
|
|
24900
23155
|
mode: "subagent",
|
|
24901
23156
|
description: "Forager (Worker/Coder) - Executes tasks directly in isolated worktrees. Never delegates.",
|
|
24902
23157
|
prompt: FORAGER_BEE_PROMPT + foragerAutoLoadedSkills,
|
|
24903
23158
|
permission: {
|
|
24904
|
-
hive_background_task: "deny",
|
|
24905
|
-
hive_background_output: "deny",
|
|
24906
|
-
hive_background_cancel: "deny",
|
|
24907
23159
|
task: "deny",
|
|
24908
23160
|
delegate: "deny",
|
|
24909
23161
|
skill: "allow"
|
|
@@ -24913,15 +23165,13 @@ Make the requested changes, then call hive_request_review again.`;
|
|
|
24913
23165
|
const hygienicAutoLoadedSkills = await buildAutoLoadedSkillsContent("hygienic-reviewer", configService, directory);
|
|
24914
23166
|
const hygienicConfig = {
|
|
24915
23167
|
model: hygienicUserConfig.model,
|
|
23168
|
+
variant: hygienicUserConfig.variant,
|
|
24916
23169
|
temperature: hygienicUserConfig.temperature ?? 0.3,
|
|
24917
23170
|
mode: "subagent",
|
|
24918
23171
|
description: "Hygienic (Consultant/Reviewer/Debugger) - Reviews plan documentation quality. OKAY/REJECT verdict.",
|
|
24919
23172
|
prompt: HYGIENIC_BEE_PROMPT + hygienicAutoLoadedSkills,
|
|
24920
23173
|
permission: {
|
|
24921
23174
|
edit: "deny",
|
|
24922
|
-
hive_background_task: "deny",
|
|
24923
|
-
hive_background_output: "deny",
|
|
24924
|
-
hive_background_cancel: "deny",
|
|
24925
23175
|
task: "deny",
|
|
24926
23176
|
delegate: "deny",
|
|
24927
23177
|
skill: "allow"
|