Bring the team member addendum and the team-mode skill in line with the
Claude Code experimental Agent Teams reference (../free-code) on the
behavioral framing the LLM needs, while keeping every omo-specific
constraint (TeamRunId param, lead-only tool blocklist, eligibility
registry, sisyphus-junior routing) intact.
Member addendum (src/features/team-mode/member-guidance.ts) — adopt
free-code teammatePromptAddendum.ts wisdom that was missing:
- "User interacts primarily with the team lead" — members no longer try
to address the user directly; they coordinate via task system + DMs.
- Stronger emphasis that plain assistant text is invisible to the team;
team_send_message is mandatory, not optional.
- Broadcast (`to: "*"`) called out explicitly with "sparingly" guidance
and the lead-only constraint.
- "Idle is normal" section: idle ≠ done, idle teammates can still
receive messages, the lead must not treat idle as an error.
- "Communication rules" section forbidding structured JSON status
messages and forbidding terminal tools as a peer-inspection backdoor;
always reference teammates by NAME, not session ID.
- Auto-delivery framing: messages arrive as new turns, no manual inbox
polling, no need to quote a teammate's message back to the lead.
Team-mode skill (src/features/builtin-skills/skills/team-mode.ts) —
adopt free-code TeamCreateTool/prompt.ts sections that were missing:
- Task ownership: any agent can set/change `owner` via team_task_update.
- Automatic message delivery: <peer_message ...> envelope flow.
- Teammate idle state: idle is the expected post-turn state; peer DM
summaries surface in the lead's idle notification.
- Discovering team members: team_status + ~/.omo/teams/ config; refer by
name, never raw session IDs.
- Task list coordination: claim by ID order (lowest first), check after
every completion, escalate blocking deadlocks to the lead.
- Communication rules: same anti-patterns as the member addendum, scoped
for the lead's awareness (no JSON status, no terminal peer-inspection,
no delegate-task from inside members).
Test (create.test.ts) updated to track the renamed section header
("Lead-only tools you must NOT call") and to lock the new free-code
parity assertions ("user interacts primarily with the team lead",
"Idle is normal", "structured JSON status messages").
All other contract tests (team-mode skill keyword/section tests,
member parser, runtime tests) untouched and still passing.
Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode).
When user types /hyperplan something, two pipelines race for the message:
1. keyword-detector hook (chat.message): the regex \b(hyperplan|hpp)\b
matches /hyperplan because \b is satisfied by the / boundary, so the
hook prepends <hyperplan-mode> to the text part.
2. auto-slash-command hook (chat.message, runs immediately after):
detectSlashCommand() checks `text.trimStart().startsWith("/")`. After
keyword-detector's prepend, the part now starts with <hyperplan-mode>
and the slash check fails, so the builtin command template
(with $ARGUMENTS substituted) is never injected.
The visible symptom: /hyperplan refactor X never runs the actual
HYPERPLAN_TEMPLATE - the user sees only the keyword-detector wrapper, which
is similar but not identical, and the slash command's $ARGUMENTS payload
is silently lost.
Fix: at the top of the keyword-detector hook, after isSystemDirective() but
before any keyword scan, bail out if the prompt text starts with a slash
command (^\s*\/[a-zA-Z][\w-]*\b). Slash commands are explicit invocations
and own their own mode-injection path; the keyword detector must not race
them. Free-form mentions like "hyperplan: refactor X" still trigger
keyword detection - only the leading-slash form is suppressed.
Lock the contract with three regression tests:
- /hyperplan refactor ... must NOT inject <hyperplan-mode>
- /hpp investigate ... must NOT inject (shorthand slash command)
- "hyperplan: refactor src/auth/handler.ts" still injects (free-form)
Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode).
The shared resolveCategoryExecution path treats `agents.sisyphus-junior.model`
(plugin config) as a higher-precedence override than the category default. That
ranking is correct for plain `task(category=...)` delegations - it lets users
pin sisyphus-junior to their preferred general model - but in team-mode it
collapses every kind:"category" member onto the same model.
Hyperplan was the visible victim: skeptic/validator/researcher/architect/creative
are routed through sisyphus-junior with five distinct categories
(unspecified-low / unspecified-high / deep / ultrabrain / artistry). With any
sisyphus-junior model configured, all five resolved to that single override
model instead of their category defaults, defeating the multi-model adversarial
debate.
Strip the override at the team-mode boundary in resolveMember rather than
changing resolveCategoryExecution itself, so:
- delegate-task callers keep the existing override semantics (3 regression
tests at tools.test.ts:2832, 2958, 3020 stay green)
- per-category user overrides (`categories[X].model`) and explicit fallback
chains continue to apply
- only the `kind:"category"` team-mode path opts out of the global override
Lock the contract with a regression test in resolve-member.test.ts.
Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode).
Adjacent "hpp ulw" or "ulw hpp" (8 form combinations: short/long, both
orders) triggers a fused mode that suppresses the standalone ultrawork
and hyperplan banners and toasts in favor of one combo banner. The
combo banner explicitly preserves hyperplan's mandatory adversarial
workflow contract (do NOT improvise, do NOT skip rounds) instead of
silently downgrading it.
Suppression runs as a named pipeline step (suppressComboStandalones)
immediately after detection and before all consumers (planner filter,
session filters, toasts, message injection), so standalone toast checks
naturally see the already-suppressed list. Combo is allowed in non-main
sessions like ultrawork, filtered for planner agents like both
standalones, and blocked in subagent sessions via the existing gate.
disabled_keywords uses the intersection rule: disabling either
"ultrawork" or "hyperplan" also disables the combo, so no flavored
content leaks via the combo embedding when either base keyword is
disabled.
Includes a same-PR refactor of KEYWORD_DETECTORS from {pattern, message}
to {type, pattern, message} tuple shape, dropping the parallel hardcoded
types array in detector.ts that previously coupled type assignment to
registry index. Future detector additions can no longer silently corrupt
DetectedKeyword.type via reorder or insertion.
10 behavioral contract tests in hyperplan-ultrawork.test.ts cover both
trigger orders, non-adjacent rejection, suppression of injection and
toast, intersection-rule disable behavior, session/agent policy, and
ultrawork variant routing through the combo. The pre-existing combined
"ultrawork hyperplan" assertion in hyperplan.test.ts is removed in
favor of the new file.
Plan distilled from a hyperplan adversarial review (5 members,
3 rounds: skeptic, validator, researcher, architect, creative).
Update both injection paths to reflect the 7-phase workflow with the
mandatory plan agent handoff in step 7. The keyword detector now lists
all 8 enumerated steps including the explicit "do not write the plan
yourself" guidance, and the /hyperplan command template references the
7-phase workflow count.
Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode).
Adds keyword_detector.disabled_keywords config so users can opt out of
specific keyword detectors individually without disabling the entire
keyword-detector hook. Allowed values: 'ultrawork', 'search', 'analyze',
'team'. Default empty/missing -> all four detectors active (no behavior
change for existing configs).
Motivation: an audit revealed search and analyze patterns trigger on
~30-60% of normal conversational user messages (e.g. 'how to', 'why is',
'show me', '왜', '어떻게'). The disable list is the immediate kill switch
while the patterns themselves are tightened in a separate PR.
Schema follows the existing per-feature config block convention shared
by team_mode, ralph_loop, runtime_fallback, and comment_checker. The
KeywordType enum (z.enum) lives next to the config schema and is
re-imported by the detector to keep the union type in lockstep with the
schema.
Threading:
pluginConfig.keyword_detector
-> create-transform-hooks.ts (factory wiring)
-> createKeywordDetectorHook(config)
-> detectKeywordsWithType(text, agent, model, disabledKeywords)
-> Set-based filter at the source-of-truth detector
Adds 8 regression tests covering per-keyword disable, multi-keyword
disable, partial disable (one keyword off, another still firing),
ultrawork toast suppression, undefined config, and empty array.
Detects user invocations of team-mode work across English and Korean
('team mode', '팀 모드', '팀으로') and injects a concise English directive
instructing the LLM to orchestrate via team_* tools (team_create ->
team_task_create + team_send_message), forbidding delegate_task
substitution and fallbacks.
The Korean variants use a Hangul-syllable negative lookbehind (가-힣) so
that '스팀으로 게임 켜줘' does not falsely match '팀으로' and '스팀모드' does
not falsely match '팀모드'.
Follows the existing folder pattern (mode/default.ts + mode/index.ts)
shared by ultrawork/, search/, and analyze/. The hook orchestration in
hook.ts handles the new keyword type generically through the shared
KEYWORD_DETECTORS array, so existing guards (non-OMO agent skip,
non-main session filter, system-reminder strip, code-block strip) all
apply automatically.
Adds 7 regression tests covering English/Korean trigger forms, the
Hangul-prefix false-positive guard, the bare-'team' negative case, and
non-main-session filtering.