bazilion 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.understand-anything/.understandignore +25 -0
- package/.understand-anything/fingerprints.json +14267 -0
- package/.understand-anything/knowledge-graph.json +18128 -0
- package/.understand-anything/meta.json +6 -0
- package/CLAUDE.md +164 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/apps/cli/package.json +21 -0
- package/apps/cli/src/auth-file.ts +18 -0
- package/apps/cli/src/client.ts +37 -0
- package/apps/cli/src/columnize.ts +32 -0
- package/apps/cli/src/commands/agent.ts +574 -0
- package/apps/cli/src/commands/auth.ts +110 -0
- package/apps/cli/src/commands/backup.ts +135 -0
- package/apps/cli/src/commands/completion.ts +155 -0
- package/apps/cli/src/commands/config.ts +95 -0
- package/apps/cli/src/commands/doctor.ts +131 -0
- package/apps/cli/src/commands/group.ts +132 -0
- package/apps/cli/src/commands/inbox.ts +82 -0
- package/apps/cli/src/commands/login.ts +73 -0
- package/apps/cli/src/commands/memory.ts +106 -0
- package/apps/cli/src/commands/profile.ts +259 -0
- package/apps/cli/src/commands/provider.ts +170 -0
- package/apps/cli/src/commands/send.ts +24 -0
- package/apps/cli/src/commands/serve.ts +89 -0
- package/apps/cli/src/commands/skill.ts +120 -0
- package/apps/cli/src/commands/token.ts +148 -0
- package/apps/cli/src/commands/trigger.ts +129 -0
- package/apps/cli/src/commands/uninstall.ts +156 -0
- package/apps/cli/src/index.ts +196 -0
- package/apps/cli/src/paths.ts +12 -0
- package/apps/cli/test/agent.test.ts +213 -0
- package/apps/cli/test/backup.test.ts +95 -0
- package/apps/cli/test/chat.test.ts +539 -0
- package/apps/cli/test/columnize.test.ts +29 -0
- package/apps/cli/test/completion.test.ts +45 -0
- package/apps/cli/test/config-page.test.ts +151 -0
- package/apps/cli/test/group.test.ts +74 -0
- package/apps/cli/test/helpers.ts +70 -0
- package/apps/cli/test/inbox-autodeliver.test.ts +143 -0
- package/apps/cli/test/inbox.test.ts +147 -0
- package/apps/cli/test/memory.test.ts +69 -0
- package/apps/cli/test/profile.test.ts +110 -0
- package/apps/cli/test/send.test.ts +30 -0
- package/apps/cli/test/server-fixture.ts +212 -0
- package/apps/cli/test/session-head.test.ts +45 -0
- package/apps/cli/test/skill.test.ts +128 -0
- package/apps/cli/test/token.test.ts +109 -0
- package/apps/cli/test/trigger.test.ts +245 -0
- package/apps/cli/tsconfig.json +4 -0
- package/apps/daemon/package.json +31 -0
- package/apps/daemon/src/app.ts +41 -0
- package/apps/daemon/src/core/agent/archive.ts +8 -0
- package/apps/daemon/src/core/agent/delete.ts +30 -0
- package/apps/daemon/src/core/agent/resolve.ts +31 -0
- package/apps/daemon/src/core/agent/spawn.ts +95 -0
- package/apps/daemon/src/core/agent/unarchive.ts +11 -0
- package/apps/daemon/src/core/availableModels.ts +58 -0
- package/apps/daemon/src/core/db/client.ts +113 -0
- package/apps/daemon/src/core/db/migrate.ts +41 -0
- package/apps/daemon/src/core/db/migrations/0001_init.sql +179 -0
- package/apps/daemon/src/core/group/delete.ts +21 -0
- package/apps/daemon/src/core/group/register.ts +68 -0
- package/apps/daemon/src/core/index.ts +71 -0
- package/apps/daemon/src/core/paths.ts +56 -0
- package/apps/daemon/src/core/profile/create.ts +78 -0
- package/apps/daemon/src/core/profile/delete.ts +26 -0
- package/apps/daemon/src/core/profile/identity.ts +70 -0
- package/apps/daemon/src/core/profile/load.ts +45 -0
- package/apps/daemon/src/core/profile/seed.ts +74 -0
- package/apps/daemon/src/core/profile/templates.ts +60 -0
- package/apps/daemon/src/core/profile/update.ts +57 -0
- package/apps/daemon/src/core/profile/validate.ts +9 -0
- package/apps/daemon/src/core/repos/agents.ts +202 -0
- package/apps/daemon/src/core/repos/config.ts +78 -0
- package/apps/daemon/src/core/repos/groups.ts +55 -0
- package/apps/daemon/src/core/repos/messages.ts +127 -0
- package/apps/daemon/src/core/repos/profiles.ts +83 -0
- package/apps/daemon/src/core/repos/providerModels.ts +58 -0
- package/apps/daemon/src/core/repos/providerState.ts +37 -0
- package/apps/daemon/src/core/repos/secrets.ts +145 -0
- package/apps/daemon/src/core/repos/skillMeta.ts +49 -0
- package/apps/daemon/src/core/repos/triggers.ts +101 -0
- package/apps/daemon/src/core/repos/webTokens.ts +87 -0
- package/apps/daemon/src/core/secrets.ts +65 -0
- package/apps/daemon/src/core/services.ts +264 -0
- package/apps/daemon/src/core/skills/discover.ts +28 -0
- package/apps/daemon/src/core/skills/import.ts +136 -0
- package/apps/daemon/src/core/skills/parse.ts +52 -0
- package/apps/daemon/src/core/skills/resolve.ts +50 -0
- package/apps/daemon/src/index.ts +45 -0
- package/apps/daemon/src/lib/agent-cancel.ts +48 -0
- package/apps/daemon/src/lib/agent-id.ts +13 -0
- package/apps/daemon/src/lib/agent-turn.ts +56 -0
- package/apps/daemon/src/lib/api-key.ts +56 -0
- package/apps/daemon/src/lib/auth.ts +41 -0
- package/apps/daemon/src/lib/cron.ts +93 -0
- package/apps/daemon/src/lib/ctx.ts +80 -0
- package/apps/daemon/src/lib/messaging-host.ts +34 -0
- package/apps/daemon/src/lib/middleware-auth.ts +52 -0
- package/apps/daemon/src/lib/scheduler.ts +294 -0
- package/apps/daemon/src/routes/agents.ts +772 -0
- package/apps/daemon/src/routes/auth-login.ts +193 -0
- package/apps/daemon/src/routes/config.ts +267 -0
- package/apps/daemon/src/routes/groups.ts +133 -0
- package/apps/daemon/src/routes/messages.ts +29 -0
- package/apps/daemon/src/routes/misc.ts +239 -0
- package/apps/daemon/src/routes/profiles.ts +197 -0
- package/apps/daemon/src/routes/skills.ts +123 -0
- package/apps/daemon/src/routes/triggers.ts +29 -0
- package/apps/daemon/src/runtime/auth/openai-codex.ts +121 -0
- package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +31 -0
- package/apps/daemon/src/runtime/index.ts +77 -0
- package/apps/daemon/src/runtime/memory/files.ts +103 -0
- package/apps/daemon/src/runtime/memory/qmd.ts +152 -0
- package/apps/daemon/src/runtime/memory/types.ts +16 -0
- package/apps/daemon/src/runtime/pi/events.ts +173 -0
- package/apps/daemon/src/runtime/pi/session.ts +536 -0
- package/apps/daemon/src/runtime/pi/tools.ts +85 -0
- package/apps/daemon/src/runtime/providers/catalog.ts +145 -0
- package/apps/daemon/src/runtime/providers/pi-adapter.ts +272 -0
- package/apps/daemon/src/runtime/providers/registry.ts +374 -0
- package/apps/daemon/src/runtime/providers/retry.ts +176 -0
- package/apps/daemon/src/runtime/providers/types.ts +33 -0
- package/apps/daemon/src/runtime/session/prompt.ts +83 -0
- package/apps/daemon/src/runtime/tools/bootstrap.ts +22 -0
- package/apps/daemon/src/runtime/tools/home.ts +114 -0
- package/apps/daemon/src/runtime/tools/memory.ts +81 -0
- package/apps/daemon/src/runtime/tools/messaging.ts +127 -0
- package/apps/daemon/src/runtime/tools/registry.ts +29 -0
- package/apps/daemon/src/runtime/tools/types.ts +13 -0
- package/apps/daemon/src/runtime/tools/web-extract.ts +110 -0
- package/apps/daemon/src/runtime/tools/web-ssrf.ts +245 -0
- package/apps/daemon/src/runtime/tools/web.ts +273 -0
- package/apps/daemon/src/runtime/worker/entry.ts +221 -0
- package/apps/daemon/src/runtime/worker/ipc-protocol.ts +75 -0
- package/apps/daemon/src/runtime/worker/spawn.ts +249 -0
- package/apps/daemon/test/core/agents.test.ts +319 -0
- package/apps/daemon/test/core/available-models.test.ts +63 -0
- package/apps/daemon/test/core/config.test.ts +99 -0
- package/apps/daemon/test/core/groups.test.ts +63 -0
- package/apps/daemon/test/core/helpers.ts +50 -0
- package/apps/daemon/test/core/identity.test.ts +85 -0
- package/apps/daemon/test/core/migrations.test.ts +83 -0
- package/apps/daemon/test/core/profiles.test.ts +182 -0
- package/apps/daemon/test/core/provider-models.test.ts +57 -0
- package/apps/daemon/test/core/provider-state.test.ts +36 -0
- package/apps/daemon/test/core/skill-meta.test.ts +45 -0
- package/apps/daemon/test/core/skills.test.ts +271 -0
- package/apps/daemon/test/core/triggers.test.ts +182 -0
- package/apps/daemon/test/core/web-tokens.test.ts +79 -0
- package/apps/daemon/test/cron.test.ts +90 -0
- package/apps/daemon/test/runtime/heartbeat.test.ts +34 -0
- package/apps/daemon/test/runtime/memory-qmd.test.ts +97 -0
- package/apps/daemon/test/runtime/memory.test.ts +78 -0
- package/apps/daemon/test/runtime/messaging.test.ts +213 -0
- package/apps/daemon/test/runtime/mock-server.ts +65 -0
- package/apps/daemon/test/runtime/openai-codex-auth.test.ts +116 -0
- package/apps/daemon/test/runtime/providers.test.ts +306 -0
- package/apps/daemon/test/runtime/retry.test.ts +191 -0
- package/apps/daemon/test/runtime/session-head.test.ts +90 -0
- package/apps/daemon/test/runtime/tools-home.test.ts +102 -0
- package/apps/daemon/test/runtime/tools-web.test.ts +206 -0
- package/apps/daemon/tsconfig.json +4 -0
- package/apps/mobile/README.md +60 -0
- package/apps/mobile/app/_layout.tsx +58 -0
- package/apps/mobile/app/agents/[id]/chat.tsx +486 -0
- package/apps/mobile/app/agents/[id]/index.tsx +166 -0
- package/apps/mobile/app/agents/index.tsx +212 -0
- package/apps/mobile/app/index.tsx +21 -0
- package/apps/mobile/app/pair.tsx +226 -0
- package/apps/mobile/app/settings.tsx +419 -0
- package/apps/mobile/app.json +49 -0
- package/apps/mobile/assets/adaptive-icon.png +0 -0
- package/apps/mobile/assets/favicon.png +0 -0
- package/apps/mobile/assets/icon.png +0 -0
- package/apps/mobile/assets/splash-icon.png +0 -0
- package/apps/mobile/babel.config.js +6 -0
- package/apps/mobile/metro.config.js +28 -0
- package/apps/mobile/package.json +44 -0
- package/apps/mobile/src/auth.ts +66 -0
- package/apps/mobile/src/pair-url.ts +48 -0
- package/apps/mobile/src/theme-context.tsx +88 -0
- package/apps/mobile/src/theme.ts +135 -0
- package/apps/mobile/test/pair-url.test.ts +46 -0
- package/apps/mobile/tsconfig.json +23 -0
- package/apps/web/components.json +25 -0
- package/apps/web/package.json +39 -0
- package/apps/web/public/baziu.svg +8 -0
- package/apps/web/src/components/AgentTabs.tsx +45 -0
- package/apps/web/src/components/BaziuLogo.tsx +21 -0
- package/apps/web/src/components/ChatPane.tsx +1033 -0
- package/apps/web/src/components/ConfigTabs.tsx +29 -0
- package/apps/web/src/components/CopyButton.tsx +68 -0
- package/apps/web/src/components/CreateGroupDialog.tsx +127 -0
- package/apps/web/src/components/FieldRow.tsx +94 -0
- package/apps/web/src/components/Footer.tsx +10 -0
- package/apps/web/src/components/PawIcon.tsx +15 -0
- package/apps/web/src/components/Sidebar.tsx +287 -0
- package/apps/web/src/components/SpawnDialog.tsx +129 -0
- package/apps/web/src/components/ThemeToggle.tsx +75 -0
- package/apps/web/src/components/TopNav.tsx +34 -0
- package/apps/web/src/components/ui/button.tsx +67 -0
- package/apps/web/src/components/ui/card.tsx +103 -0
- package/apps/web/src/components/ui/checkbox.tsx +31 -0
- package/apps/web/src/components/ui/dialog.tsx +168 -0
- package/apps/web/src/components/ui/input.tsx +19 -0
- package/apps/web/src/components/ui/label.tsx +22 -0
- package/apps/web/src/components/ui/radio-group.tsx +44 -0
- package/apps/web/src/components/ui/select.tsx +192 -0
- package/apps/web/src/components/ui/separator.tsx +26 -0
- package/apps/web/src/components/ui/table.tsx +116 -0
- package/apps/web/src/components/ui/tabs.tsx +88 -0
- package/apps/web/src/components/ui/textarea.tsx +18 -0
- package/apps/web/src/lib/auth.ts +50 -0
- package/apps/web/src/lib/daemon-client.ts +34 -0
- package/apps/web/src/lib/md.ts +45 -0
- package/apps/web/src/lib/utils.ts +6 -0
- package/apps/web/src/lib/wire-constants.ts +27 -0
- package/apps/web/src/routeTree.gen.ts +408 -0
- package/apps/web/src/router.tsx +20 -0
- package/apps/web/src/routes/__root.tsx +123 -0
- package/apps/web/src/routes/agents/$id/inbox.tsx +207 -0
- package/apps/web/src/routes/agents/$id/index.tsx +527 -0
- package/apps/web/src/routes/agents/$id/triggers.tsx +239 -0
- package/apps/web/src/routes/agents/index.tsx +265 -0
- package/apps/web/src/routes/api/$.ts +88 -0
- package/apps/web/src/routes/config/index.tsx +315 -0
- package/apps/web/src/routes/config/services.tsx +49 -0
- package/apps/web/src/routes/config/tokens.tsx +192 -0
- package/apps/web/src/routes/groups/$id/index.tsx +153 -0
- package/apps/web/src/routes/groups/$id/memory.tsx +321 -0
- package/apps/web/src/routes/groups/index.tsx +191 -0
- package/apps/web/src/routes/index.tsx +133 -0
- package/apps/web/src/routes/login.tsx +63 -0
- package/apps/web/src/routes/profiles/$id.tsx +549 -0
- package/apps/web/src/routes/profiles/index.tsx +458 -0
- package/apps/web/src/routes/skills/index.tsx +297 -0
- package/apps/web/src/routes/welcome.tsx +61 -0
- package/apps/web/src/styles.css +449 -0
- package/apps/web/tsconfig.json +25 -0
- package/apps/web/vite.config.ts +25 -0
- package/biome.json +23 -0
- package/docs/agent-engine.md +219 -0
- package/docs/architecture.md +627 -0
- package/docs/backlog/README.md +42 -0
- package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +125 -0
- package/docs/openclaw-reference.md +210 -0
- package/package.json +38 -0
- package/packages/api-types/package.json +11 -0
- package/packages/api-types/src/entities.ts +146 -0
- package/packages/api-types/src/events.ts +55 -0
- package/packages/api-types/src/index.ts +488 -0
- package/packages/api-types/src/memory.ts +15 -0
- package/packages/client/package.json +13 -0
- package/packages/client/src/index.ts +117 -0
- package/pnpm-workspace.yaml +3 -0
- package/tsconfig.base.json +23 -0
- package/tsconfig.json +11 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DMSans_400Regular, DMSans_500Medium, DMSans_700Bold } from '@expo-google-fonts/dm-sans'
|
|
2
|
+
import { DMSerifDisplay_400Regular } from '@expo-google-fonts/dm-serif-display'
|
|
3
|
+
import { JetBrainsMono_400Regular } from '@expo-google-fonts/jetbrains-mono'
|
|
4
|
+
import { useFonts } from 'expo-font'
|
|
5
|
+
import { Stack } from 'expo-router'
|
|
6
|
+
import { StatusBar } from 'expo-status-bar'
|
|
7
|
+
import { ThemeProvider, useColors, useResolvedScheme } from '@/src/theme-context'
|
|
8
|
+
import { fonts } from '@/src/theme'
|
|
9
|
+
|
|
10
|
+
export default function RootLayout() {
|
|
11
|
+
// Aliases (BaziuDisplay / BaziuBody / …) are what each StyleSheet
|
|
12
|
+
// references via theme.fonts.* — keys here are the registered family
|
|
13
|
+
// names, values are the asset module IDs imported above.
|
|
14
|
+
const [loaded] = useFonts({
|
|
15
|
+
BaziuDisplay: DMSerifDisplay_400Regular,
|
|
16
|
+
BaziuBody: DMSans_400Regular,
|
|
17
|
+
BaziuBodyMedium: DMSans_500Medium,
|
|
18
|
+
BaziuBodyBold: DMSans_700Bold,
|
|
19
|
+
BaziuMono: JetBrainsMono_400Regular,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// Block render until fonts settle so screens never flash with the
|
|
23
|
+
// system fallback font.
|
|
24
|
+
if (!loaded) return null
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<ThemeProvider>
|
|
28
|
+
<ThemedStack />
|
|
29
|
+
</ThemeProvider>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Inner component so it can subscribe to the theme. The Stack needs to
|
|
34
|
+
// re-render with new header/background colors when the user toggles.
|
|
35
|
+
function ThemedStack() {
|
|
36
|
+
const colors = useColors()
|
|
37
|
+
const resolved = useResolvedScheme()
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
<StatusBar style={resolved === 'dark' ? 'light' : 'dark'} />
|
|
41
|
+
<Stack
|
|
42
|
+
screenOptions={{
|
|
43
|
+
headerStyle: { backgroundColor: colors.background },
|
|
44
|
+
headerTintColor: colors.foreground,
|
|
45
|
+
headerTitleStyle: { fontFamily: fonts.display, fontSize: 18 },
|
|
46
|
+
contentStyle: { backgroundColor: colors.background },
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<Stack.Screen name="index" options={{ headerShown: false }} />
|
|
50
|
+
<Stack.Screen name="pair" options={{ title: 'Pair' }} />
|
|
51
|
+
<Stack.Screen name="agents/index" options={{ title: 'Agents' }} />
|
|
52
|
+
<Stack.Screen name="agents/[id]/index" options={{ title: 'Agent' }} />
|
|
53
|
+
<Stack.Screen name="agents/[id]/chat" options={{ title: 'Chat' }} />
|
|
54
|
+
<Stack.Screen name="settings" options={{ title: 'Settings' }} />
|
|
55
|
+
</Stack>
|
|
56
|
+
</>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import type { ChatFrame, ProviderMessage, SessionEvent } from '@bazilion/api-types'
|
|
2
|
+
import { useHeaderHeight } from '@react-navigation/elements'
|
|
3
|
+
import { router, useLocalSearchParams } from 'expo-router'
|
|
4
|
+
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
5
|
+
import {
|
|
6
|
+
ActivityIndicator,
|
|
7
|
+
FlatList,
|
|
8
|
+
KeyboardAvoidingView,
|
|
9
|
+
Platform,
|
|
10
|
+
Pressable,
|
|
11
|
+
StyleSheet,
|
|
12
|
+
Text,
|
|
13
|
+
TextInput,
|
|
14
|
+
View,
|
|
15
|
+
} from 'react-native'
|
|
16
|
+
import Markdown from 'react-native-markdown-display'
|
|
17
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
18
|
+
import { clearCredentials, type Credentials, loadCredentials } from '@/src/auth'
|
|
19
|
+
import { useColors } from '@/src/theme-context'
|
|
20
|
+
import { type Colors, fonts, radii } from '@/src/theme'
|
|
21
|
+
|
|
22
|
+
// Display-side rendering item. Derived from the server's ProviderMessage[]
|
|
23
|
+
// (history) and SessionEvent stream (live turn). Tool-call/result pairs are
|
|
24
|
+
// collapsed into a single tool item so the UI doesn't show them as separate
|
|
25
|
+
// rows.
|
|
26
|
+
type Item =
|
|
27
|
+
| { id: string; kind: 'user'; text: string }
|
|
28
|
+
| { id: string; kind: 'assistant'; text: string }
|
|
29
|
+
| { id: string; kind: 'tool'; name: string; result?: string; error?: string }
|
|
30
|
+
| { id: string; kind: 'error'; text: string }
|
|
31
|
+
|
|
32
|
+
let _itemIdSeq = 0
|
|
33
|
+
const nextItemId = (): string => `i${++_itemIdSeq}`
|
|
34
|
+
|
|
35
|
+
/** Turn the server's flattened ProviderMessage[] (history) into display Items. */
|
|
36
|
+
function historyToItems(messages: ProviderMessage[]): Item[] {
|
|
37
|
+
const out: Item[] = []
|
|
38
|
+
for (const m of messages) {
|
|
39
|
+
if (m.role === 'user') {
|
|
40
|
+
out.push({ id: nextItemId(), kind: 'user', text: m.content })
|
|
41
|
+
} else if (m.role === 'assistant') {
|
|
42
|
+
if (m.content) out.push({ id: nextItemId(), kind: 'assistant', text: m.content })
|
|
43
|
+
for (const tc of m.toolCalls ?? []) {
|
|
44
|
+
out.push({ id: tc.id, kind: 'tool', name: tc.name })
|
|
45
|
+
}
|
|
46
|
+
} else if (m.role === 'tool') {
|
|
47
|
+
// Backfill matching tool item with its result. The tool entry was
|
|
48
|
+
// pushed when we saw the assistant's tool_call above; find by id.
|
|
49
|
+
const idx = out.findIndex((it) => it.kind === 'tool' && it.id === m.toolCallId)
|
|
50
|
+
if (idx >= 0) {
|
|
51
|
+
const existing = out[idx]
|
|
52
|
+
if (existing && existing.kind === 'tool') {
|
|
53
|
+
out[idx] = { ...existing, result: m.content }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return out
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Apply a SessionEvent emitted during a turn to the running items list.
|
|
63
|
+
* Pure — caller commits the new array via setState.
|
|
64
|
+
*/
|
|
65
|
+
function applyEvent(items: Item[], ev: SessionEvent): Item[] {
|
|
66
|
+
switch (ev.type) {
|
|
67
|
+
case 'user_message':
|
|
68
|
+
// Already appended locally on send; ignore the echo.
|
|
69
|
+
return items
|
|
70
|
+
case 'assistant_message':
|
|
71
|
+
return [...items, { id: nextItemId(), kind: 'assistant', text: ev.text }]
|
|
72
|
+
case 'assistant_delta': {
|
|
73
|
+
const last = items[items.length - 1]
|
|
74
|
+
if (last && last.kind === 'assistant') {
|
|
75
|
+
return [...items.slice(0, -1), { ...last, text: last.text + ev.delta }]
|
|
76
|
+
}
|
|
77
|
+
return [...items, { id: nextItemId(), kind: 'assistant', text: ev.delta }]
|
|
78
|
+
}
|
|
79
|
+
case 'tool_call':
|
|
80
|
+
return [...items, { id: ev.id, kind: 'tool', name: ev.name }]
|
|
81
|
+
case 'tool_result':
|
|
82
|
+
case 'tool_error': {
|
|
83
|
+
const idx = items.findIndex((it) => it.kind === 'tool' && it.id === ev.id)
|
|
84
|
+
if (idx < 0) return items
|
|
85
|
+
const existing = items[idx]
|
|
86
|
+
if (!existing || existing.kind !== 'tool') return items
|
|
87
|
+
const updated: Item =
|
|
88
|
+
ev.type === 'tool_result'
|
|
89
|
+
? { ...existing, result: ev.result }
|
|
90
|
+
: { ...existing, error: ev.error }
|
|
91
|
+
return [...items.slice(0, idx), updated, ...items.slice(idx + 1)]
|
|
92
|
+
}
|
|
93
|
+
case 'error':
|
|
94
|
+
return [...items, { id: nextItemId(), kind: 'error', text: ev.error }]
|
|
95
|
+
default:
|
|
96
|
+
return items
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default function ChatScreen() {
|
|
101
|
+
const { id } = useLocalSearchParams<{ id: string }>()
|
|
102
|
+
const [items, setItems] = useState<Item[]>([])
|
|
103
|
+
const [creds, setCreds] = useState<Credentials | null>(null)
|
|
104
|
+
const [draft, setDraft] = useState('')
|
|
105
|
+
const [loadState, setLoadState] = useState<'loading' | 'ready' | 'error'>('loading')
|
|
106
|
+
const [loadError, setLoadError] = useState<string | null>(null)
|
|
107
|
+
const [sending, setSending] = useState(false)
|
|
108
|
+
const colors = useColors()
|
|
109
|
+
const styles = useMemo(() => makeStyles(colors), [colors])
|
|
110
|
+
// Real header height (varies with notch / dynamic island / safe-area
|
|
111
|
+
// insets) so KeyboardAvoidingView lifts the input box above the keyboard
|
|
112
|
+
// by exactly the right amount.
|
|
113
|
+
const headerHeight = useHeaderHeight()
|
|
114
|
+
// Bottom inset clears the home indicator on iPhones with curved
|
|
115
|
+
// corners — without this, the input row gets clipped at rest.
|
|
116
|
+
const insets = useSafeAreaInsets()
|
|
117
|
+
|
|
118
|
+
// FlatList is `inverted` so the visual bottom is index 0 of the rendered
|
|
119
|
+
// data — chronological items get reversed at render time. This makes the
|
|
120
|
+
// list anchor itself to the bottom by default; no scrollToEnd dance.
|
|
121
|
+
const reversedItems = useMemo(() => [...items].reverse(), [items])
|
|
122
|
+
|
|
123
|
+
// History fetch on mount.
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
let cancelled = false
|
|
126
|
+
;(async () => {
|
|
127
|
+
const c = await loadCredentials()
|
|
128
|
+
if (!c) {
|
|
129
|
+
router.replace('/pair')
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
if (cancelled) return
|
|
133
|
+
setCreds(c)
|
|
134
|
+
try {
|
|
135
|
+
const res = await fetch(`${c.server}/api/agents/${id}/sessions/messages`, {
|
|
136
|
+
headers: { authorization: `Bearer ${c.token}`, origin: c.server },
|
|
137
|
+
})
|
|
138
|
+
if (res.status === 401) {
|
|
139
|
+
await clearCredentials()
|
|
140
|
+
router.replace('/pair')
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
if (!res.ok) {
|
|
144
|
+
throw new Error(`server returned ${res.status} loading history`)
|
|
145
|
+
}
|
|
146
|
+
const body = (await res.json()) as { messages: ProviderMessage[] }
|
|
147
|
+
if (cancelled) return
|
|
148
|
+
setItems(historyToItems(body.messages))
|
|
149
|
+
setLoadState('ready')
|
|
150
|
+
} catch (err) {
|
|
151
|
+
if (cancelled) return
|
|
152
|
+
setLoadError(err instanceof Error ? err.message : 'unknown error')
|
|
153
|
+
setLoadState('error')
|
|
154
|
+
}
|
|
155
|
+
})()
|
|
156
|
+
return () => {
|
|
157
|
+
cancelled = true
|
|
158
|
+
}
|
|
159
|
+
}, [id])
|
|
160
|
+
|
|
161
|
+
const onSend = useCallback(async () => {
|
|
162
|
+
if (!creds || sending) return
|
|
163
|
+
const text = draft.trim()
|
|
164
|
+
if (!text) return
|
|
165
|
+
|
|
166
|
+
setDraft('')
|
|
167
|
+
setSending(true)
|
|
168
|
+
setItems((prev) => [...prev, { id: nextItemId(), kind: 'user', text }])
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const res = await fetch(`${creds.server}/api/agents/${id}/chat`, {
|
|
172
|
+
method: 'POST',
|
|
173
|
+
headers: {
|
|
174
|
+
authorization: `Bearer ${creds.token}`,
|
|
175
|
+
origin: creds.server,
|
|
176
|
+
'content-type': 'application/json',
|
|
177
|
+
},
|
|
178
|
+
body: JSON.stringify({ message: text }),
|
|
179
|
+
})
|
|
180
|
+
if (res.status === 401) {
|
|
181
|
+
await clearCredentials()
|
|
182
|
+
router.replace('/pair')
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
if (!res.ok) {
|
|
186
|
+
const errBody = (await res.json().catch(() => ({ error: res.statusText }))) as {
|
|
187
|
+
error?: string
|
|
188
|
+
}
|
|
189
|
+
throw new Error(errBody.error ?? `server returned ${res.status}`)
|
|
190
|
+
}
|
|
191
|
+
// Buffered NDJSON: read the whole body, split per line, parse each
|
|
192
|
+
// ChatFrame, then apply contained SessionEvents in order. RN's
|
|
193
|
+
// ReadableStream support is platform-flaky; buffering trades the
|
|
194
|
+
// typing-effect for reliability.
|
|
195
|
+
const raw = await res.text()
|
|
196
|
+
const frames: ChatFrame[] = raw
|
|
197
|
+
.split('\n')
|
|
198
|
+
.filter((l) => l.trim())
|
|
199
|
+
.map((l) => JSON.parse(l) as ChatFrame)
|
|
200
|
+
|
|
201
|
+
let next = items
|
|
202
|
+
for (const frame of frames) {
|
|
203
|
+
if (frame.kind === 'event') {
|
|
204
|
+
next = applyEvent(next, frame.event)
|
|
205
|
+
// Apply via setState too so each frame committed visibly when
|
|
206
|
+
// the chunk lands. (We defer to the final setState below for
|
|
207
|
+
// efficiency; this comment notes intent.)
|
|
208
|
+
} else if (frame.kind === 'fatal') {
|
|
209
|
+
next = [...next, { id: nextItemId(), kind: 'error', text: frame.error }]
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
setItems(next)
|
|
213
|
+
} catch (err) {
|
|
214
|
+
const message = err instanceof Error ? err.message : 'unknown error'
|
|
215
|
+
setItems((prev) => [...prev, { id: nextItemId(), kind: 'error', text: message }])
|
|
216
|
+
} finally {
|
|
217
|
+
setSending(false)
|
|
218
|
+
}
|
|
219
|
+
}, [creds, draft, id, items, sending])
|
|
220
|
+
|
|
221
|
+
if (loadState === 'loading') {
|
|
222
|
+
return (
|
|
223
|
+
<View style={styles.centered}>
|
|
224
|
+
<ActivityIndicator />
|
|
225
|
+
</View>
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (loadState === 'error') {
|
|
230
|
+
return (
|
|
231
|
+
<View style={styles.centered}>
|
|
232
|
+
<Text style={styles.errorTitle}>Couldn't load chat</Text>
|
|
233
|
+
<Text style={styles.errorBody}>{loadError}</Text>
|
|
234
|
+
</View>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<KeyboardAvoidingView
|
|
240
|
+
style={styles.screen}
|
|
241
|
+
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
242
|
+
keyboardVerticalOffset={headerHeight}
|
|
243
|
+
>
|
|
244
|
+
<FlatList
|
|
245
|
+
data={reversedItems}
|
|
246
|
+
inverted
|
|
247
|
+
keyExtractor={(it) => it.id}
|
|
248
|
+
renderItem={({ item }) => <Bubble item={item} />}
|
|
249
|
+
contentContainerStyle={styles.listContent}
|
|
250
|
+
ListEmptyComponent={
|
|
251
|
+
<View style={styles.empty}>
|
|
252
|
+
<Text style={styles.emptyText}>Start the conversation.</Text>
|
|
253
|
+
</View>
|
|
254
|
+
}
|
|
255
|
+
/>
|
|
256
|
+
{sending ? (
|
|
257
|
+
<View style={styles.thinking}>
|
|
258
|
+
<ActivityIndicator size="small" color={colors.mochaLight} />
|
|
259
|
+
<Text style={styles.thinkingText}>thinking…</Text>
|
|
260
|
+
</View>
|
|
261
|
+
) : null}
|
|
262
|
+
<View style={[styles.inputRow, { paddingBottom: 4 + insets.bottom }]}>
|
|
263
|
+
<TextInput
|
|
264
|
+
value={draft}
|
|
265
|
+
onChangeText={setDraft}
|
|
266
|
+
placeholder="Message"
|
|
267
|
+
placeholderTextColor={colors.fawn}
|
|
268
|
+
style={styles.input}
|
|
269
|
+
multiline
|
|
270
|
+
editable={!sending}
|
|
271
|
+
/>
|
|
272
|
+
<Pressable
|
|
273
|
+
onPress={onSend}
|
|
274
|
+
disabled={sending || !draft.trim()}
|
|
275
|
+
style={({ pressed }) => [
|
|
276
|
+
styles.sendBtn,
|
|
277
|
+
(sending || !draft.trim()) && styles.sendBtnDisabled,
|
|
278
|
+
pressed && styles.sendBtnPressed,
|
|
279
|
+
]}
|
|
280
|
+
>
|
|
281
|
+
<Text style={styles.sendBtnText}>Send</Text>
|
|
282
|
+
</Pressable>
|
|
283
|
+
</View>
|
|
284
|
+
</KeyboardAvoidingView>
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function Bubble({ item }: { item: Item }) {
|
|
289
|
+
const colors = useColors()
|
|
290
|
+
const styles = useMemo(() => makeStyles(colors), [colors])
|
|
291
|
+
const markdownStyles = useMemo(() => makeMarkdownStyles(colors), [colors])
|
|
292
|
+
if (item.kind === 'user') {
|
|
293
|
+
return (
|
|
294
|
+
<View style={[styles.bubbleRow, styles.bubbleRowRight]}>
|
|
295
|
+
<View style={[styles.bubble, styles.bubbleUser]}>
|
|
296
|
+
<Text style={styles.bubbleUserText}>{item.text}</Text>
|
|
297
|
+
</View>
|
|
298
|
+
</View>
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
if (item.kind === 'assistant') {
|
|
302
|
+
return (
|
|
303
|
+
<View style={[styles.bubbleRow, styles.bubbleRowLeft]}>
|
|
304
|
+
<View style={[styles.bubble, styles.bubbleAssistant]}>
|
|
305
|
+
<Markdown style={markdownStyles}>{item.text}</Markdown>
|
|
306
|
+
</View>
|
|
307
|
+
</View>
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
if (item.kind === 'tool') {
|
|
311
|
+
const status = item.error ? 'error' : item.result !== undefined ? 'done' : 'running'
|
|
312
|
+
return (
|
|
313
|
+
<View style={styles.toolRow}>
|
|
314
|
+
<Text style={styles.toolLabel}>
|
|
315
|
+
⚙ {item.name} · {status}
|
|
316
|
+
</Text>
|
|
317
|
+
{item.error ? (
|
|
318
|
+
<Text style={styles.toolError} numberOfLines={3}>
|
|
319
|
+
{item.error}
|
|
320
|
+
</Text>
|
|
321
|
+
) : item.result ? (
|
|
322
|
+
<Text style={styles.toolResult} numberOfLines={4}>
|
|
323
|
+
{item.result}
|
|
324
|
+
</Text>
|
|
325
|
+
) : null}
|
|
326
|
+
</View>
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
return (
|
|
330
|
+
<View style={styles.errorRow}>
|
|
331
|
+
<Text style={styles.errorBubbleText}>error: {item.text}</Text>
|
|
332
|
+
</View>
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const makeStyles = (colors: Colors) =>
|
|
337
|
+
StyleSheet.create({
|
|
338
|
+
screen: { flex: 1, backgroundColor: colors.background },
|
|
339
|
+
centered: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24, gap: 12 },
|
|
340
|
+
listContent: { padding: 12, paddingBottom: 24, gap: 8 },
|
|
341
|
+
empty: { padding: 32, alignItems: 'center' },
|
|
342
|
+
emptyText: { color: colors.mochaLight, fontSize: 13, fontFamily: fonts.body },
|
|
343
|
+
bubbleRow: { flexDirection: 'row' },
|
|
344
|
+
bubbleRowLeft: { justifyContent: 'flex-start' },
|
|
345
|
+
bubbleRowRight: { justifyContent: 'flex-end' },
|
|
346
|
+
bubble: { maxWidth: '85%', paddingHorizontal: 12, paddingVertical: 8, borderRadius: radii.lg },
|
|
347
|
+
bubbleUser: { backgroundColor: colors.sapphire },
|
|
348
|
+
bubbleUserText: { color: colors.primaryForeground, fontSize: 15, fontFamily: fonts.body },
|
|
349
|
+
bubbleAssistant: { backgroundColor: colors.card },
|
|
350
|
+
bubbleAssistantText: { color: colors.foreground, fontSize: 15, fontFamily: fonts.body },
|
|
351
|
+
toolRow: {
|
|
352
|
+
paddingHorizontal: 12,
|
|
353
|
+
paddingVertical: 6,
|
|
354
|
+
backgroundColor: colors.ivory,
|
|
355
|
+
borderRadius: radii.md,
|
|
356
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
357
|
+
borderColor: colors.border,
|
|
358
|
+
gap: 4,
|
|
359
|
+
},
|
|
360
|
+
toolLabel: { fontFamily: fonts.mono, fontSize: 12, color: colors.mocha },
|
|
361
|
+
toolResult: { fontFamily: fonts.mono, fontSize: 11, color: colors.mochaLight },
|
|
362
|
+
toolError: { fontFamily: fonts.mono, fontSize: 11, color: colors.destructive },
|
|
363
|
+
errorRow: {
|
|
364
|
+
paddingHorizontal: 12,
|
|
365
|
+
paddingVertical: 8,
|
|
366
|
+
// 6-char hex + 1A alpha suffix = ~10% opacity; tracks colors.destructive
|
|
367
|
+
// across both themes without needing a separate `destructiveBg` token.
|
|
368
|
+
backgroundColor: `${colors.destructive}1A`,
|
|
369
|
+
borderRadius: radii.md,
|
|
370
|
+
},
|
|
371
|
+
errorBubbleText: { color: colors.destructive, fontSize: 13, fontFamily: fonts.body },
|
|
372
|
+
thinking: {
|
|
373
|
+
flexDirection: 'row',
|
|
374
|
+
alignItems: 'center',
|
|
375
|
+
gap: 8,
|
|
376
|
+
paddingHorizontal: 16,
|
|
377
|
+
paddingVertical: 6,
|
|
378
|
+
},
|
|
379
|
+
thinkingText: {
|
|
380
|
+
color: colors.mochaLight,
|
|
381
|
+
fontSize: 12,
|
|
382
|
+
fontStyle: 'italic',
|
|
383
|
+
fontFamily: fonts.body,
|
|
384
|
+
},
|
|
385
|
+
inputRow: {
|
|
386
|
+
flexDirection: 'row',
|
|
387
|
+
alignItems: 'flex-end',
|
|
388
|
+
gap: 8,
|
|
389
|
+
paddingHorizontal: 12,
|
|
390
|
+
paddingTop: 8,
|
|
391
|
+
paddingBottom: 12,
|
|
392
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
393
|
+
borderTopColor: colors.border,
|
|
394
|
+
backgroundColor: colors.background,
|
|
395
|
+
},
|
|
396
|
+
input: {
|
|
397
|
+
flex: 1,
|
|
398
|
+
minHeight: 40,
|
|
399
|
+
maxHeight: 120,
|
|
400
|
+
paddingHorizontal: 12,
|
|
401
|
+
paddingVertical: 8,
|
|
402
|
+
borderRadius: radii.xl,
|
|
403
|
+
backgroundColor: colors.ivory,
|
|
404
|
+
fontSize: 15,
|
|
405
|
+
color: colors.foreground,
|
|
406
|
+
fontFamily: fonts.body,
|
|
407
|
+
},
|
|
408
|
+
sendBtn: {
|
|
409
|
+
paddingHorizontal: 16,
|
|
410
|
+
paddingVertical: 10,
|
|
411
|
+
borderRadius: radii.xl,
|
|
412
|
+
backgroundColor: colors.sapphire,
|
|
413
|
+
alignSelf: 'flex-end',
|
|
414
|
+
},
|
|
415
|
+
sendBtnDisabled: { backgroundColor: colors.mochaLight },
|
|
416
|
+
sendBtnPressed: { backgroundColor: colors.sapphireDeep },
|
|
417
|
+
sendBtnText: { color: colors.primaryForeground, fontSize: 14, fontFamily: fonts.bodyMedium },
|
|
418
|
+
errorTitle: { fontSize: 18, fontFamily: fonts.bodyBold, color: colors.foreground },
|
|
419
|
+
errorBody: { color: colors.destructive, textAlign: 'center', fontFamily: fonts.body },
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
// Style overrides for assistant-bubble markdown. Matches the surrounding
|
|
423
|
+
// bubble look (Baziu body font, foreground color) and trims default
|
|
424
|
+
// vertical paragraph margins so short replies don't get extra space.
|
|
425
|
+
const makeMarkdownStyles = (colors: Colors) =>
|
|
426
|
+
StyleSheet.create({
|
|
427
|
+
body: { color: colors.foreground, fontSize: 15, fontFamily: fonts.body },
|
|
428
|
+
paragraph: { marginTop: 0, marginBottom: 0 },
|
|
429
|
+
heading1: {
|
|
430
|
+
fontSize: 22,
|
|
431
|
+
fontFamily: fonts.display,
|
|
432
|
+
color: colors.foreground,
|
|
433
|
+
marginTop: 4,
|
|
434
|
+
marginBottom: 4,
|
|
435
|
+
},
|
|
436
|
+
heading2: {
|
|
437
|
+
fontSize: 19,
|
|
438
|
+
fontFamily: fonts.display,
|
|
439
|
+
color: colors.foreground,
|
|
440
|
+
marginTop: 4,
|
|
441
|
+
marginBottom: 4,
|
|
442
|
+
},
|
|
443
|
+
heading3: {
|
|
444
|
+
fontSize: 17,
|
|
445
|
+
fontFamily: fonts.bodyBold,
|
|
446
|
+
color: colors.foreground,
|
|
447
|
+
marginTop: 4,
|
|
448
|
+
marginBottom: 4,
|
|
449
|
+
},
|
|
450
|
+
strong: { fontFamily: fonts.bodyBold },
|
|
451
|
+
code_inline: {
|
|
452
|
+
fontFamily: fonts.mono,
|
|
453
|
+
fontSize: 13,
|
|
454
|
+
backgroundColor: colors.frost,
|
|
455
|
+
color: colors.charcoal,
|
|
456
|
+
paddingHorizontal: 4,
|
|
457
|
+
borderRadius: 4,
|
|
458
|
+
},
|
|
459
|
+
code_block: {
|
|
460
|
+
fontFamily: fonts.mono,
|
|
461
|
+
fontSize: 12,
|
|
462
|
+
backgroundColor: colors.frost,
|
|
463
|
+
color: colors.charcoal,
|
|
464
|
+
padding: 8,
|
|
465
|
+
borderRadius: radii.sm,
|
|
466
|
+
},
|
|
467
|
+
fence: {
|
|
468
|
+
fontFamily: fonts.mono,
|
|
469
|
+
fontSize: 12,
|
|
470
|
+
backgroundColor: colors.frost,
|
|
471
|
+
color: colors.charcoal,
|
|
472
|
+
padding: 8,
|
|
473
|
+
borderRadius: radii.sm,
|
|
474
|
+
},
|
|
475
|
+
link: { color: colors.sapphireDeep },
|
|
476
|
+
blockquote: {
|
|
477
|
+
backgroundColor: colors.ivory,
|
|
478
|
+
borderLeftColor: colors.sapphire,
|
|
479
|
+
borderLeftWidth: 3,
|
|
480
|
+
paddingLeft: 8,
|
|
481
|
+
paddingVertical: 4,
|
|
482
|
+
marginVertical: 4,
|
|
483
|
+
},
|
|
484
|
+
bullet_list: { marginVertical: 2 },
|
|
485
|
+
ordered_list: { marginVertical: 2 },
|
|
486
|
+
})
|