Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ jobs:
- name: Build trpc types
run: pnpm --filter @kilocode/trpc run build

- name: Build harness SDK
run: pnpm --filter @kilocode/harness-sdk run build

- name: Lint
run: scripts/lint-all.sh

Expand Down Expand Up @@ -412,6 +415,12 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

# apps/mobile tests the app against the built harness SDK, the way it
# already runs against built trpc types.
- name: Build harness SDK
if: matrix.workspace.name == 'kilo-app'
run: pnpm --filter @kilocode/harness-sdk run build

- name: Run tests
run: pnpm --filter ${{ matrix.workspace.name }} test

Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/kilo-app-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ jobs:
- name: Build trpc types
run: pnpm --filter @kilocode/trpc run build

- name: Build harness SDK
run: pnpm --filter @kilocode/harness-sdk run build

- name: Typecheck
run: pnpm --filter kilo-app run typecheck

Expand Down Expand Up @@ -96,6 +99,9 @@ jobs:
- name: Build trpc types
run: pnpm --filter @kilocode/trpc run build

- name: Build harness SDK
run: pnpm --filter @kilocode/harness-sdk run build

- name: Lint
run: pnpm --filter kilo-app run lint

Expand All @@ -122,6 +128,9 @@ jobs:
- name: Build trpc types
run: pnpm --filter @kilocode/trpc run build

- name: Build harness SDK
run: pnpm --filter @kilocode/harness-sdk run build

- name: Check i18n catalogs and leftover copy
run: pnpm --filter kilo-app run check:i18n
- name: Check for silently-dropped NativeWind classes
Expand Down Expand Up @@ -203,5 +212,8 @@ jobs:
- name: Build trpc types
run: pnpm --filter @kilocode/trpc run build

- name: Build harness SDK
run: pnpm --filter @kilocode/harness-sdk run build

- name: Test
run: pnpm --filter kilo-app run test
6 changes: 6 additions & 0 deletions .github/workflows/mobile-native-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ jobs:
- name: Build trpc types
run: pnpm --filter @kilocode/trpc run build

- name: Build harness SDK
run: pnpm --filter @kilocode/harness-sdk run build

# A consumer computes the hash on macOS; the gate computed it on
# Linux. A mismatch would publish an artifact nobody can ever find,
# so fail loudly instead. Android is checked here too: it builds on
Expand Down Expand Up @@ -225,6 +228,9 @@ jobs:
- name: Build trpc types
run: pnpm --filter @kilocode/trpc run build

- name: Build harness SDK
run: pnpm --filter @kilocode/harness-sdk run build

- name: Expo prebuild
run: CI=1 pnpm --filter kilo-app exec expo prebuild --platform android

Expand Down
6 changes: 6 additions & 0 deletions apps/mobile/.oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@
"import/no-nodejs-modules": "off"
}
},
{
"files": ["src/lib/chat/store.test.ts"],
"rules": {
"import/no-nodejs-modules": "off"
}
},
{
"files": ["src/lib/expo-router-linking.mounted.test.tsx"],
"rules": {
Expand Down
12 changes: 12 additions & 0 deletions apps/mobile/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,15 @@ The app follows https://github.com/Kilo-Org/kilo-design/ in general, except wher
## Debugging

Add narrow temporary logs at the real boundaries. Reproduce. Read the tmux service logs. Fix the demonstrated cause. Remove the logs. Do not guess, and do not commit debug logging.

## Chat tab

The Chat tab runs `@kilocode/harness-sdk` on the device. `src/lib/chat/` holds the registry (the conversations that are running), the layers (the plugins they run on) and the store (the list and its CRUD). The SDK saves one conversation; this app owns the list, the ordering, the titles, the deleting and the scoping.

**A conversation can move, and the screen follows the state rather than a return value.** Switching models clones the conversation onto a new session, and the mover is not always the screen: a question typed on another model while an answer was arriving moves the chat from inside the registry. So the chat that was left points at the one it became, and `useChat` follows that.

A chat is opened with the SDK's `time` tool and no other. A model has no clock, so it answers a dated question from its training date, confidently and wrong. The other three tools the SDK ships stay off: the composer is already how a person is asked something, a subagent costs a second session, and a to-do list is working memory for a run a chat does not have.

**Prove a chat change against the same eleven models the SDK's live runs use.** The list is in `packages/harness-sdk/e2e/setup.ts`, and `pnpm --filter @kilocode/harness-sdk test:e2e:time full` is the sweep across all of them. Take one of those models through the app itself on the simulator as well: the sweep proves what the models do, the simulator proves this app's wiring, and neither proves the other. Both cost real money, so run the sweep when the tool set or the system prompt changes, not for a change to a screen.

Reading the model picker's accessibility tree in full takes WebDriverAgent down, and the Appium server with it. Drive it by XPath, never `getPageSource`, and cap the snapshot with `driver.updateSettings({ snapshotMaxDepth: 50 })`.
7 changes: 7 additions & 0 deletions apps/mobile/drizzle/0001_safe_hellfire_club.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE `chats` (
`session_id` text PRIMARY KEY NOT NULL,
`scope` text NOT NULL,
`updated_at` integer NOT NULL
);
--> statement-breakpoint
CREATE INDEX `chats_scope_updated_at` ON `chats` (`scope`,`updated_at`);
104 changes: 104 additions & 0 deletions apps/mobile/drizzle/meta/0001_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{
"version": "6",
"dialect": "sqlite",
"id": "32071253-3838-4db3-aee9-d05b75bc652b",
"prevId": "e7d69526-c2d6-4633-b14f-24bcef818f44",
"tables": {
"chats": {
"name": "chats",
"columns": {
"session_id": {
"name": "session_id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"chats_scope_updated_at": {
"name": "chats_scope_updated_at",
"columns": [
"scope",
"updated_at"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"kv": {
"name": "kv",
"columns": {
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"k": {
"name": "k",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"v": {
"name": "v",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"kv_scope_k_pk": {
"columns": [
"scope",
"k"
],
"name": "kv_scope_k_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}
7 changes: 7 additions & 0 deletions apps/mobile/drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
"when": 1786968532800,
"tag": "0000_material_toad",
"breakpoints": true
},
{
"idx": 1,
"version": "6",
"when": 1788574828397,
"tag": "0001_safe_hellfire_club",
"breakpoints": true
}
]
}
2 changes: 2 additions & 0 deletions apps/mobile/drizzle/migrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import journal from './meta/_journal.json';
import m0000 from './0000_material_toad.sql';
import m0001 from './0001_safe_hellfire_club.sql';

export default {
journal,
migrations: {
m0000,
m0001,
},
};
2 changes: 2 additions & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@kilocode/app-shared": "workspace:*",
"@kilocode/cloud-agent-sdk": "workspace:*",
"@kilocode/event-service": "workspace:*",
"@kilocode/harness-sdk": "workspace:*",
"@kilocode/kilo-chat": "workspace:*",
"@kilocode/kilo-chat-hooks": "workspace:*",
"@kilocode/notifications": "workspace:*",
Expand All @@ -58,6 +59,7 @@
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"drizzle-orm": "catalog:",
"effect": "3.22.1",
"expo": "~57.0.15",
"expo-apple-authentication": "~57.0.1",
"expo-application": "~57.0.2",
Expand Down
8 changes: 8 additions & 0 deletions apps/mobile/src/app/(app)/(tabs)/(4_chat)/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useLocalSearchParams } from 'expo-router';

import { ChatScreen } from '@/components/chat/chat-screen';

export default function ChatConversation() {
const { id } = useLocalSearchParams<{ id: string }>();
return <ChatScreen opened={id} />;
}
4 changes: 4 additions & 0 deletions apps/mobile/src/app/(app)/(tabs)/(4_chat)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Stack } from 'expo-router';

export const unstable_settings = {
initialRouteName: 'index',
};

export default function ChatLayout() {
return <Stack screenOptions={{ headerShown: false }} />;
}
6 changes: 3 additions & 3 deletions apps/mobile/src/app/(app)/(tabs)/(4_chat)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { QuickChatScreen } from '@/components/quick-chat/quick-chat-screen';
import { ChatListScreen } from '@/components/chat/chat-list-screen';

export default function QuickChatIndex() {
return <QuickChatScreen />;
export default function ChatIndex() {
return <ChatListScreen />;
}
18 changes: 9 additions & 9 deletions apps/mobile/src/app/(app)/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
import { StateSurfaceInsets } from '@/components/centered-state-surface';
import { BlurBar } from '@/components/ui/blur-bar';
import { Text } from '@/components/ui/text';
import { FEATURE_FLAG_QUICK_CHAT, useFeatureFlag } from '@/lib/analytics/posthog';
import { FEATURE_FLAG_CHAT, useFeatureFlag } from '@/lib/analytics/posthog';
import { PROFILE_TAB_ROOT } from '@/lib/finding-detail-back';
import { useLiveAgentSessions } from '@/lib/hooks/use-agent-sessions';
import { useKiloClawTabVisible } from '@/lib/hooks/use-kiloclaw-tab-visible';
Expand Down Expand Up @@ -71,7 +71,7 @@ export default function TabsLayout() {
const colors = useThemeColors();
const { bottom } = useSafeAreaInsets();
const { fontScale } = useWindowDimensions();
const hideTabs = shouldHideTabBar(pathname);
const hideTabs = shouldHideTabBar(pathname, segments);
const showTabLabel = shouldShowTabLabel(fontScale);
const tabBarHeight = getEffectiveTabBarHeight({
bottomInset: bottom,
Expand All @@ -80,9 +80,9 @@ export default function TabsLayout() {
});
const tabIconSize = getTabBarIconSize(fontScale);
const showKiloClawTab = useKiloClawTabVisible();
const showQuickChatTab = useFeatureFlag(FEATURE_FLAG_QUICK_CHAT, false);
const tabFlags = { showKiloClaw: showKiloClawTab, showQuickChat: showQuickChatTab };
const tabCount = visibleTabCount(showKiloClawTab, showQuickChatTab);
const showChatTab = useFeatureFlag(FEATURE_FLAG_CHAT, false);
const tabFlags = { showKiloClaw: showKiloClawTab, showChat: showChatTab };
const tabCount = visibleTabCount(showKiloClawTab, showChatTab);
const { t } = useTranslation();
const { organizationId, isLoaded: orgLoaded } = useOrganization();
const { activeSessions, isLoading, isError } = useLiveAgentSessions({
Expand All @@ -109,13 +109,13 @@ export default function TabsLayout() {
orgLoaded && !isLoading && !isError && needsInputCount > 0 ? needsInputCount : undefined;

// If the flag flips off while the Chat tab is focused, its `href` becomes
// null but the route is still mounted move to Home instead.
// null but the route is still mounted, so move to Home instead.
const onChatTab = segments.some(segment => segment === '(4_chat)');
useEffect(() => {
if (!showQuickChatTab && onChatTab) {
if (!showChatTab && onChatTab) {
router.replace('/(app)/(tabs)/(0_home)' as Href);
}
}, [showQuickChatTab, onChatTab, router]);
}, [showChatTab, onChatTab, router]);

return (
<StateSurfaceInsets bottomInset={hideTabs ? 0 : tabBarHeight + 16}>
Expand Down Expand Up @@ -218,7 +218,7 @@ export default function TabsLayout() {
<Tabs.Screen
name="(4_chat)"
options={{
href: showQuickChatTab ? undefined : null,
href: showChatTab ? undefined : null,
title: t('common.chat'),
tabBarAccessibilityLabel: tabAccessibilityLabel(
t('common.chat'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ vi.mock('@/lib/hooks/use-theme-colors', () => ({
useThemeColors: () => ({ foreground: '#000000', mutedForeground: '#666666' }),
}));
vi.mock('@/lib/analytics/posthog', () => ({
FEATURE_FLAG_QUICK_CHAT: 'quick-chat',
FEATURE_FLAG_CHAT: 'mobile-chat',
useFeatureFlag: () => false,
}));
vi.mock('@/lib/hooks/use-kiloclaw-tab-visible', () => ({ useKiloClawTabVisible: () => false }));
Expand Down
16 changes: 16 additions & 0 deletions apps/mobile/src/components/chat/beta-pill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { View } from 'react-native';
import { useTranslation } from 'react-i18next';

import { Text } from '@/components/ui/text';

/** Says, everywhere the chat is shown, that the chat is not finished yet. */
export function BetaPill() {
const { t } = useTranslation();
return (
<View className="rounded-full border border-border bg-secondary px-2 py-0.5">
<Text className="font-mono-medium text-[10px] uppercase leading-4 tracking-[0.2px] text-muted-foreground">
{t('modelChat.beta')}
</Text>
</View>
);
}
Loading
Loading