Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
ca6a056
feat: add ACP integration for Gemini with model selection and improve…
Scoteezy Jan 13, 2026
308c3bf
fix(tools): hide unknown Gemini tools automatically
Scoteezy Jan 13, 2026
2bfe284
feat(gemini): improve tool display for edit and execute actions
Scoteezy Jan 13, 2026
ec42982
fix: upstream sync regressions (wizard, i18n, profiles, routing)
leeroybrun Jan 12, 2026
2e65322
feat(settings): add profiles feature flag
leeroybrun Jan 13, 2026
ba866b8
fix(new-session): restore legacy session creation UI
leeroybrun Jan 12, 2026
8b37d10
refactor(pickers): unify selector layout and selection UI
leeroybrun Jan 13, 2026
306cd71
fix(sync): prevent settings version-mismatch retry loop
leeroybrun Jan 13, 2026
7b937d8
fix(security): avoid logging sensitive sync/message data
leeroybrun Jan 13, 2026
e25e11b
refactor(pickers): consolidate search toggle and remove default favor…
leeroybrun Jan 13, 2026
0cfafbc
fix(new-session): restore chip layout and mobile close
leeroybrun Jan 13, 2026
45a9038
Merge pull request #376 from Scoteezy/feat/acp-gemini-integration
bra1nDump Jan 13, 2026
ec01fad
fix(profiles): improve tmux and env var spacing
leeroybrun Jan 13, 2026
80b1789
fix(ui): align SearchHeader with maxWidth layout
leeroybrun Jan 13, 2026
5d4f1f9
refactor(wizard): use machine and path picker modals
leeroybrun Jan 13, 2026
98db44f
refactor(wizard): restore inline machine/path selectors
leeroybrun Jan 14, 2026
33f305a
fix(wizard): always show working directory input
leeroybrun Jan 14, 2026
c276449
fix(wizard): embed profile list and stabilize picker row icons
leeroybrun Jan 14, 2026
4f49ef4
feat(wizard): embed profiles and use searchable directory picker
leeroybrun Jan 14, 2026
01281fb
refactor(wizard): reuse path picker UI and fix profile availability
leeroybrun Jan 14, 2026
c6f3441
refactor(wizard): unify bottom chips and profile icons
leeroybrun Jan 14, 2026
da8a1ef
fix(new-session): improve close and picker affordances
leeroybrun Jan 14, 2026
cf83504
refactor(pickers): split search toggles and unify wizard inputs
leeroybrun Jan 14, 2026
b4c2f26
fix(ui): align wizard search and env var cards
leeroybrun Jan 14, 2026
f0929d1
fix(ui): unify picker selection and profile icons
leeroybrun Jan 14, 2026
ecc6246
fix(ui): use profile compatibility glyph icons
leeroybrun Jan 14, 2026
3debc6c
fix(pickers): keep search visible with zero matches
leeroybrun Jan 14, 2026
3ca8e85
fix(ui): stabilize chip icons
leeroybrun Jan 14, 2026
f6ce61c
feat(profiles): add favorites and group lists
leeroybrun Jan 14, 2026
80c1abd
fix(paths): keep search visible in path picker
leeroybrun Jan 14, 2026
5c9702c
fix(ui): adjust icons
leeroybrun Jan 14, 2026
a9eb77c
fix(paths): preserve search focus when moving
leeroybrun Jan 14, 2026
7c5b888
fix(ui): tweak profile glyph sizing
leeroybrun Jan 14, 2026
406c96c
fix(ui): polish wizard labels and focus styles
leeroybrun Jan 14, 2026
d30d5db
fix(ui): simplify wizard headers and list icons
leeroybrun Jan 14, 2026
ba0c6c3
fix(ui): lock backend chip when profile selected
leeroybrun Jan 14, 2026
e8e6c4b
fix(profiles): gate backend changes by profile compatibility
leeroybrun Jan 14, 2026
f4b1ffe
feat(profiles): add backend compatibility and wizard AI step
leeroybrun Jan 14, 2026
71ffe40
fix(new-session): persist profile edits and preserve permissions
leeroybrun Jan 14, 2026
a57cbb5
fix(profiles): polish editor and picker UI
leeroybrun Jan 14, 2026
64e8422
fix(profiles): improve env var UX and editor safety
leeroybrun Jan 14, 2026
c197e9f
fix(web): alert modals not clickable when shown above expo-router modals
leeroybrun Jan 11, 2026
92e1f39
fix(ui): polish wizard profile actions and env var previews
leeroybrun Jan 14, 2026
45a5ac6
fix(i18n): restore dedicated English translations file
leeroybrun Jan 14, 2026
8336b40
fix(ui): stabilize profile selection and env vars modals
leeroybrun Jan 14, 2026
b79dfe5
fix(new-session): persist model mode in draft
leeroybrun Jan 14, 2026
78d696b
refactor(new-session): stop using lastUsedModelMode
leeroybrun Jan 14, 2026
8307d1d
fix(new-session): compact mobile profile actions
leeroybrun Jan 14, 2026
4692b3b
fix(ui): suppress selected background for single-item groups
leeroybrun Jan 14, 2026
f3815a1
fix(ui): collapse row actions on mobile
leeroybrun Jan 14, 2026
97d644e
refactor(new-session): reuse ItemRowActions for profile rows
leeroybrun Jan 14, 2026
17c531e
fix(ui): center modals and tighten chips
leeroybrun Jan 14, 2026
8ea98a2
fix(ui): tighten new session chip spacing
leeroybrun Jan 14, 2026
71a0edf
fix(ui): align new session input padding
leeroybrun Jan 14, 2026
3e40eb0
fix(ui): simplify chips and move session profile info
leeroybrun Jan 14, 2026
05233ff
fix(session-info): show profile under metadata
leeroybrun Jan 15, 2026
94f8b28
fix(profiles): align edit form buttons
leeroybrun Jan 15, 2026
7482c7c
fix(env-vars): improve preview scroll and machine resolution
leeroybrun Jan 15, 2026
b00bbc0
fix(new-session): refine wizard padding and bottom bar
leeroybrun Jan 15, 2026
3d1d6fb
fix(env-vars): clarify machine resolution and source var input
leeroybrun Jan 15, 2026
f0148f1
fix(new-session): improve wizard sizing and AI profile labels
leeroybrun Jan 15, 2026
6bb0209
fix(new-session): avoid nested scroll and align composer padding
leeroybrun Jan 15, 2026
a1bcc98
fix(env-vars): resolve via login shell and clarify machine status
leeroybrun Jan 15, 2026
2b8b6e7
fix(web): increase modal height and align env preview with daemon
leeroybrun Jan 15, 2026
4b70ee6
fix(env-vars): batch resolve in editor and support :=
leeroybrun Jan 15, 2026
21c7968
refactor(i18n): move translation types to _types
leeroybrun Jan 15, 2026
04628ed
refactor(profiles): remove provider config objects
leeroybrun Jan 15, 2026
5571035
fix(new-session): switch profile picker to id-based navigation
leeroybrun Jan 15, 2026
1a36c61
fix(env): align template semantics and preview safety
leeroybrun Jan 15, 2026
8adde3f
fix(sync): stop resetting model meta and gate logs
leeroybrun Jan 15, 2026
15a768d
Merge remote-tracking branch 'upstream/main' into slopus/pr/upstream-…
leeroybrun Jan 15, 2026
b870e1d
fix(new): align Gemini permission and model modes
leeroybrun Jan 15, 2026
47ae80a
fix(app): avoid bundling vitest test file
leeroybrun Jan 15, 2026
6673881
fix(agent-input): remove duplicate machine/path panel
leeroybrun Jan 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 35 additions & 35 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,42 +23,42 @@ This allows you to test production-like builds with real users before releasing

```bash
# Development variant (default)
npm run ios:dev
yarn ios:dev

# Preview variant
npm run ios:preview
yarn ios:preview

# Production variant
npm run ios:production
yarn ios:production
```

### Android Development

```bash
# Development variant
npm run android:dev
yarn android:dev

# Preview variant
npm run android:preview
yarn android:preview

# Production variant
npm run android:production
yarn android:production
```

### macOS Desktop (Tauri)

```bash
# Development variant - run with hot reload
npm run tauri:dev
yarn tauri:dev

# Build development variant
npm run tauri:build:dev
yarn tauri:build:dev

# Build preview variant
npm run tauri:build:preview
yarn tauri:build:preview

# Build production variant
npm run tauri:build:production
yarn tauri:build:production
```

**How Tauri Variants Work:**
Expand All @@ -71,13 +71,13 @@ npm run tauri:build:production

```bash
# Start dev server for development variant
npm run start:dev
yarn start:dev

# Start dev server for preview variant
npm run start:preview
yarn start:preview

# Start dev server for production variant
npm run start:production
yarn start:production
```

## Visual Differences
Expand All @@ -95,7 +95,7 @@ This makes it easy to distinguish which version you're testing!

1. **Build development variant:**
```bash
npm run ios:dev
yarn ios:dev
```

2. **Make your changes** to the code
Expand All @@ -104,19 +104,19 @@ This makes it easy to distinguish which version you're testing!

4. **Rebuild if needed** for native changes:
```bash
npm run ios:dev
yarn ios:dev
```

### Testing Preview (Pre-Release)

1. **Build preview variant:**
```bash
npm run ios:preview
yarn ios:preview
```

2. **Test OTA updates:**
```bash
npm run ota # Publishes to preview branch
yarn ota # Publishes to preview branch
```

3. **Verify** the preview build works as expected
Expand All @@ -125,17 +125,17 @@ This makes it easy to distinguish which version you're testing!

1. **Build production variant:**
```bash
npm run ios:production
yarn ios:production
```

2. **Submit to App Store:**
```bash
npm run submit
yarn submit
```

3. **Deploy OTA updates:**
```bash
npm run ota:production
yarn ota:production
```

## All Variants Simultaneously
Expand All @@ -144,9 +144,9 @@ You can install all three variants on the same device:

```bash
# Build all three variants
npm run ios:dev
npm run ios:preview
npm run ios:production
yarn ios:dev
yarn ios:preview
yarn ios:production
```

All three apps appear on your device with different icons and names!
Expand Down Expand Up @@ -195,12 +195,12 @@ You can connect different variants to different Happy CLI instances:

```bash
# Development app → Dev CLI daemon
npm run android:dev
# Connect to CLI running: npm run dev:daemon:start
yarn android:dev
# Connect to CLI running: yarn dev:daemon:start

# Production app → Stable CLI daemon
npm run android:production
# Connect to CLI running: npm run stable:daemon:start
yarn android:production
# Connect to CLI running: yarn stable:daemon:start
```

Each app maintains separate authentication and sessions!
Expand All @@ -210,7 +210,7 @@ Each app maintains separate authentication and sessions!
To test with a local Happy server:

```bash
npm run start:local-server
yarn start:local-server
```

This sets:
Expand All @@ -227,21 +227,21 @@ This shouldn't happen - each variant has a unique bundle ID. If it does:
1. Check `app.config.js` - verify `bundleId` is set correctly for the variant
2. Clean build:
```bash
npm run prebuild
npm run ios:dev # or whichever variant
yarn prebuild
yarn ios:dev # or whichever variant
```

### App not updating after changes

1. **For JS changes**: Hot reload should work automatically
2. **For native changes**: Rebuild the variant:
```bash
npm run ios:dev # Force rebuild
yarn ios:dev # Force rebuild
```
3. **For config changes**: Clean and prebuild:
```bash
npm run prebuild
npm run ios:dev
yarn prebuild
yarn ios:dev
```

### All three apps look the same
Expand All @@ -258,7 +258,7 @@ If they're all the same name, the variant might not be set correctly. Verify:
echo $APP_ENV

# Or look at the build output
npm run ios:dev # Should show "Happy (dev)" as the name
yarn ios:dev # Should show "Happy (dev)" as the name
```

### Connected device not found
Expand All @@ -270,7 +270,7 @@ For iOS connected device testing:
xcrun devicectl list devices

# Run on specific connected device
npm run ios:connected-device
yarn ios:connected-device
```

## Tips
Expand Down
10 changes: 10 additions & 0 deletions sources/-session/SessionView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ function SessionViewLoaded({ sessionId, session }: { sessionId: string, session:
const shouldShowCliWarning = isCliOutdated && !isAcknowledged;
// Get permission mode from session object, default to 'default'
const permissionMode = session.permissionMode || 'default';
// Get model mode from session object - for Gemini sessions use explicit model, default to gemini-2.5-pro
const isGeminiSession = session.metadata?.flavor === 'gemini';
const modelMode = session.modelMode || (isGeminiSession ? 'gemini-2.5-pro' : 'default');
const sessionStatus = useSessionStatus(session);
const sessionUsage = useSessionUsage(sessionId);
const alwaysShowContextSize = useSetting('alwaysShowContextSize');
Expand All @@ -193,6 +196,11 @@ function SessionViewLoaded({ sessionId, session }: { sessionId: string, session:
storage.getState().updateSessionPermissionMode(sessionId, mode);
}, [sessionId]);

// Function to update model mode (for Gemini sessions)
const updateModelMode = React.useCallback((mode: 'default' | 'gemini-2.5-pro' | 'gemini-2.5-flash' | 'gemini-2.5-flash-lite') => {
storage.getState().updateSessionModelMode(sessionId, mode);
}, [sessionId]);

// Memoize header-dependent styles to prevent re-renders
const headerDependentStyles = React.useMemo(() => ({
contentContainer: {
Expand Down Expand Up @@ -272,6 +280,8 @@ function SessionViewLoaded({ sessionId, session }: { sessionId: string, session:
sessionId={sessionId}
permissionMode={permissionMode}
onPermissionModeChange={updatePermissionMode}
modelMode={modelMode as any}
onModelModeChange={updateModelMode as any}
Comment on lines +283 to +284
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While this works, using as any bypasses type checking and can hide potential bugs. It seems there might be a slight type mismatch between the modelMode and updateModelMode here and the props expected by the child component (AgentInput).

It would be best to resolve the type discrepancy to ensure full type safety. For example, you could explicitly cast modelMode to the ModelMode type if you are certain it's compatible, rather than using any.

import { ModelMode } from '@/sync/permissionTypes';

// ...

<AgentInput
    // ...
    modelMode={modelMode as ModelMode}
    onModelModeChange={updateModelMode}
/>

Similarly, updateModelMode should be assignable to onModelModeChange without as any if the types are correctly aligned. Given the scope of this refactoring, this is a minor point, but it's good practice to avoid any where possible.

Comment on lines +283 to +284
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine SessionView.tsx around the mentioned lines
head -n 300 sources/-session/SessionView.tsx | tail -n 140

Repository: leeroybrun/happy

Length of output: 5956


🏁 Script executed:

# Find AgentInput component to see its prop types
fd -n "AgentInput" -t f | head -20

Repository: leeroybrun/happy

Length of output: 290


🏁 Script executed:

# Check what files exist in sources/components
ls -la sources/components/ | grep -i agent || echo "No agent files found, searching further"

Repository: leeroybrun/happy

Length of output: 431


🏁 Script executed:

# Search for AgentInput more broadly
rg -l "AgentInput" sources/ --type ts --type tsx

Repository: leeroybrun/happy

Length of output: 87


🏁 Script executed:

# Search for AgentInput without type restriction
rg "AgentInput" sources/ --include "*.ts" --include "*.tsx"

Repository: leeroybrun/happy

Length of output: 501


🏁 Script executed:

# Read AgentInput.tsx to check the prop types
head -n 100 sources/components/AgentInput.tsx

Repository: leeroybrun/happy

Length of output: 3760


🏁 Script executed:

# Search for modelMode prop definition in AgentInput
rg "modelMode" sources/components/AgentInput.tsx -B2 -A2

Repository: leeroybrun/happy

Length of output: 539


🏁 Script executed:

# Run typecheck
cd sources && yarn typecheck 2>&1 || true

Repository: leeroybrun/happy

Length of output: 245


🏁 Script executed:

# Find permissionTypes to see the actual ModelMode definition
rg "type ModelMode" sources/

Repository: leeroybrun/happy

Length of output: 115


🏁 Script executed:

# Also search for ModelMode definition more broadly
rg "ModelMode\s*=" sources/ -A2

Repository: leeroybrun/happy

Length of output: 775


🏁 Script executed:

# Check the permissionTypes file
fd "permissionTypes" sources/ -t f

Repository: leeroybrun/happy

Length of output: 91


🏁 Script executed:

# Read the full permissionTypes.ts file to see complete ModelMode definition
cat sources/sync/permissionTypes.ts

Repository: leeroybrun/happy

Length of output: 539


🏁 Script executed:

# Also check if there's a proper ModelMode type being imported anywhere
rg "import.*ModelMode" sources/ -A1 -B1

Repository: leeroybrun/happy

Length of output: 919


🏁 Script executed:

# Check the exact line where modelMode is passed to AgentInput in SessionView
sed -n '280,290p' sources/-session/SessionView.tsx

Repository: leeroybrun/happy

Length of output: 568


🏁 Script executed:

# Check if SessionView.tsx imports ModelMode type
rg "import.*ModelMode" sources/-session/SessionView.tsx

Repository: leeroybrun/happy

Length of output: 42


🏁 Script executed:

# Check the derivation of modelMode at line 171-173
sed -n '171,175p' sources/-session/SessionView.tsx

Repository: leeroybrun/happy

Length of output: 435


🏁 Script executed:

# Check if updateSessionModelMode function exists and what it accepts
rg "updateSessionModelMode" sources/ -A3 -B1

Repository: leeroybrun/happy

Length of output: 1943


🏁 Script executed:

# Check what types are actually being used in SessionView
head -n 50 sources/-session/SessionView.tsx | grep -E "^import|^from"

Repository: leeroybrun/happy

Length of output: 1934


🏁 Script executed:

# Check the session object type to see what modelMode property type is
rg "session.*modelMode" sources/ -B2 -A2 | head -30

Repository: leeroybrun/happy

Length of output: 1918


🏁 Script executed:

# Check the Session type definition to see what modelMode property is typed as
rg "interface.*Session\|type.*Session" sources/sync/storageTypes.ts -A30 | head -60

Repository: leeroybrun/happy

Length of output: 42


🏁 Script executed:

# Check if there's a Session interface that defines modelMode property
rg "modelMode.*:" sources/sync/storageTypes.ts -B2 -A2

Repository: leeroybrun/happy

Length of output: 627


Remove as any casts—they hide type incompatibility between the callback signature and AgentInput's expectations.

The modelMode derivation is correct, but updateModelMode has a deliberate type restriction: it only accepts 'default' | 'gemini-2.5-pro' | 'gemini-2.5-flash' | 'gemini-2.5-flash-lite'. However, AgentInput expects onModelModeChange to accept the full ModelMode union (which includes sonnet, opus, gpt-5-*, adaptiveUsage, etc.).

The as any casts bypass TypeScript's type checking and violate the coding guideline requiring strict mode and proper typing. If AgentInput attempts to call the callback with a model mode outside the restricted set, the mismatch will only surface at runtime.

Either import and use the ModelMode type properly, or align the callback signature with AgentInput's expectations. If the restriction is intentional for Gemini-only sessions, document the design decision rather than hiding it with as any.

🤖 Prompt for AI Agents
In `@sources/-session/SessionView.tsx` around lines 283 - 284, Remove the unsafe
"as any" casts on modelMode and updateModelMode passed to AgentInput: locate the
AgentInput usage where modelMode={modelMode as any} and
onModelModeChange={updateModelMode as any}, then either (A) widen the signature
of updateModelMode to accept the full ModelMode union (import ModelMode and
change the function/type to (mode: ModelMode) => void) so it matches
AgentInput's onModelModeChange, or (B) keep the narrower Gemini-only handler but
create a typed wrapper that implements (mode: ModelMode) => void,
validates/guards that the incoming mode is one of
'default'|'gemini-2.5-pro'|'gemini-2.5-flash'|'gemini-2.5-flash-lite' before
calling updateModelMode (and document this behavior); remove the casts and
ensure imports reference the ModelMode type and AgentInput's prop types.

metadata={session.metadata}
connectionStatus={{
text: sessionStatus.statusText,
Expand Down
17 changes: 16 additions & 1 deletion sources/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ export default function RootLayout() {
headerTitle: t('settings.features'),
}}
/>
<Stack.Screen
name="settings/profiles"
options={{
headerTitle: t('settingsFeatures.profiles'),
}}
/>
<Stack.Screen
name="terminal/connect"
options={{
Expand Down Expand Up @@ -311,6 +317,13 @@ export default function RootLayout() {
headerBackTitle: t('common.back'),
}}
/>
<Stack.Screen
name="new/pick/profile"
options={{
headerTitle: '',
headerBackTitle: t('common.back'),
}}
/>
<Stack.Screen
name="new/pick/profile-edit"
options={{
Expand All @@ -322,7 +335,9 @@ export default function RootLayout() {
name="new/index"
options={{
headerTitle: t('newSession.title'),
headerBackTitle: t('common.back'),
headerShown: true,
headerBackTitle: t('common.cancel'),
presentation: 'modal',
}}
/>
<Stack.Screen
Expand Down
1 change: 0 additions & 1 deletion sources/app/(app)/machine/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,6 @@ export default function MachineDetailScreen() {
disabled={!isMachineOnline(machine)}
selected={isSelected}
showChevron={false}
pressableStyle={isSelected ? { backgroundColor: theme.colors.surfaceSelected } : undefined}
showDivider={!hideDivider}
/>
);
Expand Down
Loading