feat(btw): /btw context-shielded side-question command#5521
Conversation
Add a `/btw <question>` builtin slash command. The question is answered inline by the current session model (read-only, no tools, single response), then the `/btw` question and its answer are stripped from every future model-request payload, so a quick side question never consumes future token budget or alters what the model remembers. - Register /btw builtin (template + BuiltinCommandName union + disabled_commands enum) - btw-context-strip transform hook: marker-driven (non-forgeable metadata), fail-closed, deterministic positional pairing with pending-turn retention, tool-pair safe, in-place mutation of the model-request payload - btw-tool-guard: hard no-tools enforcement on the primary-session answer turn, dispatched from tool-execute-before - auto-slash marking on both chat.message and command.execute.before routes; empty/whitespace /btw flows through auto-slash and is stripped (no special-case) - compaction caveat documented; docs/guide/btw.md
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b4822e57d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await hooks.atlasHook?.["tool.execute.before"]?.(input, output) | ||
| await hooks.compactionTodoPreserver?.["tool.execute.before"]?.(input, output) | ||
| await hooks.teamToolGating?.["tool.execute.before"]?.(input, output) | ||
| await hooks.btwToolGuard?.["tool.execute.before"]?.(input, output) |
There was a problem hiding this comment.
Move /btw guard before side-effectful pre-tool hooks
When a model attempts a tool while answering /btw, this denial runs only after earlier tool.execute.before hooks such as claudeCodeHooks; that handler appends transcript/cache entries and can execute user-configured Claude Code PreToolUse hooks before the /btw guard throws. In environments with PreToolUse hooks, the advertised read-only/no-tools /btw turn can still run side-effectful hook code, so the guard needs to happen before those pre-tool hooks.
Useful? React with 👍 / 👎.
| </user-request>`, | ||
| argumentHint: "[goal]", | ||
| }, | ||
| btw: { |
There was a problem hiding this comment.
Honor disabled_commands for /btw auto expansion
Registering btw here makes the chat auto-slash path able to expand /btw even when users set disabled_commands: ["btw"]: createAutoSlashCommandHook is constructed without the disabled list, and discoverCommandsSync() loads built-ins with loadBuiltinCommands() unfiltered. That means disabling only removes the native command entry, while typing /btw ... in chat still injects the template, marks the message, and strips context unless the user disables the entire auto-slash hook.
Useful? React with 👍 / 👎.
| if (!await isPrimaryInteractiveSession(deps, input.sessionID)) { | ||
| return |
There was a problem hiding this comment.
Gate /btw before non-primary sessions can expand it
Returning here only skips the tool guard for subagent/team/child sessions; it does not prevent /btw from being expanded there, because the command is registered globally and the auto-slash hook marks /btw without checking session type. If a subagent/team/background prompt starts with /btw, it still receives the BTW template but tools are not blocked, violating the documented primary-only/read-only behavior.
Useful? React with 👍 / 👎.
The btw port inlined the auto-slash-command type definitions, clobbering the `export * from "@oh-my-opencode/skills-loader-core/auto-slash-command/types"` re-export shim. That dropped the tracked re-export-shim count 316 -> 315 and broke the package-registration-audit (re-export shim inventory). Restore the shim and add the optional `message` field that btw command marking needs to the shared CommandExecuteBeforeOutput type in skills-loader-core (mirrors AutoSlashCommandHookOutput, which already carries message).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6eadf0c7e7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| continue | ||
| } | ||
|
|
||
| safeStripIndices.delete(index) |
There was a problem hiding this comment.
Strip tool-bearing /btw answers instead of retaining them
If a /btw answer ever contains a tool call (for example when the guard is disabled, fails open, or an older marked transcript is replayed), this deletes the tool-bearing answer messages from the strip set, so the later model request still sees the side question's tool use/result even though the /btw Q&A is supposed to be removed from future context. Preserve API tool-pair validity by stripping the whole paired exchange rather than keeping it in the payload.
Useful? React with 👍 / 👎.
Summary
Adds a
/btw <question>builtin slash command. The question is answered inline by the current session model (read-only, no tools, single response), then the/btwquestion and its answer are stripped from every FUTURE model-request payload, so a quick side question never consumes future token budget or changes what the model "remembers".This takes a transform-strip approach (a different design from the child-session isolation in #3804).
What's included
/btwbuiltin (template +BuiltinCommandNameunion +disabled_commandsenum),argumentHint: "<question>".btw-context-striptransform hook — marker-driven (non-forgeable__omoBtwAutoSlashCommandmetadata), fail-closed / no-throw, deterministic positional pairing with pending-turn retention, tool-pair safe, in-place mutation of the model-request payload.btw-tool-guard— hard no-tools enforcement on the primary-session/btwanswer turn (dispatched fromtool-execute-before).chat.messageandcommand.execute.beforeroutes; empty/whitespace/btwflows through auto-slash and is stripped too (no special-case).docs/guide/btw.md, including the compaction caveat.Behavior / guarantee
/btwremoves the Q&A from future model-request payloads (token budget + model memory stay clean). It is not a secrecy feature: the Q&A stays visible in TUI scrollback and session storage by design. Compaction summaries may retain/btwcontent (compaction does not route through the message-transform pipeline), documented as a known caveat.QA & review
bun run typecheckclean; full btw + adjacent suites + meta-audits green (153 tests);bun run build+bun run build:schemagreen (schema adds only thebtwcommand enum).tool.execute.beforesequence), now fixed with a RED to GREEN dispatch regression test (tool-execute-before-btw-guard.test.ts)./btwbehavior confirmed: a normal follow-up turn after a/btwside question cannot see the side-question content, while normal context is preserved.@code-yeongyu — a feature I have found genuinely needed day to day; would love your review.
Special thanks to @IYENTeam for surfacing the need and the original
/btwattempt in #3804; this PR takes a transform-strip approach rather than child-session isolation.Summary by cubic
Adds a new
/btw <question>slash command for quick side questions answered inline by the current session model. The/btwQ&A is stripped from all future model-request payloads, keeping token budget and model “memory” clean.New Features
btwcommand withargumentHint: "<question>"; uses the current session model; single response; no tools.btw-context-striptransform: uses non-forgeable__omoBtwAutoSlashCommandmarkers to strip the/btwuser message and its answer from future payloads; retains a pending/btwturn; preserves adjacent tool pairs; mutates the payload in place.btw-tool-guard: blocks tool calls during the/btwanswer in the primary interactive session; dispatched fromtool.execute.before.chat.messageandcommand.execute.before; empty or whitespace-only/btwis still marked and stripped.docs/guide/btw.md(includes how to disable viadisabled_commands: ["btw"]and the compaction caveat that summaries may retain/btwcontent).Bug Fixes
export * from "@oh-my-opencode/skills-loader-core/auto-slash-command/types"shim and added an optionalmessagefield toCommandExecuteBeforeOutputto support/btwmarking and fix the registration audit.Written for commit 6eadf0c. Summary will update on new commits.