/ commands / brief.ts
brief.ts
  1  import { feature } from 'bun:bundle'
  2  import { z } from 'zod/v4'
  3  import { getKairosActive, setUserMsgOptIn } from '../bootstrap/state.js'
  4  import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js'
  5  import {
  6    type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  7    logEvent,
  8  } from '../services/analytics/index.js'
  9  import type { ToolUseContext } from '../Tool.js'
 10  import { isBriefEntitled } from '../tools/BriefTool/BriefTool.js'
 11  import { BRIEF_TOOL_NAME } from '../tools/BriefTool/prompt.js'
 12  import type {
 13    Command,
 14    LocalJSXCommandContext,
 15    LocalJSXCommandOnDone,
 16  } from '../types/command.js'
 17  import { lazySchema } from '../utils/lazySchema.js'
 18  
 19  // Zod guards against fat-fingered GB pushes (same pattern as pollConfig.ts /
 20  // cronScheduler.ts). A malformed config falls back to DEFAULT_BRIEF_CONFIG
 21  // entirely rather than being partially trusted.
 22  const briefConfigSchema = lazySchema(() =>
 23    z.object({
 24      enable_slash_command: z.boolean(),
 25    }),
 26  )
 27  type BriefConfig = z.infer<ReturnType<typeof briefConfigSchema>>
 28  
 29  const DEFAULT_BRIEF_CONFIG: BriefConfig = {
 30    enable_slash_command: false,
 31  }
 32  
 33  // No TTL — this gate controls slash-command *visibility*, not a kill switch.
 34  // CACHED_MAY_BE_STALE still has one background-update flip (first call kicks
 35  // off fetch; second call sees fresh value), but no additional flips after that.
 36  // The tool-availability gate (tengu_kairos_brief in isBriefEnabled) keeps its
 37  // 5-min TTL because that one IS a kill switch.
 38  function getBriefConfig(): BriefConfig {
 39    const raw = getFeatureValue_CACHED_MAY_BE_STALE<unknown>(
 40      'tengu_kairos_brief_config',
 41      DEFAULT_BRIEF_CONFIG,
 42    )
 43    const parsed = briefConfigSchema().safeParse(raw)
 44    return parsed.success ? parsed.data : DEFAULT_BRIEF_CONFIG
 45  }
 46  
 47  const brief = {
 48    type: 'local-jsx',
 49    name: 'brief',
 50    description: 'Toggle brief-only mode',
 51    isEnabled: () => {
 52      if (feature('KAIROS') || feature('KAIROS_BRIEF')) {
 53        return getBriefConfig().enable_slash_command
 54      }
 55      return false
 56    },
 57    immediate: true,
 58    load: () =>
 59      Promise.resolve({
 60        async call(
 61          onDone: LocalJSXCommandOnDone,
 62          context: ToolUseContext & LocalJSXCommandContext,
 63        ): Promise<React.ReactNode> {
 64          const current = context.getAppState().isBriefOnly
 65          const newState = !current
 66  
 67          // Entitlement check only gates the on-transition — off is always
 68          // allowed so a user whose GB gate flipped mid-session isn't stuck.
 69          if (newState && !isBriefEntitled()) {
 70            logEvent('tengu_brief_mode_toggled', {
 71              enabled: false,
 72              gated: true,
 73              source:
 74                'slash_command' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
 75            })
 76            onDone('Brief tool is not enabled for your account', {
 77              display: 'system',
 78            })
 79            return null
 80          }
 81  
 82          // Two-way: userMsgOptIn tracks isBriefOnly so the tool is available
 83          // exactly when brief mode is on. This invalidates prompt cache on
 84          // each toggle (tool list changes), but a stale tool list is worse —
 85          // when /brief is enabled mid-session the model was previously left
 86          // without the tool, emitting plain text the filter hides.
 87          setUserMsgOptIn(newState)
 88  
 89          context.setAppState(prev => {
 90            if (prev.isBriefOnly === newState) return prev
 91            return { ...prev, isBriefOnly: newState }
 92          })
 93  
 94          logEvent('tengu_brief_mode_toggled', {
 95            enabled: newState,
 96            gated: false,
 97            source:
 98              'slash_command' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
 99          })
100  
101          // The tool list change alone isn't a strong enough signal mid-session
102          // (model may keep emitting plain text from inertia, or keep calling a
103          // tool that just vanished). Inject an explicit reminder into the next
104          // turn's context so the transition is unambiguous.
105          // Skip when Kairos is active: isBriefEnabled() short-circuits on
106          // getKairosActive() so the tool never actually leaves the list, and
107          // the Kairos system prompt already mandates SendUserMessage.
108          // Inline <system-reminder> wrap — importing wrapInSystemReminder from
109          // utils/messages.ts pulls constants/xml.ts into the bridge SDK bundle
110          // via this module's import chain, tripping the excluded-strings check.
111          const metaMessages = getKairosActive()
112            ? undefined
113            : [
114                `<system-reminder>\n${
115                  newState
116                    ? `Brief mode is now enabled. Use the ${BRIEF_TOOL_NAME} tool for all user-facing output — plain text outside it is hidden from the user's view.`
117                    : `Brief mode is now disabled. The ${BRIEF_TOOL_NAME} tool is no longer available — reply with plain text.`
118                }\n</system-reminder>`,
119              ]
120  
121          onDone(
122            newState ? 'Brief-only mode enabled' : 'Brief-only mode disabled',
123            { display: 'system', metaMessages },
124          )
125          return null
126        },
127      }),
128  } satisfies Command
129  
130  export default brief