oh-my-opencode 4.4.0 → 4.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/command/get-unpublished-changes.md +148 -0
- package/.agents/command/omomomo.md +37 -0
- package/.agents/command/publish.md +376 -0
- package/.agents/command/remove-deadcode.md +221 -0
- package/.agents/command/security-research.md +16 -0
- package/.agents/skills/get-unpublished-changes/SKILL.md +24 -0
- package/.agents/skills/github-triage/SKILL.md +587 -0
- package/.agents/skills/github-triage/scripts/gh_fetch.py +398 -0
- package/.agents/skills/hyperplan/SKILL.md +450 -0
- package/.agents/skills/omomomo/SKILL.md +36 -0
- package/.agents/skills/pre-publish-review/SKILL.md +407 -0
- package/.agents/skills/publish/SKILL.md +428 -0
- package/.agents/skills/remove-deadcode/SKILL.md +216 -0
- package/.agents/skills/security-research/SKILL.md +204 -0
- package/.agents/skills/work-with-pr/SKILL.md +360 -0
- package/.agents/skills/work-with-pr-workspace/evals/evals.json +76 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/benchmark.json +138 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/benchmark.md +42 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/eval_metadata.json +57 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/grading.json +15 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/code-changes.md +454 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/execution-plan.md +136 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/pr-description.md +47 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/verification-strategy.md +163 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/grading.json +15 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/code-changes.md +615 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/execution-plan.md +99 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/pr-description.md +50 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/verification-strategy.md +111 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/eval_metadata.json +37 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/grading.json +11 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/code-changes.md +205 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/execution-plan.md +78 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/pr-description.md +42 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/verification-strategy.md +87 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/grading.json +11 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/code-changes.md +334 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/execution-plan.md +86 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/pr-description.md +23 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/verification-strategy.md +119 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/eval_metadata.json +32 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/grading.json +10 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/code-changes.md +221 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/execution-plan.md +104 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/pr-description.md +41 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/verification-strategy.md +84 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/grading.json +10 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/code-changes.md +342 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/execution-plan.md +131 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/pr-description.md +39 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/verification-strategy.md +128 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/eval_metadata.json +32 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/grading.json +10 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/code-changes.md +143 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/execution-plan.md +82 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/pr-description.md +51 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/verification-strategy.md +69 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/grading.json +10 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/code-changes.md +252 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/execution-plan.md +83 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/pr-description.md +33 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/verification-strategy.md +101 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/eval_metadata.json +32 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/grading.json +10 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/code-changes.md +387 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/execution-plan.md +112 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/pr-description.md +51 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/verification-strategy.md +75 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/grading.json +10 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/code-changes.md +529 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/execution-plan.md +127 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/pr-description.md +42 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/verification-strategy.md +120 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/review.html +1326 -0
- package/.opencode/command/get-unpublished-changes.md +148 -0
- package/.opencode/command/omomomo.md +37 -0
- package/.opencode/command/publish.md +376 -0
- package/.opencode/command/remove-deadcode.md +221 -0
- package/.opencode/command/security-research.md +16 -0
- package/.opencode/skills/github-triage/SKILL.md +587 -0
- package/.opencode/skills/github-triage/scripts/gh_fetch.py +398 -0
- package/.opencode/skills/hyperplan/SKILL.md +450 -0
- package/.opencode/skills/pre-publish-review/SKILL.md +407 -0
- package/.opencode/skills/work-with-pr/SKILL.md +360 -0
- package/.opencode/skills/work-with-pr-workspace/evals/evals.json +76 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/benchmark.json +138 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/benchmark.md +42 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/eval_metadata.json +57 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/grading.json +15 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/code-changes.md +454 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/execution-plan.md +136 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/pr-description.md +47 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/verification-strategy.md +163 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/grading.json +15 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/code-changes.md +615 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/execution-plan.md +99 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/pr-description.md +50 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/verification-strategy.md +111 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/eval_metadata.json +37 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/grading.json +11 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/code-changes.md +205 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/execution-plan.md +78 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/pr-description.md +42 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/verification-strategy.md +87 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/grading.json +11 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/code-changes.md +334 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/execution-plan.md +86 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/pr-description.md +23 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/verification-strategy.md +119 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/eval_metadata.json +32 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/grading.json +10 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/code-changes.md +221 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/execution-plan.md +104 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/pr-description.md +41 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/verification-strategy.md +84 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/grading.json +10 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/code-changes.md +342 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/execution-plan.md +131 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/pr-description.md +39 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/verification-strategy.md +128 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/eval_metadata.json +32 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/grading.json +10 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/code-changes.md +143 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/execution-plan.md +82 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/pr-description.md +51 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/verification-strategy.md +69 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/grading.json +10 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/code-changes.md +252 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/execution-plan.md +83 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/pr-description.md +33 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/verification-strategy.md +101 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/eval_metadata.json +32 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/grading.json +10 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/code-changes.md +387 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/execution-plan.md +112 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/pr-description.md +51 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/verification-strategy.md +75 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/grading.json +10 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/code-changes.md +529 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/execution-plan.md +127 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/pr-description.md +42 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/verification-strategy.md +120 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/review.html +1326 -0
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.ru.md +1 -1
- package/README.zh-cn.md +1 -1
- package/dist/agents/atlas/agent.d.ts +6 -6
- package/dist/agents/prometheus/gemini.d.ts +0 -11
- package/dist/agents/prometheus/gpt.d.ts +0 -10
- package/dist/agents/prometheus/system-prompt.d.ts +2 -20
- package/dist/agents/types.d.ts +1 -16
- package/dist/cli/index.js +178 -129
- package/dist/config/schema/agent-names.d.ts +3 -3
- package/dist/config/schema/agent-overrides.d.ts +208 -208
- package/dist/config/schema/categories.d.ts +28 -28
- package/dist/config/schema/fallback-models.d.ts +20 -20
- package/dist/config/schema/oh-my-opencode-config.d.ts +208 -208
- package/dist/features/background-agent/parent-wake-notifier.d.ts +8 -1
- package/dist/help/schema/acp.d.ts +95 -0
- package/dist/help/schema/doctor.d.ts +147 -0
- package/dist/help/schema/sandbox.d.ts +74 -0
- package/dist/help/schema/status.d.ts +139 -0
- package/dist/hooks/keyword-detector/analyze/default.d.ts +1 -1
- package/dist/hooks/keyword-detector/hyperplan/default.d.ts +1 -1
- package/dist/hooks/keyword-detector/search/default.d.ts +1 -1
- package/dist/hooks/keyword-detector/team/default.d.ts +2 -7
- package/dist/hooks/keyword-detector/ultrawork/default.d.ts +1 -9
- package/dist/hooks/keyword-detector/ultrawork/gemini.d.ts +1 -16
- package/dist/hooks/keyword-detector/ultrawork/gpt.d.ts +1 -10
- package/dist/hooks/keyword-detector/ultrawork/planner.d.ts +1 -5
- package/dist/hooks/ralph-loop/no-progress-turn-detector.d.ts +7 -0
- package/dist/hooks/ralph-loop/pending-verification-handler.d.ts +1 -0
- package/dist/hooks/ralph-loop/types.d.ts +1 -0
- package/dist/hooks/runtime-fallback/error-classifier.d.ts +1 -0
- package/dist/index.js +52205 -50528
- package/dist/shared/prompt-async-gate/pending-tool-turn.d.ts +1 -0
- package/dist/shared/prompt-async-gate/types.d.ts +4 -3
- package/package.json +19 -13
- package/dist/agents/atlas/default-prompt-sections.d.ts +0 -6
- package/dist/agents/atlas/default.d.ts +0 -2
- package/dist/agents/atlas/gemini-prompt-sections.d.ts +0 -6
- package/dist/agents/atlas/gemini.d.ts +0 -2
- package/dist/agents/atlas/gpt-prompt-sections.d.ts +0 -6
- package/dist/agents/atlas/gpt.d.ts +0 -2
- package/dist/agents/atlas/kimi-prompt-sections.d.ts +0 -6
- package/dist/agents/atlas/kimi.d.ts +0 -2
- package/dist/agents/atlas/opus-4-7-prompt-sections.d.ts +0 -6
- package/dist/agents/atlas/opus-4-7.d.ts +0 -2
- package/dist/agents/atlas/shared-prompt.d.ts +0 -9
- package/dist/agents/prometheus/behavioral-summary.d.ts +0 -6
- package/dist/agents/prometheus/high-accuracy-mode.d.ts +0 -6
- package/dist/agents/prometheus/identity-constraints.d.ts +0 -7
- package/dist/agents/prometheus/interview-mode.d.ts +0 -7
- package/dist/agents/prometheus/plan-generation.d.ts +0 -7
- package/dist/agents/prometheus/plan-template.d.ts +0 -7
- package/dist/agents/prometheus/spec-driven-mode.d.ts +0 -7
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"run_id": "eval-1-with_skill",
|
|
3
|
+
"expectations": [
|
|
4
|
+
{"text": "Plan uses git worktree in a sibling directory", "passed": true, "evidence": "Uses ../omo-wt/feat-max-background-agents"},
|
|
5
|
+
{"text": "Branch is created from origin/dev", "passed": true, "evidence": "git checkout dev && git pull origin dev, then branch"},
|
|
6
|
+
{"text": "Plan specifies multiple atomic commits for multi-file changes", "passed": true, "evidence": "2 commits: schema+tests, then concurrency+manager"},
|
|
7
|
+
{"text": "Runs bun run typecheck, bun test, and bun run build before pushing", "passed": true, "evidence": "Explicit pre-push section with all 3 commands"},
|
|
8
|
+
{"text": "PR is created targeting dev branch", "passed": true, "evidence": "--base dev in gh pr create"},
|
|
9
|
+
{"text": "Verification loop includes all 3 gates: CI, review-work, and Cubic", "passed": true, "evidence": "Gate A (CI), Gate B (review-work 5 agents), Gate C (Cubic)"},
|
|
10
|
+
{"text": "Gates are checked in order: CI first, then review-work, then Cubic", "passed": true, "evidence": "Explicit ordering in verify loop pseudocode"},
|
|
11
|
+
{"text": "Cubic check uses gh api to check cubic-dev-ai[bot] reviews", "passed": true, "evidence": "Mentions cubic-dev-ai[bot] and 'No issues found' signal"},
|
|
12
|
+
{"text": "Plan includes worktree cleanup after merge", "passed": true, "evidence": "Phase 4: git worktree remove ../omo-wt/feat-max-background-agents"},
|
|
13
|
+
{"text": "Code changes reference actual files in the codebase", "passed": true, "evidence": "References src/config/schema/background-task.ts, src/features/background-agent/concurrency.ts, manager.ts"}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
# Code Changes: `max_background_agents` Config Option
|
|
2
|
+
|
|
3
|
+
## 1. `src/config/schema/background-task.ts` — Add schema field
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { z } from "zod"
|
|
7
|
+
|
|
8
|
+
export const BackgroundTaskConfigSchema = z.object({
|
|
9
|
+
defaultConcurrency: z.number().min(1).optional(),
|
|
10
|
+
providerConcurrency: z.record(z.string(), z.number().min(0)).optional(),
|
|
11
|
+
modelConcurrency: z.record(z.string(), z.number().min(0)).optional(),
|
|
12
|
+
maxDepth: z.number().int().min(1).optional(),
|
|
13
|
+
maxDescendants: z.number().int().min(1).optional(),
|
|
14
|
+
/** Maximum number of background agents that can run simultaneously across all models/providers (default: 5, minimum: 1) */
|
|
15
|
+
maxBackgroundAgents: z.number().int().min(1).optional(),
|
|
16
|
+
/** Stale timeout in milliseconds - interrupt tasks with no activity for this duration (default: 180000 = 3 minutes, minimum: 60000 = 1 minute) */
|
|
17
|
+
staleTimeoutMs: z.number().min(60000).optional(),
|
|
18
|
+
/** Timeout for tasks that never received any progress update, falling back to startedAt (default: 1800000 = 30 minutes, minimum: 60000 = 1 minute) */
|
|
19
|
+
messageStalenessTimeoutMs: z.number().min(60000).optional(),
|
|
20
|
+
syncPollTimeoutMs: z.number().min(60000).optional(),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export type BackgroundTaskConfig = z.infer<typeof BackgroundTaskConfigSchema>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Rationale:** Follows exact same pattern as `maxDepth` and `maxDescendants` — `z.number().int().min(1).optional()`. The field is optional; runtime default of 5 is applied in `ConcurrencyManager`. No barrel export changes needed since `src/config/schema.ts` already does `export * from "./schema/background-task"` and the type is inferred.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2. `src/config/schema/background-task.test.ts` — Add validation tests
|
|
31
|
+
|
|
32
|
+
Append after the existing `syncPollTimeoutMs` describe block (before the closing `})`):
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
describe("maxBackgroundAgents", () => {
|
|
36
|
+
describe("#given valid maxBackgroundAgents (10)", () => {
|
|
37
|
+
test("#when parsed #then returns correct value", () => {
|
|
38
|
+
const result = BackgroundTaskConfigSchema.parse({ maxBackgroundAgents: 10 })
|
|
39
|
+
|
|
40
|
+
expect(result.maxBackgroundAgents).toBe(10)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe("#given maxBackgroundAgents of 1 (minimum)", () => {
|
|
45
|
+
test("#when parsed #then returns correct value", () => {
|
|
46
|
+
const result = BackgroundTaskConfigSchema.parse({ maxBackgroundAgents: 1 })
|
|
47
|
+
|
|
48
|
+
expect(result.maxBackgroundAgents).toBe(1)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe("#given maxBackgroundAgents below minimum (0)", () => {
|
|
53
|
+
test("#when parsed #then throws ZodError", () => {
|
|
54
|
+
let thrownError: unknown
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
BackgroundTaskConfigSchema.parse({ maxBackgroundAgents: 0 })
|
|
58
|
+
} catch (error) {
|
|
59
|
+
thrownError = error
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
expect(thrownError).toBeInstanceOf(ZodError)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe("#given maxBackgroundAgents not provided", () => {
|
|
67
|
+
test("#when parsed #then field is undefined", () => {
|
|
68
|
+
const result = BackgroundTaskConfigSchema.parse({})
|
|
69
|
+
|
|
70
|
+
expect(result.maxBackgroundAgents).toBeUndefined()
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('#given maxBackgroundAgents is non-integer (2.5)', () => {
|
|
75
|
+
test("#when parsed #then throws ZodError", () => {
|
|
76
|
+
let thrownError: unknown
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
BackgroundTaskConfigSchema.parse({ maxBackgroundAgents: 2.5 })
|
|
80
|
+
} catch (error) {
|
|
81
|
+
thrownError = error
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
expect(thrownError).toBeInstanceOf(ZodError)
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Rationale:** Follows exact test pattern from `maxDepth`, `maxDescendants`, and `syncPollTimeoutMs` tests. Uses `#given`/`#when`/`#then` nested describe style. Tests valid, minimum boundary, below minimum, not provided, and non-integer cases.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 3. `src/features/background-agent/concurrency.ts` — Add global agent limit
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import type { BackgroundTaskConfig } from "../../config/schema"
|
|
98
|
+
|
|
99
|
+
const DEFAULT_MAX_BACKGROUND_AGENTS = 5
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Queue entry with settled-flag pattern to prevent double-resolution.
|
|
103
|
+
*
|
|
104
|
+
* The settled flag ensures that cancelWaiters() doesn't reject
|
|
105
|
+
* an entry that was already resolved by release().
|
|
106
|
+
*/
|
|
107
|
+
interface QueueEntry {
|
|
108
|
+
resolve: () => void
|
|
109
|
+
rawReject: (error: Error) => void
|
|
110
|
+
settled: boolean
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export class ConcurrencyManager {
|
|
114
|
+
private config?: BackgroundTaskConfig
|
|
115
|
+
private counts: Map<string, number> = new Map()
|
|
116
|
+
private queues: Map<string, QueueEntry[]> = new Map()
|
|
117
|
+
private globalRunningCount = 0
|
|
118
|
+
|
|
119
|
+
constructor(config?: BackgroundTaskConfig) {
|
|
120
|
+
this.config = config
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getMaxBackgroundAgents(): number {
|
|
124
|
+
return this.config?.maxBackgroundAgents ?? DEFAULT_MAX_BACKGROUND_AGENTS
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getGlobalRunningCount(): number {
|
|
128
|
+
return this.globalRunningCount
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
canSpawnGlobally(): boolean {
|
|
132
|
+
return this.globalRunningCount < this.getMaxBackgroundAgents()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
acquireGlobal(): void {
|
|
136
|
+
this.globalRunningCount++
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
releaseGlobal(): void {
|
|
140
|
+
if (this.globalRunningCount > 0) {
|
|
141
|
+
this.globalRunningCount--
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
getConcurrencyLimit(model: string): number {
|
|
146
|
+
// ... existing implementation unchanged ...
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async acquire(model: string): Promise<void> {
|
|
150
|
+
// ... existing implementation unchanged ...
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
release(model: string): void {
|
|
154
|
+
// ... existing implementation unchanged ...
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
cancelWaiters(model: string): void {
|
|
158
|
+
// ... existing implementation unchanged ...
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
clear(): void {
|
|
162
|
+
for (const [model] of this.queues) {
|
|
163
|
+
this.cancelWaiters(model)
|
|
164
|
+
}
|
|
165
|
+
this.counts.clear()
|
|
166
|
+
this.queues.clear()
|
|
167
|
+
this.globalRunningCount = 0
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
getCount(model: string): number {
|
|
171
|
+
return this.counts.get(model) ?? 0
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getQueueLength(model: string): number {
|
|
175
|
+
return this.queues.get(model)?.length ?? 0
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Key changes:**
|
|
181
|
+
- Add `DEFAULT_MAX_BACKGROUND_AGENTS = 5` constant
|
|
182
|
+
- Add `globalRunningCount` private field
|
|
183
|
+
- Add `getMaxBackgroundAgents()`, `getGlobalRunningCount()`, `canSpawnGlobally()`, `acquireGlobal()`, `releaseGlobal()` methods
|
|
184
|
+
- `clear()` resets `globalRunningCount` to 0
|
|
185
|
+
- All existing per-model methods remain unchanged
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 4. `src/features/background-agent/concurrency.test.ts` — Add global limit tests
|
|
190
|
+
|
|
191
|
+
Append new describe block:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
describe("ConcurrencyManager global background agent limit", () => {
|
|
195
|
+
test("should default max background agents to 5 when no config", () => {
|
|
196
|
+
// given
|
|
197
|
+
const manager = new ConcurrencyManager()
|
|
198
|
+
|
|
199
|
+
// when
|
|
200
|
+
const max = manager.getMaxBackgroundAgents()
|
|
201
|
+
|
|
202
|
+
// then
|
|
203
|
+
expect(max).toBe(5)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test("should use configured maxBackgroundAgents", () => {
|
|
207
|
+
// given
|
|
208
|
+
const config: BackgroundTaskConfig = { maxBackgroundAgents: 10 }
|
|
209
|
+
const manager = new ConcurrencyManager(config)
|
|
210
|
+
|
|
211
|
+
// when
|
|
212
|
+
const max = manager.getMaxBackgroundAgents()
|
|
213
|
+
|
|
214
|
+
// then
|
|
215
|
+
expect(max).toBe(10)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test("should allow spawning when under global limit", () => {
|
|
219
|
+
// given
|
|
220
|
+
const config: BackgroundTaskConfig = { maxBackgroundAgents: 2 }
|
|
221
|
+
const manager = new ConcurrencyManager(config)
|
|
222
|
+
|
|
223
|
+
// when
|
|
224
|
+
manager.acquireGlobal()
|
|
225
|
+
|
|
226
|
+
// then
|
|
227
|
+
expect(manager.canSpawnGlobally()).toBe(true)
|
|
228
|
+
expect(manager.getGlobalRunningCount()).toBe(1)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
test("should block spawning when at global limit", () => {
|
|
232
|
+
// given
|
|
233
|
+
const config: BackgroundTaskConfig = { maxBackgroundAgents: 2 }
|
|
234
|
+
const manager = new ConcurrencyManager(config)
|
|
235
|
+
|
|
236
|
+
// when
|
|
237
|
+
manager.acquireGlobal()
|
|
238
|
+
manager.acquireGlobal()
|
|
239
|
+
|
|
240
|
+
// then
|
|
241
|
+
expect(manager.canSpawnGlobally()).toBe(false)
|
|
242
|
+
expect(manager.getGlobalRunningCount()).toBe(2)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test("should allow spawning again after release", () => {
|
|
246
|
+
// given
|
|
247
|
+
const config: BackgroundTaskConfig = { maxBackgroundAgents: 1 }
|
|
248
|
+
const manager = new ConcurrencyManager(config)
|
|
249
|
+
manager.acquireGlobal()
|
|
250
|
+
|
|
251
|
+
// when
|
|
252
|
+
manager.releaseGlobal()
|
|
253
|
+
|
|
254
|
+
// then
|
|
255
|
+
expect(manager.canSpawnGlobally()).toBe(true)
|
|
256
|
+
expect(manager.getGlobalRunningCount()).toBe(0)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test("should not go below zero on extra release", () => {
|
|
260
|
+
// given
|
|
261
|
+
const manager = new ConcurrencyManager()
|
|
262
|
+
|
|
263
|
+
// when
|
|
264
|
+
manager.releaseGlobal()
|
|
265
|
+
|
|
266
|
+
// then
|
|
267
|
+
expect(manager.getGlobalRunningCount()).toBe(0)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
test("should reset global count on clear", () => {
|
|
271
|
+
// given
|
|
272
|
+
const config: BackgroundTaskConfig = { maxBackgroundAgents: 5 }
|
|
273
|
+
const manager = new ConcurrencyManager(config)
|
|
274
|
+
manager.acquireGlobal()
|
|
275
|
+
manager.acquireGlobal()
|
|
276
|
+
manager.acquireGlobal()
|
|
277
|
+
|
|
278
|
+
// when
|
|
279
|
+
manager.clear()
|
|
280
|
+
|
|
281
|
+
// then
|
|
282
|
+
expect(manager.getGlobalRunningCount()).toBe(0)
|
|
283
|
+
})
|
|
284
|
+
})
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## 5. `src/features/background-agent/manager.ts` — Enforce global limit
|
|
290
|
+
|
|
291
|
+
### In `launch()` method — add check before task creation (after `reserveSubagentSpawn`):
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
async launch(input: LaunchInput): Promise<BackgroundTask> {
|
|
295
|
+
// ... existing logging ...
|
|
296
|
+
|
|
297
|
+
if (!input.agent || input.agent.trim() === "") {
|
|
298
|
+
throw new Error("Agent parameter is required")
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Check global background agent limit before spawn guard
|
|
302
|
+
if (!this.concurrencyManager.canSpawnGlobally()) {
|
|
303
|
+
const max = this.concurrencyManager.getMaxBackgroundAgents()
|
|
304
|
+
const current = this.concurrencyManager.getGlobalRunningCount()
|
|
305
|
+
throw new Error(
|
|
306
|
+
`Background agent spawn blocked: ${current} agents running, max is ${max}. Wait for existing tasks to complete or increase background_task.maxBackgroundAgents.`
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const spawnReservation = await this.reserveSubagentSpawn(input.parentSessionID)
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
// ... existing code ...
|
|
314
|
+
|
|
315
|
+
// After task creation, before queueing:
|
|
316
|
+
this.concurrencyManager.acquireGlobal()
|
|
317
|
+
|
|
318
|
+
// ... rest of existing code ...
|
|
319
|
+
} catch (error) {
|
|
320
|
+
spawnReservation.rollback()
|
|
321
|
+
throw error
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### In `trackTask()` method — add global check:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
async trackTask(input: { ... }): Promise<BackgroundTask> {
|
|
330
|
+
const existingTask = this.tasks.get(input.taskId)
|
|
331
|
+
if (existingTask) {
|
|
332
|
+
// ... existing re-registration logic unchanged ...
|
|
333
|
+
return existingTask
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Check global limit for new external tasks
|
|
337
|
+
if (!this.concurrencyManager.canSpawnGlobally()) {
|
|
338
|
+
const max = this.concurrencyManager.getMaxBackgroundAgents()
|
|
339
|
+
const current = this.concurrencyManager.getGlobalRunningCount()
|
|
340
|
+
throw new Error(
|
|
341
|
+
`Background agent spawn blocked: ${current} agents running, max is ${max}. Wait for existing tasks to complete or increase background_task.maxBackgroundAgents.`
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ... existing task creation ...
|
|
346
|
+
this.concurrencyManager.acquireGlobal()
|
|
347
|
+
|
|
348
|
+
// ... rest unchanged ...
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### In `tryCompleteTask()` — release global slot:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
private async tryCompleteTask(task: BackgroundTask, source: string): Promise<boolean> {
|
|
356
|
+
if (task.status !== "running") {
|
|
357
|
+
// ... existing guard ...
|
|
358
|
+
return false
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
task.status = "completed"
|
|
362
|
+
task.completedAt = new Date()
|
|
363
|
+
// ... existing history record ...
|
|
364
|
+
|
|
365
|
+
removeTaskToastTracking(task.id)
|
|
366
|
+
|
|
367
|
+
// Release per-model concurrency
|
|
368
|
+
if (task.concurrencyKey) {
|
|
369
|
+
this.concurrencyManager.release(task.concurrencyKey)
|
|
370
|
+
task.concurrencyKey = undefined
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Release global slot
|
|
374
|
+
this.concurrencyManager.releaseGlobal()
|
|
375
|
+
|
|
376
|
+
// ... rest unchanged ...
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### In `cancelTask()` — release global slot:
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
async cancelTask(taskId: string, options?: { ... }): Promise<boolean> {
|
|
384
|
+
// ... existing code up to concurrency release ...
|
|
385
|
+
|
|
386
|
+
if (task.concurrencyKey) {
|
|
387
|
+
this.concurrencyManager.release(task.concurrencyKey)
|
|
388
|
+
task.concurrencyKey = undefined
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Release global slot (only for running tasks, pending never acquired)
|
|
392
|
+
if (task.status !== "pending") {
|
|
393
|
+
this.concurrencyManager.releaseGlobal()
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ... rest unchanged ...
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### In `handleEvent()` session.error handler — release global slot:
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
if (event.type === "session.error") {
|
|
404
|
+
// ... existing error handling ...
|
|
405
|
+
|
|
406
|
+
task.status = "error"
|
|
407
|
+
// ...
|
|
408
|
+
|
|
409
|
+
if (task.concurrencyKey) {
|
|
410
|
+
this.concurrencyManager.release(task.concurrencyKey)
|
|
411
|
+
task.concurrencyKey = undefined
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Release global slot
|
|
415
|
+
this.concurrencyManager.releaseGlobal()
|
|
416
|
+
|
|
417
|
+
// ... rest unchanged ...
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### In prompt error handler inside `startTask()` — release global slot:
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
promptWithModelSuggestionRetry(this.client, { ... }).catch((error) => {
|
|
425
|
+
// ... existing error handling ...
|
|
426
|
+
if (existingTask) {
|
|
427
|
+
existingTask.status = "interrupt"
|
|
428
|
+
// ...
|
|
429
|
+
if (existingTask.concurrencyKey) {
|
|
430
|
+
this.concurrencyManager.release(existingTask.concurrencyKey)
|
|
431
|
+
existingTask.concurrencyKey = undefined
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Release global slot
|
|
435
|
+
this.concurrencyManager.releaseGlobal()
|
|
436
|
+
|
|
437
|
+
// ... rest unchanged ...
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Summary of Changes
|
|
445
|
+
|
|
446
|
+
| File | Lines Added | Lines Modified |
|
|
447
|
+
|------|-------------|----------------|
|
|
448
|
+
| `src/config/schema/background-task.ts` | 2 | 0 |
|
|
449
|
+
| `src/config/schema/background-task.test.ts` | ~50 | 0 |
|
|
450
|
+
| `src/features/background-agent/concurrency.ts` | ~25 | 1 (`clear()`) |
|
|
451
|
+
| `src/features/background-agent/concurrency.test.ts` | ~70 | 0 |
|
|
452
|
+
| `src/features/background-agent/manager.ts` | ~20 | 0 |
|
|
453
|
+
|
|
454
|
+
Total: ~167 lines added, 1 line modified across 5 files.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Execution Plan: `max_background_agents` Config Option
|
|
2
|
+
|
|
3
|
+
## Phase 0: Setup — Branch + Worktree
|
|
4
|
+
|
|
5
|
+
1. **Create branch** from `dev`:
|
|
6
|
+
```bash
|
|
7
|
+
git checkout dev && git pull origin dev
|
|
8
|
+
git checkout -b feat/max-background-agents
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
2. **Create worktree** in sibling directory:
|
|
12
|
+
```bash
|
|
13
|
+
mkdir -p ../omo-wt
|
|
14
|
+
git worktree add ../omo-wt/feat-max-background-agents feat/max-background-agents
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
3. **All subsequent work** happens in `../omo-wt/feat-max-background-agents/`, never in the main worktree.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Phase 1: Implement — Atomic Commits
|
|
22
|
+
|
|
23
|
+
### Commit 1: Add `max_background_agents` to config schema
|
|
24
|
+
|
|
25
|
+
**Files changed:**
|
|
26
|
+
- `src/config/schema/background-task.ts` — Add `maxBackgroundAgents` field to `BackgroundTaskConfigSchema`
|
|
27
|
+
- `src/config/schema/background-task.test.ts` — Add validation tests for the new field
|
|
28
|
+
|
|
29
|
+
**What:**
|
|
30
|
+
- Add `maxBackgroundAgents: z.number().int().min(1).optional()` to `BackgroundTaskConfigSchema`
|
|
31
|
+
- Default value handled at runtime (5), not in schema (all schema fields are optional per convention)
|
|
32
|
+
- Add given/when/then tests: valid value, below minimum, not provided, non-number
|
|
33
|
+
|
|
34
|
+
### Commit 2: Enforce limit in BackgroundManager + ConcurrencyManager
|
|
35
|
+
|
|
36
|
+
**Files changed:**
|
|
37
|
+
- `src/features/background-agent/concurrency.ts` — Add global agent count tracking + `getGlobalRunningCount()` + `canSpawnGlobally()`
|
|
38
|
+
- `src/features/background-agent/concurrency.test.ts` — Tests for global limit enforcement
|
|
39
|
+
- `src/features/background-agent/manager.ts` — Check global limit before `launch()` and `trackTask()`
|
|
40
|
+
|
|
41
|
+
**What:**
|
|
42
|
+
- `ConcurrencyManager` already manages per-model concurrency. Add a separate global counter:
|
|
43
|
+
- `private globalRunningCount: number = 0`
|
|
44
|
+
- `private maxBackgroundAgents: number` (from config, default 5)
|
|
45
|
+
- `acquireGlobal()` / `releaseGlobal()` methods
|
|
46
|
+
- `getGlobalRunningCount()` for observability
|
|
47
|
+
- `BackgroundManager.launch()` checks `concurrencyManager.canSpawnGlobally()` before creating task
|
|
48
|
+
- `BackgroundManager.trackTask()` also checks global limit
|
|
49
|
+
- On task completion/cancellation/error, call `releaseGlobal()`
|
|
50
|
+
- Throw descriptive error when limit hit: `"Background agent spawn blocked: ${current} agents running, max is ${max}. Wait for existing tasks to complete or increase background_task.maxBackgroundAgents."`
|
|
51
|
+
|
|
52
|
+
### Local Validation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bun run typecheck
|
|
56
|
+
bun test src/config/schema/background-task.test.ts
|
|
57
|
+
bun test src/features/background-agent/concurrency.test.ts
|
|
58
|
+
bun run build
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Phase 2: PR Creation
|
|
64
|
+
|
|
65
|
+
1. **Push branch:**
|
|
66
|
+
```bash
|
|
67
|
+
git push -u origin feat/max-background-agents
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
2. **Create PR** targeting `dev`:
|
|
71
|
+
```bash
|
|
72
|
+
gh pr create \
|
|
73
|
+
--base dev \
|
|
74
|
+
--title "feat: add max_background_agents config to limit concurrent background agents" \
|
|
75
|
+
--body-file /tmp/pull-request-max-background-agents-$(date +%s).md
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Phase 3: Verify Loop
|
|
81
|
+
|
|
82
|
+
### Gate A: CI
|
|
83
|
+
- Wait for `ci.yml` workflow to complete
|
|
84
|
+
- Check: `gh pr checks <PR_NUMBER> --watch`
|
|
85
|
+
- If fails: read logs, fix, push, re-check
|
|
86
|
+
|
|
87
|
+
### Gate B: review-work (5 agents)
|
|
88
|
+
- Run `/review-work` skill which launches 5 parallel background sub-agents:
|
|
89
|
+
1. Oracle — goal/constraint verification
|
|
90
|
+
2. Oracle — code quality
|
|
91
|
+
3. Oracle — security
|
|
92
|
+
4. Hephaestus — hands-on QA execution
|
|
93
|
+
5. Hephaestus — context mining from GitHub/git
|
|
94
|
+
- All 5 must pass. If any fails, fix and re-push.
|
|
95
|
+
|
|
96
|
+
### Gate C: Cubic (cubic-dev-ai[bot])
|
|
97
|
+
- Wait for Cubic bot review on PR
|
|
98
|
+
- Must say "No issues found"
|
|
99
|
+
- If issues found: address feedback, push, re-check
|
|
100
|
+
|
|
101
|
+
### Loop
|
|
102
|
+
```
|
|
103
|
+
while (!allGatesPass) {
|
|
104
|
+
if (CI fails) → fix → push → continue
|
|
105
|
+
if (review-work fails) → fix → push → continue
|
|
106
|
+
if (Cubic has issues) → fix → push → continue
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Phase 4: Merge + Cleanup
|
|
113
|
+
|
|
114
|
+
1. **Squash merge:**
|
|
115
|
+
```bash
|
|
116
|
+
gh pr merge <PR_NUMBER> --squash --delete-branch
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
2. **Remove worktree:**
|
|
120
|
+
```bash
|
|
121
|
+
git worktree remove ../omo-wt/feat-max-background-agents
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## File Impact Summary
|
|
127
|
+
|
|
128
|
+
| File | Change Type |
|
|
129
|
+
|------|-------------|
|
|
130
|
+
| `src/config/schema/background-task.ts` | Modified — add schema field |
|
|
131
|
+
| `src/config/schema/background-task.test.ts` | Modified — add validation tests |
|
|
132
|
+
| `src/features/background-agent/concurrency.ts` | Modified — add global limit tracking |
|
|
133
|
+
| `src/features/background-agent/concurrency.test.ts` | Modified — add global limit tests |
|
|
134
|
+
| `src/features/background-agent/manager.ts` | Modified — enforce global limit in launch/trackTask |
|
|
135
|
+
|
|
136
|
+
5 files changed across 2 atomic commits. No new files created (follows existing patterns).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# PR Description
|
|
2
|
+
|
|
3
|
+
**Title:** `feat: add max_background_agents config to limit concurrent background agents`
|
|
4
|
+
|
|
5
|
+
**Base:** `dev`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
- Add `maxBackgroundAgents` field to `BackgroundTaskConfigSchema` (default: 5, min: 1) to cap total simultaneous background agents across all models/providers
|
|
12
|
+
- Enforce the global limit in `BackgroundManager.launch()` and `trackTask()` with descriptive error messages when the limit is hit
|
|
13
|
+
- Release global slots on task completion, cancellation, error, and interrupt to prevent slot leaks
|
|
14
|
+
|
|
15
|
+
## Motivation
|
|
16
|
+
|
|
17
|
+
The existing concurrency system in `ConcurrencyManager` limits agents **per model/provider** (e.g., 5 concurrent `anthropic/claude-opus-4-6` tasks). However, there is no **global** cap across all models. A user running tasks across multiple providers could spawn an unbounded number of background agents, exhausting system resources.
|
|
18
|
+
|
|
19
|
+
`max_background_agents` provides a single knob to limit total concurrent background agents regardless of which model they use.
|
|
20
|
+
|
|
21
|
+
## Config Usage
|
|
22
|
+
|
|
23
|
+
```jsonc
|
|
24
|
+
// .opencode/oh-my-opencode.jsonc
|
|
25
|
+
{
|
|
26
|
+
"background_task": {
|
|
27
|
+
"maxBackgroundAgents": 10 // default: 5, min: 1
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Changes
|
|
33
|
+
|
|
34
|
+
| File | What |
|
|
35
|
+
|------|------|
|
|
36
|
+
| `src/config/schema/background-task.ts` | Add `maxBackgroundAgents` schema field |
|
|
37
|
+
| `src/config/schema/background-task.test.ts` | Validation tests (valid, boundary, invalid) |
|
|
38
|
+
| `src/features/background-agent/concurrency.ts` | Global counter + `canSpawnGlobally()` / `acquireGlobal()` / `releaseGlobal()` |
|
|
39
|
+
| `src/features/background-agent/concurrency.test.ts` | Global limit unit tests |
|
|
40
|
+
| `src/features/background-agent/manager.ts` | Enforce global limit in `launch()`, `trackTask()`; release in completion/cancel/error paths |
|
|
41
|
+
|
|
42
|
+
## Testing
|
|
43
|
+
|
|
44
|
+
- `bun test src/config/schema/background-task.test.ts` — schema validation
|
|
45
|
+
- `bun test src/features/background-agent/concurrency.test.ts` — global limit enforcement
|
|
46
|
+
- `bun run typecheck` — clean
|
|
47
|
+
- `bun run build` — clean
|