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/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
- hive_exec_start({ task: "01-fix-abort-tests" })
12679
- hive_exec_start({ task: "02-fix-batch-tests" })
12680
- hive_exec_start({ task: "03-fix-race-condition-tests" })
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 \`hive_exec_start()\`
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 hive_background_* or task() (Scout fan-out)",
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 \`hive_exec_start\`.
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 Background Tasks (Fan-Out)
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 hive_background_task implementation',
13045
- prompt: \`Where is hive_background_task implemented and registered?
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 \`agent: "scout-researcher"\` for read-only exploration
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 notified of completion, retrieve results:
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
- Cancel tasks that are no longer needed:
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 background task system work?"
13111
+ **Investigation:** "How does the API routing system work?"
13184
13112
 
13185
13113
  **Decomposition:**
13186
- 1. Implementation: Where is \`hive_background_task\` tool defined?
13187
- 2. Concurrency: How does task scheduling/queueing work?
13188
- 3. Notifications: How does parent session get notified?
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 hive_background_task implementation',
13224
- prompt: 'Where is hive_background_task implemented? Find tool definition and registration.',
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
- const result1 = await hive_background_task({ ..., sync: true })
13253
- const result2 = await hive_background_task({ ..., sync: true })
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 (task mode): Spawn all in the same assistant message
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
- - [ ] Received notifications for completed tasks (Path A)
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()\` or \`hive_feature_list()\` to detect phase:
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 \`hive_background_task(agent: "scout-researcher", sync: true, ...)\` for single investigations; use \`sync: false\` only for multi-scout fan-out.
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 → \`hive_background_task(agent: "scout-researcher", sync: true, ...)\` (blocks until complete, simpler flow)
14409
- - Parallel exploration → Load \`hive_skill("parallel-exploration")\` and follow the task vs hive mode delegation guidance.
14410
- - Implementation → \`hive_exec_start(task)\` (spawns Forager)
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
- In task mode, use task() for research fan-out; in hive mode, use hive_background_task.
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() or hive_background_task (Scout fan-out)
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 hive_background_task).
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
- hive_exec_start({ task: "01-task-name" }) // Creates worktree + Forager
14423
+ hive_worktree_create({ task: "01-task-name" }) // Creates worktree + Forager
14526
14424
  \`\`\`
14527
14425
 
14528
14426
  ### After Delegation
14529
14427
 
14530
- 1. Wait for the completion notification (no polling required)
14531
- 2. Use \`hive_worker_status()\` for spot checks or if you suspect notifications did not deliver
14532
- 3. Use \`hive_background_output\` only if interim output is explicitly needed, or after completion
14533
- 4. When calling \`hive_background_output\`, choose a timeout (30-120s) based on task size
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 \`hive_background_task(agent: "scout-researcher", sync: true, ...)\` for single investigations. |
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, default to synchronous exploration (\`sync: true\`). If async/parallel exploration would help, ask the user via \`question()\`.
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 \`hive_background_task(agent: "scout-researcher", sync: true, ...)\` for single investigations; use \`sync: false\` only for multi-scout fan-out.
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 calling \`hive_background_output\`, choose a timeout (30-120s) based on task size.
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; in hive mode, use hive_background_task.
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
- hive_exec_start({ task: "01-task-name" })
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; in hive mode, use hive_background_task.
14626
+ // In task mode, use task() for research fan-out.
14730
14627
  \`\`\`
14731
14628
 
14732
- **Sync Mode Guidance:**
14733
- - \`sync: true\` Use for single-scout research when you need the result before continuing
14734
- - \`sync: false\` Use for parallel fan-out (multiple scouts) or when you can proceed without waiting
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. \`hive_worker_status()\` — read blocker info
14643
+ 1. \`hive_status()\` — read blocker info
14746
14644
  2. \`question()\` — ask user (NEVER plain text)
14747
- 3. \`hive_exec_start({ task, continueFrom: "blocked", decision })\`
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
- - \`hive_exec_start\` — You ARE the spawned worker
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
- ## Notepad Location
14905
+ ## Persistent Notes
14918
14906
 
14919
- Path: \`.hive/features/{feature}/notepads/\`
14920
- - learnings.md: Patterns, conventions, successful approaches
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. Implement
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
- ### 3. Verify
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
- ### 4. Report
14941
+ ### 5. Report
14951
14942
 
14952
14943
  **Success:**
14953
14944
  \`\`\`
14954
- hive_exec_complete({
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 hive_exec_complete, STOP IMMEDIATELY.**
14952
+ **CRITICAL: After hive_worktree_commit, STOP IMMEDIATELY.**
14962
14953
 
14963
14954
  **Blocked (need user decision):**
14964
14955
  \`\`\`
14965
- hive_exec_complete({
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 \`hive_exec_start\`
14991
- - Continue after hive_exec_complete
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 hive_exec_complete** with blocker info:
21750
+ 2. **Call hive_worktree_commit** with blocker info:
21757
21751
 
21758
21752
  \`\`\`
21759
- hive_exec_complete({
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 hive_exec_complete with blocked status, STOP IMMEDIATELY.**
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
- hive_exec_complete({
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 hive_exec_complete, you MUST STOP IMMEDIATELY.**
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
- hive_exec_complete({
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
- hive_exec_complete({
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
- - \`hive_exec_complete\` - Signal task done/blocked/failed
21850
- - \`hive_exec_abort\` - Abort and discard changes
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
- - \`hive_exec_start\` - No spawning sub-workers
21856
+ - \`hive_worktree_create\` - No spawning sub-workers
21857
21857
  - \`hive_merge\` - Only Hive Master merges
21858
- - \`hive_background_task\` / \`task\` - No recursive delegation
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 hive_exec_complete when done
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/background/types.ts
22178
- var VALID_TRANSITIONS = {
22179
- spawned: ["pending", "running", "error", "cancelled"],
22180
- pending: ["running", "error", "cancelled"],
22181
- running: ["completed", "error", "cancelled", "blocked", "failed"],
22182
- completed: [],
22183
- error: [],
22184
- cancelled: [],
22185
- blocked: ["running", "cancelled"],
22186
- failed: []
22187
- };
22188
- function isTerminalStatus(status) {
22189
- return VALID_TRANSITIONS[status].length === 0;
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
- updateStatus(taskId, newStatus, updates) {
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
- var globalStore = null;
22340
- function getStore() {
22341
- if (!globalStore) {
22342
- globalStore = new BackgroundTaskStore;
22343
- }
22344
- return globalStore;
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
- // src/background/agent-gate.ts
22347
- var BLOCKED_AGENTS = new Set([
22348
- "orchestrator",
22349
- "hive",
22350
- "hive-master",
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
- async validate(agentName, options = {}) {
22388
- const name = agentName.trim().toLowerCase();
22389
- if (BLOCKED_AGENTS.has(name)) {
22390
- return {
22391
- valid: false,
22392
- error: `Agent "${agentName}" is an orchestrator agent and cannot be spawned as a background worker. ` + `Use a worker agent like "forager-worker", "scout-researcher", or "hygienic-reviewer".`
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 agent = agents.find((a) => a.name.toLowerCase() === name);
22417
- if (!agent) {
22418
- const available = agents.filter((a) => !BLOCKED_AGENTS.has(a.name.toLowerCase())).map((a) => ` • ${a.name}${a.description ? ` - ${a.description}` : ""}`).join(`
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
- return {
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
- isRestricted(agentName) {
22448
- return RESTRICTED_AGENTS.has(agentName.trim().toLowerCase());
22180
+ if (skillTemplates.length === 0) {
22181
+ return "";
22449
22182
  }
22183
+ return `
22184
+
22185
+ ` + skillTemplates.join(`
22186
+
22187
+ `);
22450
22188
  }
22451
- function createAgentGate(client) {
22452
- return new AgentGate(client);
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 (19 total)
22229
+ ### Tools (14 total)
23680
22230
 
23681
22231
  | Domain | Tools |
23682
22232
  |--------|-------|
23683
- | Feature | hive_feature_create, hive_feature_list, hive_feature_complete |
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
- | Exec | hive_exec_start, hive_exec_complete, hive_exec_abort |
23687
- | Worker | hive_worker_status |
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. \`hive_exec_start(task)\` → work in worktree → \`hive_exec_complete(task, summary)\`
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:** \`hive_exec_complete\` commits changes to task branch but does NOT merge.
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
- \`hive_exec_start\` creates worktree and spawns worker automatically:
22257
+ \`hive_worktree_create\` creates worktree and spawns worker automatically:
23709
22258
 
23710
- 1. \`hive_exec_start(task)\` → Creates worktree + spawns Forager (Worker/Coder) worker
23711
- 2. Worker executes → calls \`hive_exec_complete(status: "completed")\`
23712
- 3. Worker blocked → calls \`hive_exec_complete(status: "blocked", blocker: {...})\`
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 \`hive_worker_status()\`
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 \`hive_exec_start(task, continueFrom: "blocked", decision: answer)\`
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
- **Observation Polling (Recommended):**
23724
- - Prefer completion notifications over polling
23725
- - Use \`hive_worker_status()\` for observation-based spot checks
23726
- - Avoid tight loops with \`hive_background_output\`; if needed, wait 30-60s between checks
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 \`hive_background_task(agent: "scout-researcher", sync: false, ...)\`
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 fs11 = __require("fs");
22343
+ const fs9 = __require("fs");
23805
22344
  const blockedPath = path7.join(directory, ".hive", "features", feature, "BLOCKED");
23806
- if (fs11.existsSync(blockedPath)) {
23807
- const reason = fs11.readFileSync(blockedPath, "utf-8").trim();
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 hive_exec_start to use its worktree, and ensure any subagents work in that worktree too.`;
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
- hive_exec_start: tool({
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
- Note: delegateMode is set to 'task' in agent_hive.json. To use Hive's background tools instead, set delegateMode to 'hive'.`;
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
- ...!useHiveBackground && {
24279
- taskPromptMode: "opencode-at-file"
24280
- },
24281
- ...useHiveBackground && {
24282
- backgroundTaskCall: {
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
- ...!useHiveBackground && {
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
- hive_exec_complete: tool({
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 hive_exec_start(continueFrom: "blocked", decision: answer)'
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
- hive_exec_abort: tool({
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 hive_exec_complete first.";
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 hive_exec_start: ${runnableTasks[0]}`;
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"