feat: activate buddy system in open build (#346)
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
* distributable JS file using Bun's bundler.
|
* distributable JS file using Bun's bundler.
|
||||||
*
|
*
|
||||||
* Handles:
|
* Handles:
|
||||||
* - bun:bundle feature() flags → all false (disables internal-only features)
|
* - bun:bundle feature() flags for the open build
|
||||||
* - MACRO.* globals → inlined version/build-time constants
|
* - MACRO.* globals → inlined version/build-time constants
|
||||||
* - src/ path aliases
|
* - src/ path aliases
|
||||||
*/
|
*/
|
||||||
@@ -14,8 +14,9 @@ import { noTelemetryPlugin } from './no-telemetry-plugin'
|
|||||||
const pkg = JSON.parse(readFileSync('./package.json', 'utf-8'))
|
const pkg = JSON.parse(readFileSync('./package.json', 'utf-8'))
|
||||||
const version = pkg.version
|
const version = pkg.version
|
||||||
|
|
||||||
// Feature flags — all disabled for the open build.
|
// Feature flags for the open build.
|
||||||
// These gate Anthropic-internal features (voice, proactive, kairos, etc.)
|
// Most Anthropic-internal features stay off; open-build features can be
|
||||||
|
// selectively enabled here when their full source exists in the mirror.
|
||||||
const featureFlags: Record<string, boolean> = {
|
const featureFlags: Record<string, boolean> = {
|
||||||
VOICE_MODE: false,
|
VOICE_MODE: false,
|
||||||
PROACTIVE: false,
|
PROACTIVE: false,
|
||||||
@@ -37,7 +38,7 @@ const featureFlags: Record<string, boolean> = {
|
|||||||
TRANSCRIPT_CLASSIFIER: false,
|
TRANSCRIPT_CLASSIFIER: false,
|
||||||
WEB_BROWSER_TOOL: false,
|
WEB_BROWSER_TOOL: false,
|
||||||
MESSAGE_ACTIONS: false,
|
MESSAGE_ACTIONS: false,
|
||||||
BUDDY: false,
|
BUDDY: true,
|
||||||
CHICAGO_MCP: false,
|
CHICAGO_MCP: false,
|
||||||
COWORKER_TYPE_TELEMETRY: false,
|
COWORKER_TYPE_TELEMETRY: false,
|
||||||
}
|
}
|
||||||
@@ -110,7 +111,7 @@ export async function handleBgFlag() { throw new Error("Background sessions are
|
|||||||
build.onLoad(
|
build.onLoad(
|
||||||
{ filter: /.*/, namespace: 'bun-bundle-shim' },
|
{ filter: /.*/, namespace: 'bun-bundle-shim' },
|
||||||
() => ({
|
() => ({
|
||||||
contents: `export function feature(name) { return false; }`,
|
contents: `const featureFlags = ${JSON.stringify(featureFlags)};\nexport function feature(name) { return featureFlags[name] ?? false; }`,
|
||||||
loader: 'js',
|
loader: 'js',
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { c as _c } from "react-compiler-runtime";
|
import { c as _c } from "react-compiler-runtime";
|
||||||
import { feature } from 'bun:bundle';
|
|
||||||
import figures from 'figures';
|
import figures from 'figures';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
||||||
@@ -11,6 +10,7 @@ import { getGlobalConfig } from '../utils/config.js';
|
|||||||
import { isFullscreenActive } from '../utils/fullscreen.js';
|
import { isFullscreenActive } from '../utils/fullscreen.js';
|
||||||
import type { Theme } from '../utils/theme.js';
|
import type { Theme } from '../utils/theme.js';
|
||||||
import { getCompanion } from './companion.js';
|
import { getCompanion } from './companion.js';
|
||||||
|
import { isBuddyEnabled } from './feature.js';
|
||||||
import { renderFace, renderSprite, spriteFrameCount } from './sprites.js';
|
import { renderFace, renderSprite, spriteFrameCount } from './sprites.js';
|
||||||
import { RARITY_COLORS } from './types.js';
|
import { RARITY_COLORS } from './types.js';
|
||||||
const TICK_MS = 500;
|
const TICK_MS = 500;
|
||||||
@@ -165,7 +165,7 @@ function spriteColWidth(nameWidth: number): number {
|
|||||||
// Narrow terminals: 0 — REPL.tsx stacks the one-liner on its own row
|
// Narrow terminals: 0 — REPL.tsx stacks the one-liner on its own row
|
||||||
// (above input in fullscreen, below in scrollback), so no reservation.
|
// (above input in fullscreen, below in scrollback), so no reservation.
|
||||||
export function companionReservedColumns(terminalColumns: number, speaking: boolean): number {
|
export function companionReservedColumns(terminalColumns: number, speaking: boolean): number {
|
||||||
if (!feature('BUDDY')) return 0;
|
if (!isBuddyEnabled()) return 0;
|
||||||
const companion = getCompanion();
|
const companion = getCompanion();
|
||||||
if (!companion || getGlobalConfig().companionMuted) return 0;
|
if (!companion || getGlobalConfig().companionMuted) return 0;
|
||||||
if (terminalColumns < MIN_COLS_FOR_FULL_SPRITE) return 0;
|
if (terminalColumns < MIN_COLS_FOR_FULL_SPRITE) return 0;
|
||||||
@@ -212,7 +212,7 @@ export function CompanionSprite(): React.ReactNode {
|
|||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- tick intentionally captured at reaction-change, not tracked
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- tick intentionally captured at reaction-change, not tracked
|
||||||
}, [reaction, setAppState]);
|
}, [reaction, setAppState]);
|
||||||
if (!feature('BUDDY')) return null;
|
if (!isBuddyEnabled()) return null;
|
||||||
const companion = getCompanion();
|
const companion = getCompanion();
|
||||||
if (!companion || getGlobalConfig().companionMuted) return null;
|
if (!companion || getGlobalConfig().companionMuted) return null;
|
||||||
const color = RARITY_COLORS[companion.rarity];
|
const color = RARITY_COLORS[companion.rarity];
|
||||||
@@ -337,7 +337,7 @@ export function CompanionFloatingBubble() {
|
|||||||
t3 = $[4];
|
t3 = $[4];
|
||||||
}
|
}
|
||||||
useEffect(t2, t3);
|
useEffect(t2, t3);
|
||||||
if (!feature("BUDDY") || !reaction) {
|
if (!isBuddyEnabled() || !reaction) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const companion = getCompanion();
|
const companion = getCompanion();
|
||||||
|
|||||||
3
src/buddy/feature.ts
Normal file
3
src/buddy/feature.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function isBuddyEnabled(): boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
65
src/buddy/observer.ts
Normal file
65
src/buddy/observer.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import type { Message } from '../types/message.js'
|
||||||
|
import { getGlobalConfig } from '../utils/config.js'
|
||||||
|
import { getUserMessageText } from '../utils/messages.js'
|
||||||
|
import { getCompanion } from './companion.js'
|
||||||
|
|
||||||
|
const DIRECT_REPLIES = [
|
||||||
|
'I am observing.',
|
||||||
|
'I am helping from the corner.',
|
||||||
|
'I saw that.',
|
||||||
|
'Still here.',
|
||||||
|
'Watching closely.',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const PET_REPLIES = [
|
||||||
|
'happy chirp',
|
||||||
|
'tiny victory dance',
|
||||||
|
'quietly approves',
|
||||||
|
'wiggles with joy',
|
||||||
|
'looks pleased',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
function hashString(s: string): number {
|
||||||
|
let h = 2166136261
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
h ^= s.charCodeAt(i)
|
||||||
|
h = Math.imul(h, 16777619)
|
||||||
|
}
|
||||||
|
return h >>> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickDeterministic<T>(items: readonly T[], seed: string): T {
|
||||||
|
return items[hashString(seed) % items.length]!
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fireCompanionObserver(
|
||||||
|
messages: Message[],
|
||||||
|
onReaction: (reaction: string | undefined) => void,
|
||||||
|
): Promise<void> {
|
||||||
|
const companion = getCompanion()
|
||||||
|
if (!companion || getGlobalConfig().companionMuted) return
|
||||||
|
|
||||||
|
const lastUser = [...messages].reverse().find(msg => msg.type === 'user')
|
||||||
|
if (!lastUser) return
|
||||||
|
|
||||||
|
const text = getUserMessageText(lastUser)?.trim()
|
||||||
|
if (!text) return
|
||||||
|
|
||||||
|
const lower = text.toLowerCase()
|
||||||
|
const companionName = companion.name.toLowerCase()
|
||||||
|
|
||||||
|
if (lower.includes('/buddy')) {
|
||||||
|
onReaction(pickDeterministic(PET_REPLIES, text + companion.name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
lower.includes(companionName) ||
|
||||||
|
lower.includes('buddy') ||
|
||||||
|
lower.includes('companion')
|
||||||
|
) {
|
||||||
|
onReaction(
|
||||||
|
`${companion.name}: ${pickDeterministic(DIRECT_REPLIES, text + companion.personality)}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { feature } from 'bun:bundle'
|
|
||||||
import type { Message } from '../types/message.js'
|
import type { Message } from '../types/message.js'
|
||||||
import type { Attachment } from '../utils/attachments.js'
|
import type { Attachment } from '../utils/attachments.js'
|
||||||
import { getGlobalConfig } from '../utils/config.js'
|
import { getGlobalConfig } from '../utils/config.js'
|
||||||
import { getCompanion } from './companion.js'
|
import { getCompanion } from './companion.js'
|
||||||
|
import { isBuddyEnabled } from './feature.js'
|
||||||
|
|
||||||
export function companionIntroText(name: string, species: string): string {
|
export function companionIntroText(name: string, species: string): string {
|
||||||
return `# Companion
|
return `# Companion
|
||||||
@@ -15,7 +15,7 @@ When the user addresses ${name} directly (by name), its bubble will answer. Your
|
|||||||
export function getCompanionIntroAttachment(
|
export function getCompanionIntroAttachment(
|
||||||
messages: Message[] | undefined,
|
messages: Message[] | undefined,
|
||||||
): Attachment[] {
|
): Attachment[] {
|
||||||
if (!feature('BUDDY')) return []
|
if (!isBuddyEnabled()) return []
|
||||||
const companion = getCompanion()
|
const companion = getCompanion()
|
||||||
if (!companion || getGlobalConfig().companionMuted) return []
|
if (!companion || getGlobalConfig().companionMuted) return []
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { c as _c } from "react-compiler-runtime";
|
import { c as _c } from "react-compiler-runtime";
|
||||||
import { feature } from 'bun:bundle';
|
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useNotifications } from '../context/notifications.js';
|
import { useNotifications } from '../context/notifications.js';
|
||||||
import { Text } from '../ink.js';
|
import { Text } from '../ink.js';
|
||||||
import { getGlobalConfig } from '../utils/config.js';
|
import { getGlobalConfig } from '../utils/config.js';
|
||||||
import { getRainbowColor } from '../utils/thinking.js';
|
import { getRainbowColor } from '../utils/thinking.js';
|
||||||
|
import { isBuddyEnabled } from './feature.js';
|
||||||
|
|
||||||
// Local date, not UTC — 24h rolling wave across timezones. Sustained Twitter
|
// Local date, not UTC — 24h rolling wave across timezones. Sustained Twitter
|
||||||
// buzz instead of a single UTC-midnight spike, gentler on soul-gen load.
|
// buzz instead of a single UTC-midnight spike, gentler on soul-gen load.
|
||||||
@@ -50,7 +50,7 @@ export function useBuddyNotification() {
|
|||||||
let t1;
|
let t1;
|
||||||
if ($[0] !== addNotification || $[1] !== removeNotification) {
|
if ($[0] !== addNotification || $[1] !== removeNotification) {
|
||||||
t0 = () => {
|
t0 = () => {
|
||||||
if (!feature("BUDDY")) {
|
if (!isBuddyEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const config = getGlobalConfig();
|
const config = getGlobalConfig();
|
||||||
@@ -80,7 +80,7 @@ export function findBuddyTriggerPositions(text: string): Array<{
|
|||||||
start: number;
|
start: number;
|
||||||
end: number;
|
end: number;
|
||||||
}> {
|
}> {
|
||||||
if (!feature('BUDDY')) return [];
|
if (!isBuddyEnabled()) return [];
|
||||||
const triggers: Array<{
|
const triggers: Array<{
|
||||||
start: number;
|
start: number;
|
||||||
end: number;
|
end: number;
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import usage from './commands/usage/index.js'
|
|||||||
import theme from './commands/theme/index.js'
|
import theme from './commands/theme/index.js'
|
||||||
import vim from './commands/vim/index.js'
|
import vim from './commands/vim/index.js'
|
||||||
import { feature } from 'bun:bundle'
|
import { feature } from 'bun:bundle'
|
||||||
|
import { isBuddyEnabled } from './buddy/feature.js'
|
||||||
// Dead code elimination: conditional imports
|
// Dead code elimination: conditional imports
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
const proactive =
|
const proactive =
|
||||||
@@ -117,7 +118,7 @@ const forkCmd = feature('FORK_SUBAGENT')
|
|||||||
require('./commands/fork/index.js') as typeof import('./commands/fork/index.js')
|
require('./commands/fork/index.js') as typeof import('./commands/fork/index.js')
|
||||||
).default
|
).default
|
||||||
: null
|
: null
|
||||||
const buddy = feature('BUDDY')
|
const buddy = isBuddyEnabled()
|
||||||
? (
|
? (
|
||||||
require('./commands/buddy/index.js') as typeof import('./commands/buddy/index.js')
|
require('./commands/buddy/index.js') as typeof import('./commands/buddy/index.js')
|
||||||
).default
|
).default
|
||||||
|
|||||||
185
src/commands/buddy/buddy.tsx
Normal file
185
src/commands/buddy/buddy.tsx
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import type { LocalJSXCommandContext, LocalJSXCommandOnDone } from '../../types/command.js'
|
||||||
|
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
|
||||||
|
import { companionUserId, getCompanion, rollWithSeed } from '../../buddy/companion.js'
|
||||||
|
import type { StoredCompanion } from '../../buddy/types.js'
|
||||||
|
import { COMMON_HELP_ARGS, COMMON_INFO_ARGS } from '../../constants/xml.js'
|
||||||
|
|
||||||
|
const NAME_PREFIXES = [
|
||||||
|
'Byte',
|
||||||
|
'Echo',
|
||||||
|
'Glint',
|
||||||
|
'Miso',
|
||||||
|
'Nova',
|
||||||
|
'Pixel',
|
||||||
|
'Rune',
|
||||||
|
'Static',
|
||||||
|
'Vector',
|
||||||
|
'Whisk',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const NAME_SUFFIXES = [
|
||||||
|
'bean',
|
||||||
|
'bit',
|
||||||
|
'bud',
|
||||||
|
'dot',
|
||||||
|
'ling',
|
||||||
|
'loop',
|
||||||
|
'moss',
|
||||||
|
'patch',
|
||||||
|
'puff',
|
||||||
|
'spark',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const PERSONALITIES = [
|
||||||
|
'Curious and quietly encouraging',
|
||||||
|
'A patient little watcher with strong debugging instincts',
|
||||||
|
'Playful, observant, and suspicious of flaky tests',
|
||||||
|
'Calm under pressure and fond of clean diffs',
|
||||||
|
'A tiny terminal gremlin who likes successful builds',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const PET_REACTIONS = [
|
||||||
|
'leans into the headpat',
|
||||||
|
'does a proud little bounce',
|
||||||
|
'emits a content beep',
|
||||||
|
'looks delighted',
|
||||||
|
'wiggles happily',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
function hashString(s: string): number {
|
||||||
|
let h = 2166136261
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
h ^= s.charCodeAt(i)
|
||||||
|
h = Math.imul(h, 16777619)
|
||||||
|
}
|
||||||
|
return h >>> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickDeterministic<T>(items: readonly T[], seed: string): T {
|
||||||
|
return items[hashString(seed) % items.length]!
|
||||||
|
}
|
||||||
|
|
||||||
|
function titleCase(s: string): string {
|
||||||
|
return s.charAt(0).toUpperCase() + s.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStoredCompanion(): StoredCompanion {
|
||||||
|
const userId = companionUserId()
|
||||||
|
const { bones } = rollWithSeed(`${userId}:buddy`)
|
||||||
|
const prefix = pickDeterministic(NAME_PREFIXES, `${userId}:prefix`)
|
||||||
|
const suffix = pickDeterministic(NAME_SUFFIXES, `${userId}:suffix`)
|
||||||
|
const personality = pickDeterministic(PERSONALITIES, `${userId}:personality`)
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: `${prefix}${suffix}`,
|
||||||
|
personality: `${personality}.`,
|
||||||
|
hatchedAt: Date.now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCompanionReaction(
|
||||||
|
context: LocalJSXCommandContext,
|
||||||
|
reaction: string | undefined,
|
||||||
|
pet = false,
|
||||||
|
): void {
|
||||||
|
context.setAppState(prev => ({
|
||||||
|
...prev,
|
||||||
|
companionReaction: reaction,
|
||||||
|
companionPetAt: pet ? Date.now() : prev.companionPetAt,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function showHelp(onDone: LocalJSXCommandOnDone): void {
|
||||||
|
onDone(
|
||||||
|
'Usage: /buddy [status|mute|unmute]\n\nRun /buddy with no args to hatch your companion the first time, then pet it on later runs.',
|
||||||
|
{ display: 'system' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function call(
|
||||||
|
onDone: LocalJSXCommandOnDone,
|
||||||
|
context: LocalJSXCommandContext,
|
||||||
|
args?: string,
|
||||||
|
): Promise<null> {
|
||||||
|
const arg = args?.trim().toLowerCase() ?? ''
|
||||||
|
|
||||||
|
if (COMMON_HELP_ARGS.includes(arg) || arg === '') {
|
||||||
|
const existing = getCompanion()
|
||||||
|
if (arg !== '' || existing) {
|
||||||
|
if (arg !== '') {
|
||||||
|
showHelp(onDone)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (COMMON_HELP_ARGS.includes(arg)) {
|
||||||
|
showHelp(onDone)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (COMMON_INFO_ARGS.includes(arg) || arg === 'status') {
|
||||||
|
const companion = getCompanion()
|
||||||
|
if (!companion) {
|
||||||
|
onDone('No buddy hatched yet. Run /buddy to hatch one.', {
|
||||||
|
display: 'system',
|
||||||
|
})
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
onDone(
|
||||||
|
`${companion.name} is your ${titleCase(companion.rarity)} ${companion.species}. ${companion.personality}`,
|
||||||
|
{ display: 'system' },
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === 'mute' || arg === 'unmute') {
|
||||||
|
const muted = arg === 'mute'
|
||||||
|
saveGlobalConfig(current => ({
|
||||||
|
...current,
|
||||||
|
companionMuted: muted,
|
||||||
|
}))
|
||||||
|
if (muted) {
|
||||||
|
setCompanionReaction(context, undefined)
|
||||||
|
}
|
||||||
|
onDone(`Buddy ${muted ? 'muted' : 'unmuted'}.`, { display: 'system' })
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg !== '') {
|
||||||
|
showHelp(onDone)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let companion = getCompanion()
|
||||||
|
if (!companion) {
|
||||||
|
const stored = createStoredCompanion()
|
||||||
|
saveGlobalConfig(current => ({
|
||||||
|
...current,
|
||||||
|
companion: stored,
|
||||||
|
companionMuted: false,
|
||||||
|
}))
|
||||||
|
companion = {
|
||||||
|
...rollWithSeed(`${companionUserId()}:buddy`).bones,
|
||||||
|
...stored,
|
||||||
|
}
|
||||||
|
setCompanionReaction(
|
||||||
|
context,
|
||||||
|
`${companion.name} the ${companion.species} has hatched.`,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
onDone(
|
||||||
|
`${companion.name} the ${companion.species} is now your buddy. Run /buddy again to pet them.`,
|
||||||
|
{ display: 'system' },
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const reaction = `${companion.name} ${pickDeterministic(
|
||||||
|
PET_REACTIONS,
|
||||||
|
`${Date.now()}:${companion.name}`,
|
||||||
|
)}`
|
||||||
|
setCompanionReaction(context, reaction, true)
|
||||||
|
onDone(undefined, { display: 'skip' })
|
||||||
|
return null
|
||||||
|
}
|
||||||
12
src/commands/buddy/index.ts
Normal file
12
src/commands/buddy/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { Command } from '../../commands.js'
|
||||||
|
|
||||||
|
const buddy = {
|
||||||
|
type: 'local-jsx',
|
||||||
|
name: 'buddy',
|
||||||
|
description: 'Hatch, pet, and manage your Open Claude companion',
|
||||||
|
immediate: true,
|
||||||
|
argumentHint: '[status|mute|unmute|help]',
|
||||||
|
load: () => import('./buddy.js'),
|
||||||
|
} satisfies Command
|
||||||
|
|
||||||
|
export default buddy
|
||||||
@@ -13,6 +13,7 @@ import { getCwd } from 'src/utils/cwd.js';
|
|||||||
import { isQueuedCommandEditable, popAllEditable } from 'src/utils/messageQueueManager.js';
|
import { isQueuedCommandEditable, popAllEditable } from 'src/utils/messageQueueManager.js';
|
||||||
import stripAnsi from 'strip-ansi';
|
import stripAnsi from 'strip-ansi';
|
||||||
import { companionReservedColumns } from '../../buddy/CompanionSprite.js';
|
import { companionReservedColumns } from '../../buddy/CompanionSprite.js';
|
||||||
|
import { isBuddyEnabled } from '../../buddy/feature.js';
|
||||||
import { findBuddyTriggerPositions, useBuddyNotification } from '../../buddy/useBuddyNotification.js';
|
import { findBuddyTriggerPositions, useBuddyNotification } from '../../buddy/useBuddyNotification.js';
|
||||||
import { FastModePicker } from '../../commands/fast/fast.js';
|
import { FastModePicker } from '../../commands/fast/fast.js';
|
||||||
import { isUltrareviewEnabled } from '../../commands/review/ultrareviewEnabled.js';
|
import { isUltrareviewEnabled } from '../../commands/review/ultrareviewEnabled.js';
|
||||||
@@ -309,7 +310,7 @@ function PromptInput({
|
|||||||
const {
|
const {
|
||||||
companion: _companion,
|
companion: _companion,
|
||||||
companionMuted
|
companionMuted
|
||||||
} = feature('BUDDY') ? getGlobalConfig() : {
|
} = isBuddyEnabled() ? getGlobalConfig() : {
|
||||||
companion: undefined,
|
companion: undefined,
|
||||||
companionMuted: undefined
|
companionMuted: undefined
|
||||||
};
|
};
|
||||||
@@ -1786,7 +1787,7 @@ function PromptInput({
|
|||||||
}
|
}
|
||||||
switch (footerItemSelected) {
|
switch (footerItemSelected) {
|
||||||
case 'companion':
|
case 'companion':
|
||||||
if (feature('BUDDY')) {
|
if (isBuddyEnabled()) {
|
||||||
selectFooterItem(null);
|
selectFooterItem(null);
|
||||||
void onSubmit('/buddy');
|
void onSubmit('/buddy');
|
||||||
}
|
}
|
||||||
@@ -1981,8 +1982,7 @@ function PromptInput({
|
|||||||
});
|
});
|
||||||
}, [effortNotificationText, addNotification, removeNotification]);
|
}, [effortNotificationText, addNotification, removeNotification]);
|
||||||
useBuddyNotification();
|
useBuddyNotification();
|
||||||
const companionSpeaking = feature('BUDDY') ?
|
const companionSpeaking = isBuddyEnabled() ?
|
||||||
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
|
|
||||||
useAppState(s => s.companionReaction !== undefined) : false;
|
useAppState(s => s.companionReaction !== undefined) : false;
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
|
|||||||
@@ -275,6 +275,8 @@ const WebBrowserPanelModule = feature('WEB_BROWSER_TOOL') ? require('../tools/We
|
|||||||
import { IssueFlagBanner } from '../components/PromptInput/IssueFlagBanner.js';
|
import { IssueFlagBanner } from '../components/PromptInput/IssueFlagBanner.js';
|
||||||
import { useIssueFlagBanner } from '../hooks/useIssueFlagBanner.js';
|
import { useIssueFlagBanner } from '../hooks/useIssueFlagBanner.js';
|
||||||
import { CompanionSprite, CompanionFloatingBubble, MIN_COLS_FOR_FULL_SPRITE } from '../buddy/CompanionSprite.js';
|
import { CompanionSprite, CompanionFloatingBubble, MIN_COLS_FOR_FULL_SPRITE } from '../buddy/CompanionSprite.js';
|
||||||
|
import { isBuddyEnabled } from '../buddy/feature.js';
|
||||||
|
import { fireCompanionObserver } from '../buddy/observer.js';
|
||||||
import { DevBar } from '../components/DevBar.js';
|
import { DevBar } from '../components/DevBar.js';
|
||||||
// Session manager removed - using AppState now
|
// Session manager removed - using AppState now
|
||||||
import type { RemoteSessionConfig } from '../remote/RemoteSessionManager.js';
|
import type { RemoteSessionConfig } from '../remote/RemoteSessionManager.js';
|
||||||
@@ -1302,7 +1304,7 @@ export function REPL({
|
|||||||
// Dismiss the companion bubble on scroll — it's absolute-positioned
|
// Dismiss the companion bubble on scroll — it's absolute-positioned
|
||||||
// at bottom-right and covers transcript content. Scrolling = user is
|
// at bottom-right and covers transcript content. Scrolling = user is
|
||||||
// trying to read something under it.
|
// trying to read something under it.
|
||||||
if (feature('BUDDY')) {
|
if (isBuddyEnabled()) {
|
||||||
setAppState(prev => prev.companionReaction === undefined ? prev : {
|
setAppState(prev => prev.companionReaction === undefined ? prev : {
|
||||||
...prev,
|
...prev,
|
||||||
companionReaction: undefined
|
companionReaction: undefined
|
||||||
@@ -2806,7 +2808,7 @@ export function REPL({
|
|||||||
})) {
|
})) {
|
||||||
onQueryEvent(event);
|
onQueryEvent(event);
|
||||||
}
|
}
|
||||||
if (feature('BUDDY')) {
|
if (isBuddyEnabled()) {
|
||||||
void fireCompanionObserver(messagesRef.current, reaction => setAppState(prev => prev.companionReaction === reaction ? prev : {
|
void fireCompanionObserver(messagesRef.current, reaction => setAppState(prev => prev.companionReaction === reaction ? prev : {
|
||||||
...prev,
|
...prev,
|
||||||
companionReaction: reaction
|
companionReaction: reaction
|
||||||
@@ -4567,7 +4569,7 @@ export function REPL({
|
|||||||
{feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? <MessageActionsKeybindings handlers={messageActionHandlers} isActive={cursor !== null} /> : null}
|
{feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? <MessageActionsKeybindings handlers={messageActionHandlers} isActive={cursor !== null} /> : null}
|
||||||
<CancelRequestHandler {...cancelRequestProps} />
|
<CancelRequestHandler {...cancelRequestProps} />
|
||||||
<MCPConnectionManager key={remountKey} dynamicMcpConfig={dynamicMcpConfig} isStrictMcpConfig={strictMcpConfig}>
|
<MCPConnectionManager key={remountKey} dynamicMcpConfig={dynamicMcpConfig} isStrictMcpConfig={strictMcpConfig}>
|
||||||
<FullscreenLayout scrollRef={scrollRef} overlay={toolPermissionOverlay} bottomFloat={feature('BUDDY') && companionVisible && !companionNarrow ? <CompanionFloatingBubble /> : undefined} modal={centeredModal} modalScrollRef={modalScrollRef} dividerYRef={dividerYRef} hidePill={!!viewedAgentTask} hideSticky={!!viewedTeammateTask} newMessageCount={unseenDivider?.count ?? 0} onPillClick={() => {
|
<FullscreenLayout scrollRef={scrollRef} overlay={toolPermissionOverlay} bottomFloat={isBuddyEnabled() && companionVisible && !companionNarrow ? <CompanionFloatingBubble /> : undefined} modal={centeredModal} modalScrollRef={modalScrollRef} dividerYRef={dividerYRef} hidePill={!!viewedAgentTask} hideSticky={!!viewedTeammateTask} newMessageCount={unseenDivider?.count ?? 0} onPillClick={() => {
|
||||||
setCursor(null);
|
setCursor(null);
|
||||||
jumpToNew(scrollRef.current);
|
jumpToNew(scrollRef.current);
|
||||||
}} scrollable={<>
|
}} scrollable={<>
|
||||||
@@ -4592,8 +4594,8 @@ export function REPL({
|
|||||||
{showSpinner && <SpinnerWithVerb mode={streamMode} spinnerTip={spinnerTip} responseLengthRef={responseLengthRef} apiMetricsRef={apiMetricsRef} overrideMessage={spinnerMessage} spinnerSuffix={stopHookSpinnerSuffix} verbose={verbose} loadingStartTimeRef={loadingStartTimeRef} totalPausedMsRef={totalPausedMsRef} pauseStartTimeRef={pauseStartTimeRef} overrideColor={spinnerColor} overrideShimmerColor={spinnerShimmerColor} hasActiveTools={inProgressToolUseIDs.size > 0} leaderIsIdle={!isLoading} />}
|
{showSpinner && <SpinnerWithVerb mode={streamMode} spinnerTip={spinnerTip} responseLengthRef={responseLengthRef} apiMetricsRef={apiMetricsRef} overrideMessage={spinnerMessage} spinnerSuffix={stopHookSpinnerSuffix} verbose={verbose} loadingStartTimeRef={loadingStartTimeRef} totalPausedMsRef={totalPausedMsRef} pauseStartTimeRef={pauseStartTimeRef} overrideColor={spinnerColor} overrideShimmerColor={spinnerShimmerColor} hasActiveTools={inProgressToolUseIDs.size > 0} leaderIsIdle={!isLoading} />}
|
||||||
{!showSpinner && !isLoading && !userInputOnProcessing && !hasRunningTeammates && isBriefOnly && !viewedAgentTask && <BriefIdleStatus />}
|
{!showSpinner && !isLoading && !userInputOnProcessing && !hasRunningTeammates && isBriefOnly && !viewedAgentTask && <BriefIdleStatus />}
|
||||||
{isFullscreenEnvEnabled() && <PromptInputQueuedCommands />}
|
{isFullscreenEnvEnabled() && <PromptInputQueuedCommands />}
|
||||||
</>} bottom={<Box flexDirection={feature('BUDDY') && companionNarrow ? 'column' : 'row'} width="100%" alignItems={feature('BUDDY') && companionNarrow ? undefined : 'flex-end'}>
|
</>} bottom={<Box flexDirection={isBuddyEnabled() && companionNarrow ? 'column' : 'row'} width="100%" alignItems={isBuddyEnabled() && companionNarrow ? undefined : 'flex-end'}>
|
||||||
{feature('BUDDY') && companionNarrow && isFullscreenEnvEnabled() && companionVisible ? <CompanionSprite /> : null}
|
{isBuddyEnabled() && companionNarrow && isFullscreenEnvEnabled() && companionVisible ? <CompanionSprite /> : null}
|
||||||
<Box flexDirection="column" flexGrow={1}>
|
<Box flexDirection="column" flexGrow={1}>
|
||||||
{permissionStickyFooter}
|
{permissionStickyFooter}
|
||||||
{/* Immediate local-jsx commands (/btw, /sandbox, /assistant,
|
{/* Immediate local-jsx commands (/btw, /sandbox, /assistant,
|
||||||
@@ -4997,7 +4999,7 @@ export function REPL({
|
|||||||
}} />}
|
}} />}
|
||||||
{"external" === 'ant' && <DevBar />}
|
{"external" === 'ant' && <DevBar />}
|
||||||
</Box>
|
</Box>
|
||||||
{feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? <CompanionSprite /> : null}
|
{isBuddyEnabled() && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? <CompanionSprite /> : null}
|
||||||
</Box>} />
|
</Box>} />
|
||||||
</MCPConnectionManager>
|
</MCPConnectionManager>
|
||||||
</KeybindingSetup>;
|
</KeybindingSetup>;
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ import { isInProcessTeammate } from './teammateContext.js'
|
|||||||
import { removeTeammateFromTeamFile } from './swarm/teamHelpers.js'
|
import { removeTeammateFromTeamFile } from './swarm/teamHelpers.js'
|
||||||
import { unassignTeammateTasks } from './tasks.js'
|
import { unassignTeammateTasks } from './tasks.js'
|
||||||
import { getCompanionIntroAttachment } from '../buddy/prompt.js'
|
import { getCompanionIntroAttachment } from '../buddy/prompt.js'
|
||||||
|
import { isBuddyEnabled } from '../buddy/feature.js'
|
||||||
|
|
||||||
export const TODO_REMINDER_CONFIG = {
|
export const TODO_REMINDER_CONFIG = {
|
||||||
TURNS_SINCE_WRITE: 10,
|
TURNS_SINCE_WRITE: 10,
|
||||||
@@ -861,7 +862,7 @@ export async function getAttachments(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...(feature('BUDDY')
|
...(isBuddyEnabled()
|
||||||
? [
|
? [
|
||||||
maybe('companion_intro', () =>
|
maybe('companion_intro', () =>
|
||||||
Promise.resolve(getCompanionIntroAttachment(messages)),
|
Promise.resolve(getCompanionIntroAttachment(messages)),
|
||||||
|
|||||||
Reference in New Issue
Block a user