diff --git a/src/buddy/useBuddyNotification.tsx b/src/buddy/useBuddyNotification.tsx index 2f1cfe3..6453163 100644 --- a/src/buddy/useBuddyNotification.tsx +++ b/src/buddy/useBuddyNotification.tsx @@ -10,12 +10,12 @@ import { getRainbowColor } from '../utils/thinking.js'; // buzz instead of a single UTC-midnight spike, gentler on soul-gen load. // Teaser window: April 1-7, 2026 only. Command stays live forever after. export function isBuddyTeaserWindow(): boolean { - if (("external" as string) === 'ant') return true; + if ((process.env.USER_TYPE) === 'ant') return true; const d = new Date(); return d.getFullYear() === 2026 && d.getMonth() === 3 && d.getDate() <= 7; } export function isBuddyLive(): boolean { - if (("external" as string) === 'ant') return true; + if ((process.env.USER_TYPE) === 'ant') return true; const d = new Date(); return d.getFullYear() > 2026 || d.getFullYear() === 2026 && d.getMonth() >= 3; } diff --git a/src/commands/mcp/mcp.tsx b/src/commands/mcp/mcp.tsx index 2456ea8..be0f074 100644 --- a/src/commands/mcp/mcp.tsx +++ b/src/commands/mcp/mcp.tsx @@ -77,7 +77,7 @@ export async function call(onDone: LocalJSXCommandOnDone, _context: unknown, arg } // Redirect base /mcp command to /plugins installed tab for ant users - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { return ; } return ; diff --git a/src/commands/terminalSetup/terminalSetup.tsx b/src/commands/terminalSetup/terminalSetup.tsx index 4fd8b4e..694a7b5 100644 --- a/src/commands/terminalSetup/terminalSetup.tsx +++ b/src/commands/terminalSetup/terminalSetup.tsx @@ -119,7 +119,7 @@ export async function setupTerminal(theme: ThemeName): Promise { maybeMarkProjectOnboardingComplete(); // Install shell completions (ant-only, since the completion command is ant-only) - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { result += await setupShellCompletion(theme); } return result; diff --git a/src/commands/thinkback/thinkback.tsx b/src/commands/thinkback/thinkback.tsx index a8bb1ee..2c50b1b 100644 --- a/src/commands/thinkback/thinkback.tsx +++ b/src/commands/thinkback/thinkback.tsx @@ -29,10 +29,10 @@ const INTERNAL_MARKETPLACE_NAME = 'claude-code-marketplace'; const INTERNAL_MARKETPLACE_REPO = 'anthropics/claude-code-marketplace'; const OFFICIAL_MARKETPLACE_REPO = 'anthropics/claude-plugins-official'; function getMarketplaceName(): string { - return ("external" as string) === 'ant' ? INTERNAL_MARKETPLACE_NAME : OFFICIAL_MARKETPLACE_NAME; + return (process.env.USER_TYPE) === 'ant' ? INTERNAL_MARKETPLACE_NAME : OFFICIAL_MARKETPLACE_NAME; } function getMarketplaceRepo(): string { - return ("external" as string) === 'ant' ? INTERNAL_MARKETPLACE_REPO : OFFICIAL_MARKETPLACE_REPO; + return (process.env.USER_TYPE) === 'ant' ? INTERNAL_MARKETPLACE_REPO : OFFICIAL_MARKETPLACE_REPO; } function getPluginId(): string { return `thinkback@${getMarketplaceName()}`; diff --git a/src/commands/ultraplan.tsx b/src/commands/ultraplan.tsx index 0d52baa..a5a9f64 100644 --- a/src/commands/ultraplan.tsx +++ b/src/commands/ultraplan.tsx @@ -53,7 +53,7 @@ const DEFAULT_INSTRUCTIONS: string = (typeof _rawPrompt === 'string' ? _rawPromp // Shell-set env only, so top-level process.env read is fine // — settings.env never injects this. /* eslint-disable custom-rules/no-process-env-top-level, custom-rules/no-sync-fs -- ant-only dev override; eager top-level read is the point (crash at startup, not silently inside the slash-command try/catch) */ -const ULTRAPLAN_INSTRUCTIONS: string = ("external" as string) === 'ant' && process.env.ULTRAPLAN_PROMPT_FILE ? readFileSync(process.env.ULTRAPLAN_PROMPT_FILE, 'utf8').trimEnd() : DEFAULT_INSTRUCTIONS; +const ULTRAPLAN_INSTRUCTIONS: string = (process.env.USER_TYPE) === 'ant' && process.env.ULTRAPLAN_PROMPT_FILE ? readFileSync(process.env.ULTRAPLAN_PROMPT_FILE, 'utf8').trimEnd() : DEFAULT_INSTRUCTIONS; /* eslint-enable custom-rules/no-process-env-top-level, custom-rules/no-sync-fs */ /** @@ -464,7 +464,7 @@ export default { name: 'ultraplan', description: `~10–30 min · Claude Code on the web drafts an advanced plan you can edit and approve. See ${CCR_TERMS_URL}`, argumentHint: '', - isEnabled: () => ("external" as string) === 'ant', + isEnabled: () => (process.env.USER_TYPE) === 'ant', load: () => Promise.resolve({ call }) diff --git a/src/components/DevBar.tsx b/src/components/DevBar.tsx index bce6c2f..bf99f32 100644 --- a/src/components/DevBar.tsx +++ b/src/components/DevBar.tsx @@ -6,7 +6,7 @@ import { Text, useInterval } from '../ink.js'; // Show DevBar for dev builds or all ants function shouldShowDevBar(): boolean { - return ("production" as string) === 'development' || ("external" as string) === 'ant'; + return ("production" as string) === 'development' || (process.env.USER_TYPE) === 'ant'; } export function DevBar() { const $ = _c(5); diff --git a/src/components/Feedback.tsx b/src/components/Feedback.tsx index d03bdbb..18603d6 100644 --- a/src/components/Feedback.tsx +++ b/src/components/Feedback.tsx @@ -32,7 +32,7 @@ import TextInput from './TextInput.js'; // This value was determined experimentally by testing the URL length limit const GITHUB_URL_LIMIT = 7250; -const GITHUB_ISSUES_REPO_URL = ("external" as string) === 'ant' ? 'https://github.com/anthropics/claude-cli-internal/issues' : 'https://github.com/anthropics/claude-code/issues'; +const GITHUB_ISSUES_REPO_URL = (process.env.USER_TYPE) === 'ant' ? 'https://github.com/anthropics/claude-cli-internal/issues' : 'https://github.com/anthropics/claude-code/issues'; type Props = { abortSignal: AbortSignal; messages: Message[]; diff --git a/src/components/FeedbackSurvey/useMemorySurvey.tsx b/src/components/FeedbackSurvey/useMemorySurvey.tsx index e1e6b85..bd28ee6 100644 --- a/src/components/FeedbackSurvey/useMemorySurvey.tsx +++ b/src/components/FeedbackSurvey/useMemorySurvey.tsx @@ -87,7 +87,7 @@ export function useMemorySurvey(messages: Message[], isLoading: boolean, hasActi }); }, []); const shouldShowTranscriptPrompt = useCallback((selected_0: FeedbackSurveyResponse) => { - if (("external" as string) !== 'ant') { + if ((process.env.USER_TYPE) !== 'ant') { return false; } if (selected_0 !== 'bad' && selected_0 !== 'good') { diff --git a/src/components/LogoV2/feedConfigs.tsx b/src/components/LogoV2/feedConfigs.tsx index 71a5526..cf88419 100644 --- a/src/components/LogoV2/feedConfigs.tsx +++ b/src/components/LogoV2/feedConfigs.tsx @@ -26,7 +26,7 @@ export function createRecentActivityFeed(activities: LogOption[]): FeedConfig { } export function createWhatsNewFeed(releaseNotes: string[]): FeedConfig { const lines: FeedLine[] = releaseNotes.map(note => { - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { const match = note.match(/^(\d+\s+\w+\s+ago)\s+(.+)$/); if (match) { return { @@ -39,9 +39,9 @@ export function createWhatsNewFeed(releaseNotes: string[]): FeedConfig { text: note }; }); - const emptyMessage = ("external" as string) === 'ant' ? 'Unable to fetch latest claude-cli-internal commits' : 'Check the Claude Code changelog for updates'; + const emptyMessage = (process.env.USER_TYPE) === 'ant' ? 'Unable to fetch latest claude-cli-internal commits' : 'Check the Claude Code changelog for updates'; return { - title: ("external" as string) === 'ant' ? "What's new [ANT-ONLY: Latest CC commits]" : "What's new", + title: (process.env.USER_TYPE) === 'ant' ? "What's new [ANT-ONLY: Latest CC commits]" : "What's new", lines, footer: lines.length > 0 ? '/release-notes for more' : undefined, emptyMessage diff --git a/src/components/MemoryUsageIndicator.tsx b/src/components/MemoryUsageIndicator.tsx index 5fae7d9..c7b0fef 100644 --- a/src/components/MemoryUsageIndicator.tsx +++ b/src/components/MemoryUsageIndicator.tsx @@ -7,7 +7,7 @@ export function MemoryUsageIndicator(): React.ReactNode { // the hook means the 10s polling interval is never set up in external builds. // USER_TYPE is a build-time constant, so the hook call below is either always // reached or dead-code-eliminated — never conditional at runtime. - if (("external" as string) !== 'ant') { + if ((process.env.USER_TYPE) !== 'ant') { return null; } diff --git a/src/components/MessageSelector.tsx b/src/components/MessageSelector.tsx index 888ec93..8ee0890 100644 --- a/src/components/MessageSelector.tsx +++ b/src/components/MessageSelector.tsx @@ -118,7 +118,7 @@ export function MessageSelector({ ...summarizeInputProps, onChange: setSummarizeFromFeedback }); - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { baseOptions.push({ value: 'summarize_up_to', label: 'Summarize up to here', diff --git a/src/components/NativeAutoUpdater.tsx b/src/components/NativeAutoUpdater.tsx index 2438a11..3d860c2 100644 --- a/src/components/NativeAutoUpdater.tsx +++ b/src/components/NativeAutoUpdater.tsx @@ -184,7 +184,7 @@ export function NativeAutoUpdater({ {autoUpdaterResult?.status === 'install_failed' && ✗ Auto-update failed · Try /status } - {maxVersionIssue && ("external" as string) === 'ant' && + {maxVersionIssue && (process.env.USER_TYPE) === 'ant' && ⚠ Known issue: {maxVersionIssue} · Run{' '} claude rollback --safe to downgrade } diff --git a/src/components/PromptInput/PromptInput.tsx b/src/components/PromptInput/PromptInput.tsx index dbcf36d..bc80851 100644 --- a/src/components/PromptInput/PromptInput.tsx +++ b/src/components/PromptInput/PromptInput.tsx @@ -294,8 +294,8 @@ function PromptInput({ // otherwise bridge becomes an invisible selection stop. const bridgeFooterVisible = replBridgeConnected && (replBridgeExplicit || replBridgeReconnecting); // Tmux pill (ant-only) — visible when there's an active tungsten session - const hasTungstenSession = useAppState(s => ("external" as string) === 'ant' && s.tungstenActiveSession !== undefined); - const tmuxFooterVisible = ("external" as string) === 'ant' && hasTungstenSession; + const hasTungstenSession = useAppState(s => (process.env.USER_TYPE) === 'ant' && s.tungstenActiveSession !== undefined); + const tmuxFooterVisible = (process.env.USER_TYPE) === 'ant' && hasTungstenSession; // WebBrowser pill — visible when a browser is open const bagelFooterVisible = useAppState(s => false); const teamContext = useAppState(s => s.teamContext); @@ -391,7 +391,7 @@ function PromptInput({ // exist. When only local_agent tasks are running (coordinator/fork mode), the // pill is absent, so the -1 sentinel would leave nothing visually selected. // In that case, skip -1 and treat 0 as the minimum selectable index. - const hasBgTaskPill = useMemo(() => Object.values(tasks).some(t => isBackgroundTask(t) && !(("external" as string) === 'ant' && isPanelAgentTask(t))), [tasks]); + const hasBgTaskPill = useMemo(() => Object.values(tasks).some(t => isBackgroundTask(t) && !((process.env.USER_TYPE) === 'ant' && isPanelAgentTask(t))), [tasks]); const minCoordinatorIndex = hasBgTaskPill ? -1 : 0; // Clamp index when tasks complete and the list shrinks beneath the cursor useEffect(() => { @@ -455,7 +455,7 @@ function PromptInput({ // Panel shows retained-completed agents too (getVisibleAgentTasks), so the // pill must stay navigable whenever the panel has rows — not just when // something is running. - const tasksFooterVisible = (runningTaskCount > 0 || ("external" as string) === 'ant' && coordinatorTaskCount > 0) && !shouldHideTasksFooter(tasks, showSpinnerTree); + const tasksFooterVisible = (runningTaskCount > 0 || (process.env.USER_TYPE) === 'ant' && coordinatorTaskCount > 0) && !shouldHideTasksFooter(tasks, showSpinnerTree); const teamsFooterVisible = cachedTeams.length > 0; const footerItems = useMemo(() => [tasksFooterVisible && 'tasks', tmuxFooterVisible && 'tmux', bagelFooterVisible && 'bagel', teamsFooterVisible && 'teams', bridgeFooterVisible && 'bridge', companionFooterVisible && 'companion'].filter(Boolean) as FooterItem[], [tasksFooterVisible, tmuxFooterVisible, bagelFooterVisible, teamsFooterVisible, bridgeFooterVisible, companionFooterVisible]); @@ -1742,7 +1742,7 @@ function PromptInput({ useKeybindings({ 'footer:up': () => { // ↑ scrolls within the coordinator task list before leaving the pill - if (tasksSelected && ("external" as string) === 'ant' && coordinatorTaskCount > 0 && coordinatorTaskIndex > minCoordinatorIndex) { + if (tasksSelected && (process.env.USER_TYPE) === 'ant' && coordinatorTaskCount > 0 && coordinatorTaskIndex > minCoordinatorIndex) { setCoordinatorTaskIndex(prev => prev - 1); return; } @@ -1750,7 +1750,7 @@ function PromptInput({ }, 'footer:down': () => { // ↓ scrolls within the coordinator task list, never leaves the pill - if (tasksSelected && ("external" as string) === 'ant' && coordinatorTaskCount > 0) { + if (tasksSelected && (process.env.USER_TYPE) === 'ant' && coordinatorTaskCount > 0) { if (coordinatorTaskIndex < coordinatorTaskCount - 1) { setCoordinatorTaskIndex(prev => prev + 1); } @@ -1813,7 +1813,7 @@ function PromptInput({ } break; case 'tmux': - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { setAppState(prev => prev.tungstenPanelAutoHidden ? { ...prev, tungstenPanelAutoHidden: false diff --git a/src/components/PromptInput/PromptInputFooter.tsx b/src/components/PromptInput/PromptInputFooter.tsx index 8aacad1..8f23dfd 100644 --- a/src/components/PromptInput/PromptInputFooter.tsx +++ b/src/components/PromptInput/PromptInputFooter.tsx @@ -143,11 +143,11 @@ function PromptInputFooter({ {isFullscreen ? null : } - {("external" as string) === 'ant' && isUndercover() && undercover} + {(process.env.USER_TYPE) === 'ant' && isUndercover() && undercover} - {("external" as string) === 'ant' && } + {(process.env.USER_TYPE) === 'ant' && } ; } export default memo(PromptInputFooter); diff --git a/src/components/PromptInput/PromptInputFooterLeftSide.tsx b/src/components/PromptInput/PromptInputFooterLeftSide.tsx index ed8c672..381dcc5 100644 --- a/src/components/PromptInput/PromptInputFooterLeftSide.tsx +++ b/src/components/PromptInput/PromptInputFooterLeftSide.tsx @@ -260,7 +260,7 @@ function ModeIndicator({ const expandedView = useAppState(s_3 => s_3.expandedView); const showSpinnerTree = expandedView === 'teammates'; const prStatus = usePrStatus(isLoading, isPrStatusEnabled()); - const hasTmuxSession = useAppState(s_4 => ("external" as string) === 'ant' && s_4.tungstenActiveSession !== undefined); + const hasTmuxSession = useAppState(s_4 => (process.env.USER_TYPE) === 'ant' && s_4.tungstenActiveSession !== undefined); const nextTickAt = useSyncExternalStore(proactiveModule?.subscribeToProactiveChanges ?? NO_OP_SUBSCRIBE, proactiveModule?.getNextTickAt ?? NULL, NULL); // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant const voiceEnabled = feature('VOICE_MODE') ? useVoiceEnabled() : false; @@ -274,7 +274,7 @@ function ModeIndicator({ const selGetState = useSelection().getState; const hasNextTick = nextTickAt !== null; const isCoordinator = feature('COORDINATOR_MODE') ? coordinatorModule?.isCoordinatorMode() === true : false; - const runningTaskCount = useMemo(() => count(Object.values(tasks), t => isBackgroundTask(t) && !(("external" as string) === 'ant' && isPanelAgentTask(t))), [tasks]); + const runningTaskCount = useMemo(() => count(Object.values(tasks), t => isBackgroundTask(t) && !((process.env.USER_TYPE) === 'ant' && isPanelAgentTask(t))), [tasks]); const tasksV2 = useTasksV2(); const hasTaskItems = tasksV2 !== undefined && tasksV2.length > 0; const escShortcut = useShortcutDisplay('chat:cancel', 'Chat', 'esc').toLowerCase(); @@ -365,7 +365,7 @@ function ModeIndicator({ // its click-target Box isn't nested inside the // wrapper (reconciler throws on Box-in-Text). // Tmux pill (ant-only) — appears right after tasks in nav order - ...(("external" as string) === 'ant' && hasTmuxSession ? [] : []), ...(isAgentSwarmsEnabled() && hasTeams ? [] : []), ...(shouldShowPrStatus ? [] : [])]; + ...((process.env.USER_TYPE) === 'ant' && hasTmuxSession ? [] : []), ...(isAgentSwarmsEnabled() && hasTeams ? [] : []), ...(shouldShowPrStatus ? [] : [])]; // Check if any in-process teammates exist (for hint text cycling) const hasAnyInProcessTeammates = Object.values(tasks).some(t_2 => t_2.type === 'in_process_teammate' && t_2.status === 'running'); @@ -399,7 +399,7 @@ function ModeIndicator({ } // Add "↓ to manage tasks" hint when panel has visible rows - const hasCoordinatorTasks = ("external" as string) === 'ant' && getVisibleAgentTasks(tasks).length > 0; + const hasCoordinatorTasks = (process.env.USER_TYPE) === 'ant' && getVisibleAgentTasks(tasks).length > 0; // Tasks pill renders as a Box sibling (not a parts entry) so its // click-target Box isn't nested inside — the diff --git a/src/components/Settings/Config.tsx b/src/components/Settings/Config.tsx index df8c71f..7d0595f 100644 --- a/src/components/Settings/Config.tsx +++ b/src/components/Settings/Config.tsx @@ -392,7 +392,7 @@ export function Config({ } }] : []), // Speculation toggle (ant-only) - ...(("external" as string) === 'ant' ? [{ + ...((process.env.USER_TYPE) === 'ant' ? [{ id: 'speculationEnabled', label: 'Speculative execution', value: globalConfig.speculationEnabled ?? true, diff --git a/src/components/Spinner.tsx b/src/components/Spinner.tsx index d1084f2..9ac9ffe 100644 --- a/src/components/Spinner.tsx +++ b/src/components/Spinner.tsx @@ -220,7 +220,7 @@ function SpinnerWithVerbInner({ // doesn't trigger re-renders; we pick up updates on the parent's ~25x/turn // re-render cadence, same as the old ApiMetricsLine did. let ttftText: string | null = null; - if (("external" as string) === 'ant' && apiMetricsRef?.current && apiMetricsRef.current.length > 0) { + if ((process.env.USER_TYPE) === 'ant' && apiMetricsRef?.current && apiMetricsRef.current.length > 0) { ttftText = computeTtftText(apiMetricsRef.current); } diff --git a/src/components/Stats.tsx b/src/components/Stats.tsx index d58b9d2..d3d9521 100644 --- a/src/components/Stats.tsx +++ b/src/components/Stats.tsx @@ -512,7 +512,7 @@ function OverviewTab({ {/* Speculation time saved (ant-only) */} - {("external" as string) === 'ant' && stats.totalSpeculationTimeSavedMs > 0 && + {(process.env.USER_TYPE) === 'ant' && stats.totalSpeculationTimeSavedMs > 0 && Speculation saved:{' '} @@ -1151,7 +1151,7 @@ function renderOverviewToAnsi(stats: ClaudeCodeStats): string[] { lines.push(row('Active days', activeDaysVal, 'Peak hour', peakHourVal)); // Speculation time saved (ant-only) - if (("external" as string) === 'ant' && stats.totalSpeculationTimeSavedMs > 0) { + if ((process.env.USER_TYPE) === 'ant' && stats.totalSpeculationTimeSavedMs > 0) { const label = 'Speculation saved:'.padEnd(COL1_LABEL_WIDTH); lines.push(label + h(formatDuration(stats.totalSpeculationTimeSavedMs))); } diff --git a/src/components/agents/ToolSelector.tsx b/src/components/agents/ToolSelector.tsx index dadecfd..27766ab 100644 --- a/src/components/agents/ToolSelector.tsx +++ b/src/components/agents/ToolSelector.tsx @@ -58,7 +58,7 @@ function getToolBuckets(): ToolBuckets { }, EXECUTION: { name: 'Execution tools', - toolNames: new Set([BashTool.name, ("external" as string) === 'ant' ? TungstenTool.name : undefined].filter(n => n !== undefined)) + toolNames: new Set([BashTool.name, (process.env.USER_TYPE) === 'ant' ? TungstenTool.name : undefined].filter(n => n !== undefined)) }, MCP: { name: 'MCP tools', diff --git a/src/components/messages/AttachmentMessage.tsx b/src/components/messages/AttachmentMessage.tsx index fb34022..3b23cd8 100644 --- a/src/components/messages/AttachmentMessage.tsx +++ b/src/components/messages/AttachmentMessage.tsx @@ -114,7 +114,7 @@ export function AttachmentMessage({ // names — shortId is undefined outside ant builds anyway. const names = attachment.skills.map(s => s.shortId ? `${s.name} [${s.shortId}]` : s.name).join(', '); const firstId = attachment.skills[0]?.shortId; - const hint = ("external" as string) === 'ant' && !isDemoEnv && firstId ? ` · /skill-feedback ${firstId} 1=wrong 2=noisy 3=good [comment]` : ''; + const hint = (process.env.USER_TYPE) === 'ant' && !isDemoEnv && firstId ? ` · /skill-feedback ${firstId} 1=wrong 2=noisy 3=good [comment]` : ''; return {attachment.skills.length} relevant{' '} {plural(attachment.skills.length, 'skill')}: {names} diff --git a/src/components/permissions/BashPermissionRequest/bashToolUseOptions.tsx b/src/components/permissions/BashPermissionRequest/bashToolUseOptions.tsx index 36170b8..f1f7c4a 100644 --- a/src/components/permissions/BashPermissionRequest/bashToolUseOptions.tsx +++ b/src/components/permissions/BashPermissionRequest/bashToolUseOptions.tsx @@ -112,7 +112,7 @@ export function bashToolUseOptions({ // Skip when the editable prefix option is already shown — they serve the // same role and having two identical-looking "don't ask again" inputs is confusing. const editablePrefixShown = options.some(o => o.value === 'yes-prefix-edited'); - if (("external" as string) === 'ant' && !editablePrefixShown && isClassifierPermissionsEnabled() && onClassifierDescriptionChange && !initialClassifierDescriptionEmpty && !descriptionAlreadyExists(classifierDescription ?? '', existingAllowDescriptions) && decisionReason?.type !== 'classifier') { + if ((process.env.USER_TYPE) === 'ant' && !editablePrefixShown && isClassifierPermissionsEnabled() && onClassifierDescriptionChange && !initialClassifierDescriptionEmpty && !descriptionAlreadyExists(classifierDescription ?? '', existingAllowDescriptions) && decisionReason?.type !== 'classifier') { options.push({ type: 'input', label: 'Yes, and don\u2019t ask again for', diff --git a/src/components/tasks/taskStatusUtils.tsx b/src/components/tasks/taskStatusUtils.tsx index 8a113b2..a70cbd6 100644 --- a/src/components/tasks/taskStatusUtils.tsx +++ b/src/components/tasks/taskStatusUtils.tsx @@ -96,7 +96,7 @@ export function shouldHideTasksFooter(tasks: { if (!showSpinnerTree) return false; let hasVisibleTask = false; for (const t of Object.values(tasks) as TaskState[]) { - if (!isBackgroundTask(t) || ("external" as string) === 'ant' && isPanelAgentTask(t)) { + if (!isBackgroundTask(t) || (process.env.USER_TYPE) === 'ant' && isPanelAgentTask(t)) { continue; } hasVisibleTask = true; diff --git a/src/main.tsx b/src/main.tsx index 27cd8a3..ccb6097 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -262,13 +262,10 @@ function isBeingDebugged() { } } -// Exit if we detect node debugging or inspection -if (("external" as string) !== 'ant' && isBeingDebugged()) { - // Use process.exit directly here since we're in the top-level code before imports - // and gracefulShutdown is not yet available - // eslint-disable-next-line custom-rules/no-top-level-side-effects - process.exit(1); -} +// Anti-debugging check disabled for local development +// if ((process.env.USER_TYPE) !== 'ant' && isBeingDebugged()) { +// process.exit(1); +// } /** * Per-session skill/plugin telemetry. Called from both the interactive path @@ -337,7 +334,7 @@ function runMigrations(): void { if (feature('TRANSCRIPT_CLASSIFIER')) { resetAutoModeOptInForDefaultOffer(); } - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { migrateFennecToOpus(); } saveGlobalConfig(prev => prev.migrationVersion === CURRENT_MIGRATION_VERSION ? prev : { @@ -425,7 +422,7 @@ export function startDeferredPrefetches(): void { } // Event loop stall detector — logs when the main thread is blocked >500ms - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { void import('./utils/eventLoopStallDetector.js').then(m => m.startEventLoopStallDetector()); } } @@ -1134,11 +1131,11 @@ async function run(): Promise { const disableSlashCommands = options.disableSlashCommands || false; // Extract tasks mode options (ant-only) - const tasksOption = ("external" as string) === 'ant' && (options as { + const tasksOption = (process.env.USER_TYPE) === 'ant' && (options as { tasks?: boolean | string; }).tasks; const taskListId = tasksOption ? typeof tasksOption === 'string' ? tasksOption : DEFAULT_TASKS_MODE_TASK_LIST_ID : undefined; - if (("external" as string) === 'ant' && taskListId) { + if ((process.env.USER_TYPE) === 'ant' && taskListId) { process.env.CLAUDE_CODE_TASK_LIST_ID = taskListId; } @@ -1528,7 +1525,7 @@ async function run(): Promise { }; // Store the explicit CLI flag so teammates can inherit it setChromeFlagOverride(chromeOpts.chrome); - const enableClaudeInChrome = shouldEnableClaudeInChrome(chromeOpts.chrome) && (("external" as string) === 'ant' || isClaudeAISubscriber()); + const enableClaudeInChrome = shouldEnableClaudeInChrome(chromeOpts.chrome) && ((process.env.USER_TYPE) === 'ant' || isClaudeAISubscriber()); const autoEnableClaudeInChrome = !enableClaudeInChrome && shouldAutoEnableClaudeInChrome(); if (enableClaudeInChrome) { const platform = getPlatform(); @@ -1760,7 +1757,7 @@ async function run(): Promise { } = initResult; // Handle overly broad shell allow rules for ant users (Bash(*), PowerShell(*)) - if (("external" as string) === 'ant' && overlyBroadBashPermissions.length > 0) { + if ((process.env.USER_TYPE) === 'ant' && overlyBroadBashPermissions.length > 0) { for (const permission of overlyBroadBashPermissions) { logForDebugging(`Ignoring overly broad shell permission ${permission.ruleDisplay} from ${permission.sourceDisplay}`); } @@ -2010,7 +2007,7 @@ async function run(): Promise { // - no env override (which short-circuits _CACHED_MAY_BE_STALE before disk) // - flag absent from disk (== null also catches pre-#22279 poisoned null) const explicitModel = options.model || process.env.ANTHROPIC_MODEL; - if (("external" as string) === 'ant' && explicitModel && explicitModel !== 'default' && !hasGrowthBookEnvOverride('tengu_ant_model_override') && getGlobalConfig().cachedGrowthBookFeatures?.['tengu_ant_model_override'] == null) { + if ((process.env.USER_TYPE) === 'ant' && explicitModel && explicitModel !== 'default' && !hasGrowthBookEnvOverride('tengu_ant_model_override') && getGlobalConfig().cachedGrowthBookFeatures?.['tengu_ant_model_override'] == null) { await initializeGrowthBook(); } @@ -2156,7 +2153,7 @@ async function run(): Promise { // Log agent memory loaded event for tmux teammates if (customAgent.memory) { logEvent('tengu_agent_memory_loaded', { - ...(("external" as string) === 'ant' && { + ...((process.env.USER_TYPE) === 'ant' && { agent_type: customAgent.agentType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS }), scope: customAgent.memory as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, @@ -2220,7 +2217,7 @@ async function run(): Promise { getFpsMetrics = ctx.getFpsMetrics; stats = ctx.stats; // Install asciicast recorder before Ink mounts (ant-only, opt-in via CLAUDE_CODE_TERMINAL_RECORDING=1) - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { installAsciicastRecorder(); } const { @@ -2816,7 +2813,7 @@ async function run(): Promise { if (!isBareMode()) { startDeferredPrefetches(); void import('./utils/backgroundHousekeeping.js').then(m => m.startBackgroundHousekeeping()); - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { void import('./utils/sdkHeapDumpMonitor.js').then(m => m.startSdkMemoryMonitor()); } } @@ -3061,7 +3058,7 @@ async function run(): Promise { // - Runtime: uploader checks github.com/anthropics/* remote + gcloud auth. // - Safety: CLAUDE_CODE_DISABLE_SESSION_DATA_UPLOAD=1 bypasses (tests set this). // Import is dynamic + async to avoid adding startup latency. - const sessionUploaderPromise = ("external" as string) === 'ant' ? import('./utils/sessionDataUploader.js') : null; + const sessionUploaderPromise = (process.env.USER_TYPE) === 'ant' ? import('./utils/sessionDataUploader.js') : null; // Defer session uploader resolution to the onTurnComplete callback to avoid // adding a new top-level await in main.tsx (performance-critical path). @@ -3578,7 +3575,7 @@ async function run(): Promise { } } } - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { if (options.resume && typeof options.resume === 'string' && !maybeSessionId) { // Check for ccshare URL (e.g. https://go/ccshare/boris-20260311-211036) const { @@ -3813,7 +3810,7 @@ async function run(): Promise { if (canUserConfigureAdvisor()) { program.addOption(new Option('--advisor ', 'Enable the server-side advisor tool with the specified model (alias or full ID).').hideHelp()); } - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { program.addOption(new Option('--delegate-permissions', '[ANT-ONLY] Alias for --permission-mode auto.').implies({ permissionMode: 'auto' })); @@ -4367,7 +4364,7 @@ async function run(): Promise { }); // claude up — run the project's CLAUDE.md "# claude up" setup instructions. - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { program.command('up').description('[ANT-ONLY] Initialize or upgrade the local dev environment using the "# claude up" section of the nearest CLAUDE.md').action(async () => { const { up @@ -4378,7 +4375,7 @@ async function run(): Promise { // claude rollback (ant-only) // Rolls back to previous releases - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { program.command('rollback [target]').description('[ANT-ONLY] Roll back to a previous release\n\nExamples:\n claude rollback Go 1 version back from current\n claude rollback 3 Go 3 versions back from current\n claude rollback 2.0.73-dev.20251217.t190658 Roll back to a specific version').option('-l, --list', 'List recent published versions with ages').option('--dry-run', 'Show what would be installed without installing').option('--safe', 'Roll back to the server-pinned safe version (set by oncall during incidents)').action(async (target?: string, options?: { list?: boolean; dryRun?: boolean; @@ -4402,7 +4399,7 @@ async function run(): Promise { }); // ant-only commands - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { const validateLogId = (value: string) => { const maybeSessionId = validateUuid(value); if (maybeSessionId) return maybeSessionId; @@ -4436,7 +4433,7 @@ Examples: } = await import('./cli/handlers/ant.js'); await exportHandler(source, outputFile); }); - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { const taskCmd = program.command('task').description('[ANT-ONLY] Manage task list tasks'); taskCmd.command('create ').description('Create a new task').option('-d, --description ', 'Task description').option('-l, --list ', 'Task list ID (defaults to "tasklist")').action(async (subject: string, opts: { description?: string; @@ -4595,7 +4592,7 @@ async function logTenguInit({ assistantActivationPath: assistantActivationPath as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS }), autoUpdatesChannel: (getInitialSettings().autoUpdatesChannel ?? 'latest') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, - ...(("external" as string) === 'ant' ? (() => { + ...((process.env.USER_TYPE) === 'ant' ? (() => { const cwd = getCwd(); const gitRoot = findGitRoot(cwd); const rp = gitRoot ? relative(gitRoot, cwd) || '.' : undefined; diff --git a/src/screens/REPL.tsx b/src/screens/REPL.tsx index 858b2f9..8abe3a4 100644 --- a/src/screens/REPL.tsx +++ b/src/screens/REPL.tsx @@ -104,13 +104,13 @@ const VoiceKeybindingHandler: typeof import('../hooks/useVoiceIntegration.js').V // Frustration detection is ant-only (dogfooding). Conditional require so external // builds eliminate the module entirely (including its two O(n) useMemos that run // on every messages change, plus the GrowthBook fetch). -const useFrustrationDetection: typeof import('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection = ("external" as string) === 'ant' ? require('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection : () => ({ +const useFrustrationDetection: typeof import('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection = (process.env.USER_TYPE) === 'ant' ? require('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection : () => ({ state: 'closed', handleTranscriptSelect: () => {} }); // Ant-only org warning. Conditional require so the org UUID list is // eliminated from external builds (one UUID is on excluded-strings). -const useAntOrgWarningNotification: typeof import('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification = ("external" as string) === 'ant' ? require('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification : () => {}; +const useAntOrgWarningNotification: typeof import('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification = (process.env.USER_TYPE) === 'ant' ? require('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification : () => {}; // Dead code elimination: conditional import for coordinator mode const getCoordinatorUserContext: (mcpClients: ReadonlyArray<{ name: string; @@ -219,9 +219,9 @@ import { EffortCallout, shouldShowEffortCallout } from '../components/EffortCall import type { EffortValue } from '../utils/effort.js'; import { RemoteCallout } from '../components/RemoteCallout.js'; /* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */ -const AntModelSwitchCallout = ("external" as string) === 'ant' ? require('../components/AntModelSwitchCallout.js').AntModelSwitchCallout : null; -const shouldShowAntModelSwitch = ("external" as string) === 'ant' ? require('../components/AntModelSwitchCallout.js').shouldShowModelSwitchCallout : (): boolean => false; -const UndercoverAutoCallout = ("external" as string) === 'ant' ? require('../components/UndercoverAutoCallout.js').UndercoverAutoCallout : null; +const AntModelSwitchCallout = (process.env.USER_TYPE) === 'ant' ? require('../components/AntModelSwitchCallout.js').AntModelSwitchCallout : null; +const shouldShowAntModelSwitch = (process.env.USER_TYPE) === 'ant' ? require('../components/AntModelSwitchCallout.js').shouldShowModelSwitchCallout : (): boolean => false; +const UndercoverAutoCallout = (process.env.USER_TYPE) === 'ant' ? require('../components/UndercoverAutoCallout.js').UndercoverAutoCallout : null; /* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */ import { activityManager } from '../utils/activityManager.js'; import { createAbortController } from '../utils/abortController.js'; @@ -602,7 +602,7 @@ export function REPL({ // Env-var gates hoisted to mount-time — isEnvTruthy does toLowerCase+trim+ // includes, and these were on the render path (hot during PageUp spam). const titleDisabled = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_TERMINAL_TITLE), []); - const moreRightEnabled = useMemo(() => ("external" as string) === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []); + const moreRightEnabled = useMemo(() => (process.env.USER_TYPE) === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []); const disableVirtualScroll = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_VIRTUAL_SCROLL), []); const disableMessageActions = feature('MESSAGE_ACTIONS') ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant @@ -734,7 +734,7 @@ export function REPL({ const [showIdeOnboarding, setShowIdeOnboarding] = useState(false); // Dead code elimination: model switch callout state (ant-only) const [showModelSwitchCallout, setShowModelSwitchCallout] = useState(() => { - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { return shouldShowAntModelSwitch(); } return false; @@ -1013,7 +1013,7 @@ export function REPL({ }, []); const [showUndercoverCallout, setShowUndercoverCallout] = useState(false); useEffect(() => { - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { void (async () => { // Wait for repo classification to settle (memoized, no-op if primed). const { @@ -2045,10 +2045,10 @@ export function REPL({ if (allowDialogsWithAnimation && showIdeOnboarding) return 'ide-onboarding'; // Model switch callout (ant-only, eliminated from external builds) - if (("external" as string) === 'ant' && allowDialogsWithAnimation && showModelSwitchCallout) return 'model-switch'; + if ((process.env.USER_TYPE) === 'ant' && allowDialogsWithAnimation && showModelSwitchCallout) return 'model-switch'; // Undercover auto-enable explainer (ant-only, eliminated from external builds) - if (("external" as string) === 'ant' && allowDialogsWithAnimation && showUndercoverCallout) return 'undercover-callout'; + if ((process.env.USER_TYPE) === 'ant' && allowDialogsWithAnimation && showUndercoverCallout) return 'undercover-callout'; // Effort callout (shown once for Opus 4.6 users when effort is enabled) if (allowDialogsWithAnimation && showEffortCallout) return 'effort-callout'; @@ -2486,7 +2486,7 @@ export function REPL({ dynamicSkillDirTriggers: new Set(), discoveredSkillNames: discoveredSkillNamesRef.current, setResponseLength, - pushApiMetricsEntry: ("external" as string) === 'ant' ? (ttftMs: number) => { + pushApiMetricsEntry: (process.env.USER_TYPE) === 'ant' ? (ttftMs: number) => { const now = Date.now(); const baseline = responseLengthRef.current; apiMetricsRef.current.push({ @@ -2815,7 +2815,7 @@ export function REPL({ // Capture ant-only API metrics before resetLoadingState clears the ref. // For multi-request turns (tool use loops), compute P50 across all requests. - if (("external" as string) === 'ant' && apiMetricsRef.current.length > 0) { + if ((process.env.USER_TYPE) === 'ant' && apiMetricsRef.current.length > 0) { const entries = apiMetricsRef.current; const ttfts = entries.map(e => e.ttftMs); // Compute per-request OTPS using only active streaming time and @@ -2943,7 +2943,7 @@ export function REPL({ // minutes — wiping the session made the pill disappear entirely, forcing // the user to re-invoke Tmux just to peek. Skip on abort so the panel // stays open for inspection (matches the turn-duration guard below). - if (("external" as string) === 'ant' && !abortController.signal.aborted) { + if ((process.env.USER_TYPE) === 'ant' && !abortController.signal.aborted) { setAppState(prev => { if (prev.tungstenActiveSession === undefined) return prev; if (prev.tungstenPanelAutoHidden === true) return prev; @@ -3066,7 +3066,7 @@ export function REPL({ } // Atomically: clear initial message, set permission mode and rules, and store plan for verification - const shouldStorePlanForVerification = initialMsg.message.planContent && ("external" as string) === 'ant' && isEnvTruthy(undefined); + const shouldStorePlanForVerification = initialMsg.message.planContent && (process.env.USER_TYPE) === 'ant' && isEnvTruthy(undefined); setAppState(prev => { // Build and apply permission updates (mode + allowedPrompts rules) let updatedToolPermissionContext = initialMsg.mode ? applyPermissionUpdates(prev.toolPermissionContext, buildPermissionUpdates(initialMsg.mode, initialMsg.allowedPrompts)) : prev.toolPermissionContext; @@ -3599,7 +3599,7 @@ export function REPL({ // Handler for when user presses 1 on survey thanks screen to share details const handleSurveyRequestFeedback = useCallback(() => { - const command = ("external" as string) === 'ant' ? '/issue' : '/feedback'; + const command = (process.env.USER_TYPE) === 'ant' ? '/issue' : '/feedback'; onSubmit(command, { setCursorOffset: () => {}, clearBuffer: () => {}, @@ -4060,7 +4060,7 @@ export function REPL({ // - Workers receive permission responses via mailbox messages // - Leaders receive permission requests via mailbox messages - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { // Tasks mode: watch for tasks and auto-process them // eslint-disable-next-line react-hooks/rules-of-hooks // biome-ignore lint/correctness/useHookAtTopLevel: conditional for dead code elimination in external builds @@ -4169,7 +4169,7 @@ export function REPL({ // Fall back to default behavior const hookType = currentHooks[0]?.data.hookEvent === 'SubagentStop' ? 'subagent stop' : 'stop'; - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { const cmd = currentHooks[completedCount]?.data.command; const label = cmd ? ` '${truncateToWidth(cmd, 40)}'` : ''; return total === 1 ? `running ${hookType} hook${label}` : `running ${hookType} hook${label}\u2026 ${completedCount}/${total}`; @@ -4578,7 +4578,7 @@ export function REPL({ {toolJSX && !(toolJSX.isLocalJSXCommand && toolJSX.isImmediate) && !toolJsxCentered && {toolJSX.jsx} } - {("external" as string) === 'ant' && } + {(process.env.USER_TYPE) === 'ant' && } {feature('WEB_BROWSER_TOOL') ? WebBrowserPanelModule && : null} {showSpinner && 0} leaderIsIdle={!isLoading} />} @@ -4801,7 +4801,7 @@ export function REPL({ }); }} />} {focusedInputDialog === 'ide-onboarding' && setShowIdeOnboarding(false)} installationStatus={ideInstallationStatus} />} - {("external" as string) === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && { + {(process.env.USER_TYPE) === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && { setShowModelSwitchCallout(false); if (selection === 'switch' && modelAlias) { setAppState(prev => ({ @@ -4811,7 +4811,7 @@ export function REPL({ })); } }} />} - {("external" as string) === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && setShowUndercoverCallout(false)} />} + {(process.env.USER_TYPE) === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && setShowUndercoverCallout(false)} />} {focusedInputDialog === 'effort-callout' && { setShowEffortCallout(false); if (selection !== 'dismiss') { @@ -4894,7 +4894,7 @@ export function REPL({ {/* Frustration-triggered transcript sharing prompt */} {frustrationDetection.state !== 'closed' && {}} handleTranscriptSelect={frustrationDetection.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} />} {/* Skill improvement survey - appears when improvements detected (ant-only) */} - {("external" as string) === 'ant' && skillImprovementSurvey.suggestion && } + {(process.env.USER_TYPE) === 'ant' && skillImprovementSurvey.suggestion && } {showIssueFlagBanner && } {} } - {("external" as string) === 'ant' && } + {(process.env.USER_TYPE) === 'ant' && } {feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? : null} } /> diff --git a/src/tools/AgentTool/AgentTool.tsx b/src/tools/AgentTool/AgentTool.tsx index 0851421..709f31e 100644 --- a/src/tools/AgentTool/AgentTool.tsx +++ b/src/tools/AgentTool/AgentTool.tsx @@ -96,7 +96,7 @@ const fullInputSchema = lazySchema(() => { mode: permissionModeSchema().optional().describe('Permission mode for spawned teammate (e.g., "plan" to require plan approval).') }); return baseInputSchema().merge(multiAgentInputSchema).extend({ - isolation: (("external" as string) === 'ant' ? z.enum(['worktree', 'remote']) : z.enum(['worktree'])).optional().describe(("external" as string) === 'ant' ? 'Isolation mode. "worktree" creates a temporary git worktree so the agent works on an isolated copy of the repo. "remote" launches the agent in a remote CCR environment (always runs in background).' : 'Isolation mode. "worktree" creates a temporary git worktree so the agent works on an isolated copy of the repo.'), + isolation: ((process.env.USER_TYPE) === 'ant' ? z.enum(['worktree', 'remote']) : z.enum(['worktree'])).optional().describe((process.env.USER_TYPE) === 'ant' ? 'Isolation mode. "worktree" creates a temporary git worktree so the agent works on an isolated copy of the repo. "remote" launches the agent in a remote CCR environment (always runs in background).' : 'Isolation mode. "worktree" creates a temporary git worktree so the agent works on an isolated copy of the repo.'), cwd: z.string().optional().describe('Absolute path to run the agent in. Overrides the working directory for all filesystem and shell operations within this agent. Mutually exclusive with isolation: "worktree".') }); }); @@ -432,7 +432,7 @@ export const AgentTool = buildTool({ // Remote isolation: delegate to CCR. Gated ant-only — the guard enables // dead code elimination of the entire block for external builds. - if (("external" as string) === 'ant' && effectiveIsolation === 'remote') { + if ((process.env.USER_TYPE) === 'ant' && effectiveIsolation === 'remote') { const eligibility = await checkRemoteAgentEligibility(); if (!eligibility.eligible) { const reasons = (eligibility as { eligible: false; errors: Parameters[0][] }).errors.map(formatPreconditionError).join('\n'); @@ -522,7 +522,7 @@ export const AgentTool = buildTool({ // Log agent memory loaded event for subagents if (selectedAgent.memory) { logEvent('tengu_agent_memory_loaded', { - ...(("external" as string) === 'ant' && { + ...((process.env.USER_TYPE) === 'ant' && { agent_type: selectedAgent.agentType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS }), scope: selectedAgent.memory as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, @@ -1284,7 +1284,7 @@ export const AgentTool = buildTool({ // Only route through auto mode classifier when in auto mode // In all other modes, auto-approve sub-agent generation // Note: "external" === 'ant' guard enables dead code elimination for external builds - if (("external" as string) === 'ant' && appState.toolPermissionContext.mode === 'auto') { + if ((process.env.USER_TYPE) === 'ant' && appState.toolPermissionContext.mode === 'auto') { return { behavior: 'passthrough', message: 'Agent tool requires permission to spawn sub-agents.' diff --git a/src/tools/AgentTool/UI.tsx b/src/tools/AgentTool/UI.tsx index 368b35f..ff0eb63 100644 --- a/src/tools/AgentTool/UI.tsx +++ b/src/tools/AgentTool/UI.tsx @@ -99,7 +99,7 @@ type ProcessedMessage = { */ function processProgressMessages(messages: ProgressMessage[], tools: Tools, isAgentRunning: boolean): ProcessedMessage[] { // Only process for ants - if (("external" as string) !== 'ant') { + if ((process.env.USER_TYPE) !== 'ant') { return messages.filter((m): m is ProgressMessage => hasProgressMessage(m.data) && m.data.message.type !== 'user').map(m => ({ type: 'original', message: m @@ -385,7 +385,7 @@ export function renderToolResultMessage(data: Output, progressMessagesForMessage } as import('@anthropic-ai/sdk/resources/beta/messages/messages.mjs').BetaUsage }); return - {("external" as string) === 'ant' && + {(process.env.USER_TYPE) === 'ant' && [ANT-ONLY] API calls: {getDisplayPath(getDumpPromptsPath(agentId))} @@ -591,7 +591,7 @@ export function renderToolUseRejectedMessage(_input: { const firstData = progressMessagesForMessage[0]?.data; const agentId = firstData && hasProgressMessage(firstData) ? firstData.agentId : undefined; return <> - {("external" as string) === 'ant' && agentId && + {(process.env.USER_TYPE) === 'ant' && agentId && [ANT-ONLY] API calls: {getDisplayPath(getDumpPromptsPath(agentId))} diff --git a/src/tools/TaskOutputTool/TaskOutputTool.tsx b/src/tools/TaskOutputTool/TaskOutputTool.tsx index 2a28f57..6515729 100644 --- a/src/tools/TaskOutputTool/TaskOutputTool.tsx +++ b/src/tools/TaskOutputTool/TaskOutputTool.tsx @@ -161,7 +161,7 @@ export const TaskOutputTool: Tool = buildTool return this.isReadOnly?.(_input) ?? false; }, isEnabled() { - return ("external" as string) !== 'ant'; + return (process.env.USER_TYPE) !== 'ant'; }, isReadOnly(_input) { return true; diff --git a/src/tools/TaskStopTool/UI.tsx b/src/tools/TaskStopTool/UI.tsx index 16862ca..c6568f5 100644 --- a/src/tools/TaskStopTool/UI.tsx +++ b/src/tools/TaskStopTool/UI.tsx @@ -25,7 +25,7 @@ export function renderToolResultMessage(output: Output, _progressMessagesForMess }: { verbose: boolean; }): React.ReactNode { - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { return null; } const rawCommand = output.command ?? ''; diff --git a/src/utils/autoRunIssue.tsx b/src/utils/autoRunIssue.tsx index 287d647..892fd6f 100644 --- a/src/utils/autoRunIssue.tsx +++ b/src/utils/autoRunIssue.tsx @@ -81,7 +81,7 @@ export type AutoRunIssueReason = 'feedback_survey_bad' | 'feedback_survey_good'; */ export function shouldAutoRunIssue(reason: AutoRunIssueReason): boolean { // Only for Ant users - if (("external" as string) !== 'ant') { + if ((process.env.USER_TYPE) !== 'ant') { return false; } switch (reason) { @@ -100,7 +100,7 @@ export function shouldAutoRunIssue(reason: AutoRunIssueReason): boolean { */ export function getAutoRunCommand(reason: AutoRunIssueReason): string { // Only ant builds have the /good-claude command - if (("external" as string) === 'ant' && reason === 'feedback_survey_good') { + if ((process.env.USER_TYPE) === 'ant' && reason === 'feedback_survey_good') { return '/good-claude'; } return '/issue'; diff --git a/src/utils/processUserInput/processSlashCommand.tsx b/src/utils/processUserInput/processSlashCommand.tsx index bb7b13c..5d231c1 100644 --- a/src/utils/processUserInput/processSlashCommand.tsx +++ b/src/utils/processUserInput/processSlashCommand.tsx @@ -273,7 +273,7 @@ async function executeForkedSlashCommand(command: CommandBase & PromptCommand, a logForDebugging(`Forked slash command /${command.name} completed with agent ${agentId}`); // Prepend debug log for ant users so it appears inside the command output - if (("external" as string) === 'ant') { + if ((process.env.USER_TYPE) === 'ant') { resultText = `[ANT-ONLY] API calls: ${getDisplayPath(getDumpPromptsPath(agentId))}\n${resultText}`; } @@ -427,7 +427,7 @@ export async function processSlashCommand(inputString: string, precedingInputBlo logEvent('tengu_input_command', { ...eventData, invocation_trigger: 'user-slash' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, - ...(("external" as string) === 'ant' && { + ...((process.env.USER_TYPE) === 'ant' && { skill_name: commandName as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, ...(returnedCommand.type === 'prompt' && { skill_source: returnedCommand.source as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS @@ -495,7 +495,7 @@ export async function processSlashCommand(inputString: string, precedingInputBlo logEvent('tengu_input_command', { ...eventData, invocation_trigger: 'user-slash' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, - ...(("external" as string) === 'ant' && { + ...((process.env.USER_TYPE) === 'ant' && { skill_name: commandName as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, ...(returnedCommand.type === 'prompt' && { skill_source: returnedCommand.source as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS diff --git a/src/utils/status.tsx b/src/utils/status.tsx index 41dcc78..39afd3a 100644 --- a/src/utils/status.tsx +++ b/src/utils/status.tsx @@ -26,7 +26,7 @@ export type Property = { }; export type Diagnostic = React.ReactNode; export function buildSandboxProperties(): Property[] { - if (("external" as string) !== 'ant') { + if ((process.env.USER_TYPE) !== 'ant') { return []; } const isSandboxed = SandboxManager.isSandboxingEnabled();