diff --git a/README.md b/README.md index 0f38a10..d5c752b 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ BAD is configured at install time (`/bad setup`) and stores settings in the `bad | `RETRO_TIMER_SECONDS` | `600` | Delay before auto-retrospective | | `CONTEXT_COMPACTION_THRESHOLD` | `80` | Context window % at which to compact context | | `STALE_TIMEOUT_MINUTES` | `60` | Minutes of subagent inactivity before watchdog alerts (0 = disabled) | -| `TIMER_SUPPORT` | `true` | Use native platform timers; `false` for prompt-based continuation | +| `TIMER_SUPPORT` | `cron` (Claude Code) / `blocking-sleep` (Codex) / `prompt` (others) | `cron` uses `CronCreate`; `blocking-sleep` uses a shell `sleep N` (auto-fires when it returns); `prompt` waits for a user reply. Legacy `true`/`false` still accepted | | `MONITOR_SUPPORT` | `true` | Use the Monitor tool for CI/PR-merge polling; `false` for Bedrock/Vertex/Foundry | | `API_FIVE_HOUR_THRESHOLD` | `80` | (Claude Code) 5-hour usage % at which to pause | | `API_SEVEN_DAY_THRESHOLD` | `95` | (Claude Code) 7-day usage % at which to pause | diff --git a/docs/index.md b/docs/index.md index 2691bfa..67ead8f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -99,7 +99,7 @@ BAD is configured at install time (`/bad setup`) and stores settings in the `bad | `RETRO_TIMER_SECONDS` | `600` | Seconds before auto-retrospective after epic completion | | `CONTEXT_COMPACTION_THRESHOLD` | `80` | Context window % at which to compact context | | `STALE_TIMEOUT_MINUTES` | `60` | Minutes of subagent inactivity before watchdog alerts (0 = disabled) | -| `TIMER_SUPPORT` | `true` | Use native platform timers; `false` for prompt-based continuation | +| `TIMER_SUPPORT` | `cron` (Claude Code) / `blocking-sleep` (Codex) / `prompt` (others) | `cron` uses `CronCreate`; `blocking-sleep` uses a shell `sleep N` (auto-fires when it returns); `prompt` waits for a user reply. Legacy `true`/`false` still accepted | | `MONITOR_SUPPORT` | `true` | Use the Monitor tool for CI/PR-merge polling; `false` for Bedrock/Vertex/Foundry | | `API_FIVE_HOUR_THRESHOLD` | `80` | (Claude Code) 5-hour usage % at which to pause | | `API_SEVEN_DAY_THRESHOLD` | `95` | (Claude Code) 7-day usage % at which to pause | diff --git a/skills/bad/SKILL.md b/skills/bad/SKILL.md index 0154897..08a8c80 100644 --- a/skills/bad/SKILL.md +++ b/skills/bad/SKILL.md @@ -56,7 +56,7 @@ Load base values from the `bad` section of `_bmad/config.yaml` at startup. Then | `WAIT_TIMER_SECONDS` | `wait_timer_seconds` | `3600` | Post-batch wait before re-checking PR status (1 hr) | | `CONTEXT_COMPACTION_THRESHOLD` | `context_compaction_threshold` | `80` | Context window % at which to compact/summarise context | | `STALE_TIMEOUT_MINUTES` | `stale_timeout_minutes` | `60` | Minutes of subagent inactivity before watchdog alerts (0 = disabled) | -| `TIMER_SUPPORT` | `timer_support` | `true` | When `true`, use native platform timers; when `false`, use prompt-based continuation | +| `TIMER_SUPPORT` | `timer_support` | `cron` (Claude Code) / `blocking-sleep` (Codex) / `prompt` (others) | `cron` uses `CronCreate`; `blocking-sleep` uses a shell `sleep N` that auto-fires when it returns; `prompt` waits for a user reply. Legacy `true`→`cron`, `false`→`prompt` | | `MONITOR_SUPPORT` | `monitor_support` | `true` | When `true`, use the Monitor tool for CI and PR-merge polling; when `false`, fall back to manual polling loops (required for Bedrock/Vertex/Foundry) | | `API_FIVE_HOUR_THRESHOLD` | `api_five_hour_threshold` | `80` | (Claude Code) 5-hour rate limit % that triggers a pause | | `API_SEVEN_DAY_THRESHOLD` | `api_seven_day_threshold` | `95` | (Claude Code) 7-day rate limit % that triggers a pause | @@ -68,7 +68,7 @@ Load base values from the `bad` section of `_bmad/config.yaml` at startup. Then After resolving all values, print the active configuration so the user can confirm before Phase 0 begins: ``` -⚙️ BAD config: MAX_PARALLEL_STORIES=3, RUN_CI_LOCALLY=false, AUTO_PR_MERGE=false, MODEL_STANDARD=sonnet, MODEL_QUALITY=opus, TIMER_SUPPORT=true, ... +⚙️ BAD config: MAX_PARALLEL_STORIES=3, RUN_CI_LOCALLY=false, AUTO_PR_MERGE=false, MODEL_STANDARD=sonnet, MODEL_QUALITY=opus, TIMER_SUPPORT=cron, ... ``` --- @@ -536,7 +536,7 @@ Read `references/coordinator/pattern-notify.md` whenever a `📣 Notify:` callou ## Timer Pattern -Read `references/coordinator/pattern-timer.md` when instructed to start a timer. It covers both `TIMER_SUPPORT=true` (CronCreate) and `TIMER_SUPPORT=false` (prompt-based) paths. +Read `references/coordinator/pattern-timer.md` when instructed to start a timer. It covers all three `TIMER_SUPPORT` paths: `cron` (Claude Code's `CronCreate`), `blocking-sleep` (Codex — shell `sleep N` auto-fires), and `prompt` (manual reply). --- diff --git a/skills/bad/assets/module-setup.md b/skills/bad/assets/module-setup.md index c9e3091..12162bb 100644 --- a/skills/bad/assets/module-setup.md +++ b/skills/bad/assets/module-setup.md @@ -201,9 +201,10 @@ If multiple harnesses are detected, repeat this step once per additional harness section clearly and store model/threshold values with a harness prefix (e.g. `claude_model_standard`). -Automatically write without prompting: -- Claude Code: `timer_support: true`, `monitor_support: true` -- All other harnesses: `timer_support: false`, `monitor_support: false` +Automatically write without prompting (based on the `current_harness` detected in Step 2): +- Claude Code: `timer_support: cron`, `monitor_support: true` +- `openai-codex`: `timer_support: blocking-sleep`, `monitor_support: false` +- All other harnesses: `timer_support: prompt`, `monitor_support: false` ## Step 7: Write Files @@ -220,7 +221,7 @@ Write a temp JSON file with collected answers structured as: "retro_timer_seconds": "600", "context_compaction_threshold": "80", "stale_timeout_minutes": "60", - "timer_support": true, + "timer_support": "", "monitor_support": true, "model_standard": "", "model_quality": "", diff --git a/skills/bad/references/coordinator/gate-pre-continuation.md b/skills/bad/references/coordinator/gate-pre-continuation.md index 32e4504..33c1f49 100644 --- a/skills/bad/references/coordinator/gate-pre-continuation.md +++ b/skills/bad/references/coordinator/gate-pre-continuation.md @@ -49,7 +49,7 @@ If `rate_limits.five_hour.used_percentage` is present and **> `API_FIVE_HOUR_THR date -d @{resets_at} ``` 2. Print: `"⏸ 5-hour usage limit at {usage}% — auto-pausing until reset at {reset_time}. BAD will resume automatically."` -3. **If `TIMER_SUPPORT=true`:** compute a cron expression from the reset epoch and schedule a resume: +3. **If `TIMER_SUPPORT=cron`** (or legacy `true`): compute a cron expression from the reset epoch and schedule a resume: ```bash # macOS date -r {resets_at} '+%M %H %d %m *' @@ -63,7 +63,14 @@ If `rate_limits.five_hour.used_percentage` is present and **> `API_FIVE_HOUR_THR Save the job ID. Do not ask the user for input — resume automatically when `BAD_RATE_LIMIT_TIMER_FIRED` arrives. -4. **If `TIMER_SUPPORT=false`:** print the reset time and wait for the user to reply when they're ready to continue. Then re-check the limit before proceeding. +4. **If `TIMER_SUPPORT=blocking-sleep`:** compute the remaining seconds and block in the shell until reset: + ```bash + SLEEP_SECS=$(( {resets_at} - $(date +%s) )) + [ "$SLEEP_SECS" -gt 0 ] && sleep "$SLEEP_SECS" + ``` + When `sleep` returns, re-read session state and re-check `five_hour.used_percentage` — if now below `API_FIVE_HOUR_THRESHOLD`, proceed to Check 3. If still too high (rare — usage sample hasn't refreshed), sleep another 60s and re-check. + +5. **If `TIMER_SUPPORT=prompt`** (or legacy `false`): print the reset time and wait for the user to reply when they're ready to continue. Then re-check the limit before proceeding. --- @@ -79,7 +86,7 @@ If `rate_limits.seven_day.used_percentage` is present and **> `API_SEVEN_DAY_THR date -d @{resets_at} ``` 2. Print: `"⏸ 7-day usage limit at {usage}% — auto-pausing until reset at {reset_time}. BAD will resume automatically."` -3. **If `TIMER_SUPPORT=true`:** compute a cron expression from the reset epoch and schedule a resume: +3. **If `TIMER_SUPPORT=cron`** (or legacy `true`): compute a cron expression from the reset epoch and schedule a resume: ```bash # macOS date -r {resets_at} '+%M %H %d %m *' @@ -93,7 +100,14 @@ If `rate_limits.seven_day.used_percentage` is present and **> `API_SEVEN_DAY_THR Save the job ID. Resume automatically when `BAD_RATE_LIMIT_TIMER_FIRED` arrives. -4. **If `TIMER_SUPPORT=false`:** print the reset time and wait for the user to reply when ready. Then re-check before proceeding. +4. **If `TIMER_SUPPORT=blocking-sleep`:** compute the remaining seconds and block in the shell until reset: + ```bash + SLEEP_SECS=$(( {resets_at} - $(date +%s) )) + [ "$SLEEP_SECS" -gt 0 ] && sleep "$SLEEP_SECS" + ``` + When `sleep` returns, re-read session state and re-check `seven_day.used_percentage` — if now below `API_SEVEN_DAY_THRESHOLD`, proceed to Phase 0. If still too high, sleep another 60s and re-check. + +5. **If `TIMER_SUPPORT=prompt`** (or legacy `false`): print the reset time and wait for the user to reply when ready. Then re-check before proceeding. --- diff --git a/skills/bad/references/coordinator/pattern-timer.md b/skills/bad/references/coordinator/pattern-timer.md index 4bf2531..9115a26 100644 --- a/skills/bad/references/coordinator/pattern-timer.md +++ b/skills/bad/references/coordinator/pattern-timer.md @@ -2,11 +2,17 @@ Both the retrospective and post-batch wait timers use this pattern. The caller supplies the duration, fire prompt, option labels, and actions. -Behaviour depends on `TIMER_SUPPORT`: +Behaviour depends on `TIMER_SUPPORT`. Accepted values: + +- `cron` — Claude Code's `CronCreate` schedules a one-shot wake-up. +- `blocking-sleep` — Codex (and any harness without a scheduler but with a shell): a blocking `sleep N` *is* the timer; it auto-fires when it returns. +- `prompt` — no auto-fire; wait for the user to reply. + +Legacy values: `true` is read as `cron`, `false` is read as `prompt`. --- -## If `TIMER_SUPPORT=true` (native platform timers) +## If `TIMER_SUPPORT=cron` (native platform timers) **Step 1 — compute target cron expression** (convert seconds to minutes: `SECONDS ÷ 60`): ```bash @@ -64,7 +70,37 @@ echo "⏱ Time elapsed: $((ELAPSED / 60))m $((ELAPSED % 60))s" --- -## If `TIMER_SUPPORT=false` (prompt-based continuation) +## If `TIMER_SUPPORT=blocking-sleep` (Codex — shell-blocking timer) + +Save `TIMER_START=$(date +%s)`. Codex has no scheduler, but its shell tool blocks on long commands — so `sleep N` *is* the timer. Print the options menu: + +> Timer running — shell sleep for {N} minutes. I'll act automatically when it returns. +> +> - **[C] Continue** — {C label} (auto-fires when sleep completes) +> - **[S] Stop** — {S label} +> - **[X] Exit** — {X label} ← omit this line if no [X] label was supplied +> - **[M] N** — to modify the countdown, press **Esc** (or ctrl-C) to interrupt the sleep, then reply with `[M] ` + +📣 **Notify** (see `references/coordinator/pattern-notify.md`) with the same options. Make sure the user knows that typing a reply mid-wait requires pressing Esc first — while the shell tool is running, the agent is blocked on it. + +Then run a single blocking command: +```bash +sleep {SECONDS} +EXIT=$? +ELAPSED=$(( $(date +%s) - TIMER_START )) +echo "⏱ Time elapsed: $((ELAPSED / 60))m $((ELAPSED % 60))s (exit=$EXIT)" +``` + +- **`EXIT=0`** (sleep completed normally) → run the [C] action automatically. This is the auto-fire path. +- **`EXIT!=0`** (user interrupted with Esc / ctrl-C) → reprint the menu and wait for the user's reply: + - **[C]** → run the [C] action + - **[S]** → run the [S] action + - **[X]** → run the [X] action ← only if [X] label was supplied + - **[M] N** → update `TIMER_START`, recompute `SECONDS = N × 60`, print the updated wait message, 📣 **Notify**, then re-run the `sleep $SECONDS` command above + +--- + +## If `TIMER_SUPPORT=prompt` (prompt-based continuation) Save `TIMER_START=$(date +%s)`. No native timer is created — print the options menu immediately and wait for user reply: