Update files
This commit is contained in:
@@ -6,8 +6,10 @@
|
||||
"start": "expo start",
|
||||
"reset-project": "node ./scripts/reset-project.js",
|
||||
"android": "npm run android:fast",
|
||||
"android:fast": "APP_VARIANT=fast expo run:android --variant=debugOptimized",
|
||||
"android:debug": "APP_VARIANT=debug expo run:android --variant=debug",
|
||||
"android:fast": "APP_VARIANT=fast bash ./scripts/android-run-variant.sh",
|
||||
"android:debug": "APP_VARIANT=debug bash ./scripts/android-run-variant.sh",
|
||||
"android:fast:build": "APP_VARIANT=fast bash ./scripts/android-build-variant.sh",
|
||||
"android:debug:build": "APP_VARIANT=debug bash ./scripts/android-build-variant.sh",
|
||||
"android:prod": "APP_VARIANT=prod expo run:android --variant=release",
|
||||
"android:release": "npm run android:prod",
|
||||
"ios": "expo run:ios",
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
app_variant="${APP_VARIANT:-fast}"
|
||||
|
||||
case "$app_variant" in
|
||||
fast)
|
||||
gradle_task="app:assembleDebugOptimized"
|
||||
apk_path="android/app/build/outputs/apk/debugOptimized/app-debugOptimized.apk"
|
||||
;;
|
||||
debug)
|
||||
gradle_task="app:assembleDebug"
|
||||
apk_path="android/app/build/outputs/apk/debug/app-debug.apk"
|
||||
;;
|
||||
*)
|
||||
echo "APP_VARIANT must be one of: fast, debug"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
APP_VARIANT="$app_variant" npx expo prebuild --platform android --clean --no-install
|
||||
(cd android && ./gradlew "$gradle_task")
|
||||
|
||||
echo "Built APK: ${apk_path}"
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
app_variant="${APP_VARIANT:-fast}"
|
||||
metro_endpoint="${METRO_ENDPOINT:-localhost:8080}"
|
||||
launch_app="${LAUNCH_APP:-1}"
|
||||
|
||||
case "$app_variant" in
|
||||
fast)
|
||||
package_id="sh.paseo.dev"
|
||||
app_name="Paseo Dev"
|
||||
;;
|
||||
debug)
|
||||
package_id="sh.paseo.debug"
|
||||
app_name="Paseo Debug"
|
||||
;;
|
||||
*)
|
||||
echo "APP_VARIANT must be one of: fast, debug"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if ! [[ "$metro_endpoint" =~ ^[A-Za-z0-9._-]+:[0-9]{1,5}$ ]]; then
|
||||
echo "Invalid METRO_ENDPOINT '$metro_endpoint'. Expected host:port (for example macbook:8081)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v adb >/dev/null 2>&1; then
|
||||
echo "adb is not installed or not on PATH."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! adb get-state >/dev/null 2>&1; then
|
||||
echo "No Android device detected by adb."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
metro_host="${metro_endpoint%:*}"
|
||||
metro_port="${metro_endpoint##*:}"
|
||||
|
||||
if [[ "$metro_host" == "localhost" || "$metro_host" == "127.0.0.1" ]]; then
|
||||
if ! lsof -nP -iTCP:"${metro_port}" -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
if [[ "$metro_port" == "8080" ]] && lsof -nP -iTCP:8081 -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
metro_endpoint="localhost:8081"
|
||||
metro_host="localhost"
|
||||
metro_port="8081"
|
||||
echo "Metro was not listening on localhost:8080; using localhost:8081."
|
||||
else
|
||||
echo "No Metro server detected on ${metro_endpoint}. Start Metro there or set METRO_ENDPOINT=host:port."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
adb reverse "tcp:${metro_port}" "tcp:${metro_port}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
prefs_file="${package_id}_preferences.xml"
|
||||
prefs_path="shared_prefs/${prefs_file}"
|
||||
prefs_xml="<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>
|
||||
<map>
|
||||
<string name=\"debug_http_host\">${metro_endpoint}</string>
|
||||
</map>"
|
||||
|
||||
adb shell run-as "$package_id" mkdir -p shared_prefs
|
||||
printf '%s\n' "$prefs_xml" | adb shell run-as "$package_id" tee "$prefs_path" >/dev/null
|
||||
|
||||
echo "Configured ${app_name} (${package_id}) to use Metro at ${metro_endpoint}."
|
||||
|
||||
if [[ "$launch_app" == "1" ]]; then
|
||||
adb shell am force-stop "$package_id" >/dev/null 2>&1 || true
|
||||
adb shell monkey -p "$package_id" -c android.intent.category.LAUNCHER 1 >/dev/null
|
||||
echo "Launched ${app_name}."
|
||||
fi
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
app_variant="${APP_VARIANT:-fast}"
|
||||
metro_endpoint="${METRO_ENDPOINT:-localhost:8080}"
|
||||
|
||||
case "$app_variant" in
|
||||
fast)
|
||||
gradle_variant="debugOptimized"
|
||||
;;
|
||||
debug)
|
||||
gradle_variant="debug"
|
||||
;;
|
||||
*)
|
||||
echo "APP_VARIANT must be one of: fast, debug"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
APP_VARIANT="$app_variant" npx expo run:android --variant="$gradle_variant"
|
||||
APP_VARIANT="$app_variant" METRO_ENDPOINT="$metro_endpoint" LAUNCH_APP=1 bash ./scripts/android-configure-metro-host.sh
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Text,
|
||||
ActivityIndicator,
|
||||
Platform,
|
||||
Alert,
|
||||
} from "react-native";
|
||||
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
@@ -425,15 +424,10 @@ export function AgentInputArea({
|
||||
return;
|
||||
}
|
||||
if (voice.isVoiceModeForAgent(serverId, agentId)) {
|
||||
void voice.stopVoice().catch((error) => {
|
||||
console.error("[AgentInputArea] Failed to stop voice mode", error);
|
||||
Alert.alert("Voice failed", "Unable to stop realtime voice mode.");
|
||||
});
|
||||
return;
|
||||
}
|
||||
void voice.startVoice(serverId, agentId).catch((error) => {
|
||||
console.error("[AgentInputArea] Failed to start voice mode", error);
|
||||
Alert.alert("Voice failed", "Unable to start realtime voice mode.");
|
||||
});
|
||||
}, [agentId, isConnected, serverId, voice]);
|
||||
|
||||
@@ -543,29 +537,26 @@ export function AgentInputArea({
|
||||
|
||||
const rightContent = (
|
||||
<View style={styles.rightControls}>
|
||||
<Pressable
|
||||
onPress={handleToggleRealtimeVoice}
|
||||
disabled={!isConnected || voice?.isVoiceSwitching}
|
||||
accessibilityLabel={
|
||||
isVoiceModeForAgent ? "Disable realtime voice mode" : "Enable realtime voice mode"
|
||||
}
|
||||
accessibilityRole="button"
|
||||
style={[
|
||||
styles.realtimeVoiceButton as any,
|
||||
isVoiceModeForAgent ? (styles.realtimeVoiceButtonActive as any) : undefined,
|
||||
(!isConnected || voice?.isVoiceSwitching
|
||||
? styles.buttonDisabled
|
||||
: undefined) as any,
|
||||
]}
|
||||
>
|
||||
{voice?.isVoiceSwitching ? (
|
||||
<ActivityIndicator size="small" color="white" />
|
||||
) : isVoiceModeForAgent ? (
|
||||
<Square size={16} color="white" fill="white" />
|
||||
) : (
|
||||
<AudioLines size={18} color={theme.colors.foreground} />
|
||||
)}
|
||||
</Pressable>
|
||||
{!isVoiceModeForAgent ? (
|
||||
<Pressable
|
||||
onPress={handleToggleRealtimeVoice}
|
||||
disabled={!isConnected || voice?.isVoiceSwitching}
|
||||
accessibilityLabel="Enable realtime voice mode"
|
||||
accessibilityRole="button"
|
||||
style={[
|
||||
styles.realtimeVoiceButton as any,
|
||||
(!isConnected || voice?.isVoiceSwitching
|
||||
? styles.buttonDisabled
|
||||
: undefined) as any,
|
||||
]}
|
||||
>
|
||||
{voice?.isVoiceSwitching ? (
|
||||
<ActivityIndicator size="small" color="white" />
|
||||
) : (
|
||||
<AudioLines size={18} color={theme.colors.foreground} />
|
||||
)}
|
||||
</Pressable>
|
||||
) : null}
|
||||
{cancelButton}
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { Pressable, View } from "react-native";
|
||||
import { ActivityIndicator, Alert, Pressable, View } from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { Mic, MicOff } from "lucide-react-native";
|
||||
import { Mic, MicOff, Square } from "lucide-react-native";
|
||||
import { VolumeMeter } from "@/components/volume-meter";
|
||||
import { useVoice } from "@/contexts/voice-context";
|
||||
|
||||
export function VoiceCompactIndicator() {
|
||||
const { theme } = useUnistyles();
|
||||
const { isVoiceMode, volume, isMuted, isDetecting, isSpeaking, toggleMute } =
|
||||
useVoice();
|
||||
const {
|
||||
isVoiceMode,
|
||||
isVoiceSwitching,
|
||||
volume,
|
||||
isMuted,
|
||||
isDetecting,
|
||||
isSpeaking,
|
||||
toggleMute,
|
||||
stopVoice,
|
||||
} = useVoice();
|
||||
|
||||
if (!isVoiceMode) {
|
||||
return null;
|
||||
@@ -26,19 +34,52 @@ export function VoiceCompactIndicator() {
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Pressable
|
||||
onPress={toggleMute}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={isMuted ? "Unmute voice" : "Mute voice"}
|
||||
style={styles.muteButton}
|
||||
hitSlop={8}
|
||||
>
|
||||
{isMuted ? (
|
||||
<MicOff size={14} color={theme.colors.palette.white} />
|
||||
) : (
|
||||
<Mic size={14} color={theme.colors.foreground} />
|
||||
)}
|
||||
</Pressable>
|
||||
<View style={styles.controlsRow}>
|
||||
<Pressable
|
||||
onPress={toggleMute}
|
||||
disabled={isVoiceSwitching}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={isMuted ? "Unmute voice" : "Mute voice"}
|
||||
style={[
|
||||
styles.muteButton,
|
||||
isVoiceSwitching ? styles.buttonDisabled : undefined,
|
||||
]}
|
||||
hitSlop={8}
|
||||
>
|
||||
{isMuted ? (
|
||||
<MicOff size={14} color={theme.colors.palette.white} />
|
||||
) : (
|
||||
<Mic size={14} color={theme.colors.foreground} />
|
||||
)}
|
||||
</Pressable>
|
||||
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
void stopVoice().catch((error) => {
|
||||
console.error("[VoiceCompactIndicator] Failed to stop voice mode", error);
|
||||
Alert.alert("Voice failed", "Unable to stop realtime voice mode.");
|
||||
});
|
||||
}}
|
||||
disabled={isVoiceSwitching}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Disable realtime voice mode"
|
||||
style={[
|
||||
styles.stopButton,
|
||||
isVoiceSwitching ? styles.buttonDisabled : undefined,
|
||||
]}
|
||||
hitSlop={8}
|
||||
>
|
||||
{isVoiceSwitching ? (
|
||||
<ActivityIndicator size="small" color={theme.colors.palette.white} />
|
||||
) : (
|
||||
<Square
|
||||
size={14}
|
||||
color={theme.colors.palette.white}
|
||||
fill={theme.colors.palette.white}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -47,7 +88,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
container: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
gap: theme.spacing[1],
|
||||
paddingLeft: theme.spacing[3],
|
||||
paddingRight: theme.spacing[1],
|
||||
height: 32,
|
||||
@@ -63,6 +104,11 @@ const styles = StyleSheet.create((theme) => ({
|
||||
meterContainer: {
|
||||
justifyContent: "center",
|
||||
},
|
||||
controlsRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[1],
|
||||
},
|
||||
muteButton: {
|
||||
width: 28,
|
||||
height: 28,
|
||||
@@ -72,4 +118,17 @@ const styles = StyleSheet.create((theme) => ({
|
||||
backgroundColor: "transparent",
|
||||
borderWidth: 0,
|
||||
},
|
||||
stopButton: {
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: theme.colors.palette.red[600],
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.palette.red[800],
|
||||
},
|
||||
buttonDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -111,6 +111,18 @@ describe("parseToolCallDisplay", () => {
|
||||
expect(display.toolName).toBe("Read");
|
||||
});
|
||||
|
||||
test("normalizes tool names - paseo_voice.speak to Speak", () => {
|
||||
const input = { text: "hello from namespaced speak" };
|
||||
const display = parseToolCallDisplay("paseo_voice.speak", input, undefined);
|
||||
expect(display.toolName).toBe("Speak");
|
||||
});
|
||||
|
||||
test("normalizes tool names - mcp__paseo_voice__speak to Speak", () => {
|
||||
const input = { text: "hello from claude mcp speak" };
|
||||
const display = parseToolCallDisplay("mcp__paseo_voice__speak", input, undefined);
|
||||
expect(display.toolName).toBe("Speak");
|
||||
});
|
||||
|
||||
test("preserves unknown tool names", () => {
|
||||
const input = { some_arg: "value" };
|
||||
const display = parseToolCallDisplay("MyCustomTool", input, undefined);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import stripAnsi from "strip-ansi";
|
||||
import { z } from "zod";
|
||||
import { stripShellWrapperPrefix } from "@getpaseo/server/utils/tool-call-parsers";
|
||||
import {
|
||||
normalizeToolDisplayName,
|
||||
stripShellWrapperPrefix,
|
||||
} from "@getpaseo/server/utils/tool-call-parsers";
|
||||
import { getNowMs, isPerfLoggingEnabled, perfLog } from "./perf";
|
||||
|
||||
const TOOL_CALL_DIFF_LOG_TAG = "[ToolCallDiff]";
|
||||
@@ -1203,7 +1206,9 @@ const ToolCallDisplaySchema = z
|
||||
result: z.unknown(),
|
||||
})
|
||||
.transform((data) => {
|
||||
const normalizedToolName = TOOL_NAME_MAP[data.toolName] ?? data.toolName;
|
||||
const normalizedToolName = normalizeToolDisplayName(
|
||||
TOOL_NAME_MAP[data.toolName] ?? data.toolName
|
||||
);
|
||||
|
||||
// Handle thinking - input is the thinking text content
|
||||
if (data.toolName === "thinking") {
|
||||
|
||||
Reference in New Issue
Block a user